Skip to content

Commit 1ceefdb

Browse files
committed
add navigation component burger mode
1 parent 2a82955 commit 1ceefdb

File tree

1 file changed

+176
-23
lines changed
  • client/packages/lowcoder/src/comps/comps/navComp

1 file changed

+176
-23
lines changed

client/packages/lowcoder/src/comps/comps/navComp/navComp.tsx

Lines changed: 176 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,34 @@ import { Section, sectionNames } from "lowcoder-design";
55
import styled from "styled-components";
66
import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl";
77
import { BoolCodeControl, StringControl } from "comps/controls/codeControl";
8+
import { dropdownControl } from "comps/controls/dropdownControl";
89
import { alignWithJustifyControl } from "comps/controls/alignControl";
910
import { navListComp } from "./navItemComp";
1011
import { menuPropertyView } from "./components/MenuItemList";
1112
import { default as DownOutlined } from "@ant-design/icons/DownOutlined";
13+
import { default as MenuOutlined } from "@ant-design/icons/MenuOutlined";
1214
import { default as Dropdown } from "antd/es/dropdown";
1315
import { default as Menu, MenuProps } from "antd/es/menu";
16+
import { default as Drawer } from "antd/es/drawer";
1417
import { migrateOldData } from "comps/generators/simpleGenerators";
1518
import { styleControl } from "comps/controls/styleControl";
1619
import {
1720
AnimationStyle,
1821
AnimationStyleType,
1922
NavigationStyle,
23+
HamburgerButtonStyle,
24+
DrawerContainerStyle,
25+
NavLayoutItemStyle,
26+
NavLayoutItemHoverStyle,
27+
NavLayoutItemActiveStyle,
2028
} from "comps/controls/styleControlConstants";
2129
import { hiddenPropertyView, showDataLoadingIndicatorsPropertyView } from "comps/utils/propertyUtils";
2230
import { trans } from "i18n";
2331

24-
import { useContext } from "react";
32+
import { useContext, useState } from "react";
2533
import { EditorContext } from "comps/editorState";
26-
import { controlItem } from "lowcoder-design";
2734
import { createNavItemsControl } from "./components/NavItemsControl";
35+
import { Layers } from "constants/Layers";
2836

2937
type IProps = {
3038
$justify: boolean;
@@ -34,6 +42,7 @@ type IProps = {
3442
$borderRadius: string;
3543
$borderStyle: string;
3644
$animationStyle: AnimationStyleType;
45+
$orientation: "horizontal" | "vertical";
3746
};
3847

3948
const Wrapper = styled("div")<
@@ -45,18 +54,21 @@ ${props=>props.$animationStyle}
4554
box-sizing: border-box;
4655
border: ${(props) => props.$borderWidth ? `${props.$borderWidth}` : '1px'} ${props=>props.$borderStyle} ${(props) => props.$borderColor};
4756
background: ${(props) => props.$bgColor};
57+
position: relative;
4858
`;
4959

50-
const NavInner = styled("div") <Pick<IProps, "$justify">>`
60+
const NavInner = styled("div") <Pick<IProps, "$justify" | "$orientation">>`
5161
// margin: 0 -16px;
5262
height: 100%;
5363
display: flex;
54-
justify-content: ${(props) => (props.$justify ? "space-between" : "left")};
64+
flex-direction: ${(props) => (props.$orientation === "vertical" ? "column" : "row")};
65+
justify-content: ${(props) => (props.$orientation === "vertical" ? "flex-start" : (props.$justify ? "space-between" : "left"))};
5566
`;
5667

5768
const Item = styled.div<{
5869
$active: boolean;
5970
$activeColor: string;
71+
$hoverColor: string;
6072
$color: string;
6173
$fontFamily: string;
6274
$fontStyle: string;
@@ -66,12 +78,22 @@ const Item = styled.div<{
6678
$padding: string;
6779
$textTransform:string;
6880
$textDecoration:string;
81+
$bg?: string;
82+
$hoverBg?: string;
83+
$activeBg?: string;
84+
$border?: string;
85+
$hoverBorder?: string;
86+
$activeBorder?: string;
87+
$radius?: string;
6988
$disabled?: boolean;
7089
}>`
7190
height: 30px;
7291
line-height: 30px;
7392
padding: ${(props) => props.$padding ? props.$padding : '0 16px'};
7493
color: ${(props) => props.$disabled ? `${props.$color}80` : (props.$active ? props.$activeColor : props.$color)};
94+
background-color: ${(props) => (props.$active ? (props.$activeBg || 'transparent') : (props.$bg || 'transparent'))};
95+
border: ${(props) => props.$border ? `1px solid ${props.$border}` : '1px solid transparent'};
96+
border-radius: ${(props) => props.$radius ? props.$radius : '0px'};
7597
font-weight: ${(props) => (props.$textWeight ? props.$textWeight : 500)};
7698
font-family:${(props) => (props.$fontFamily ? props.$fontFamily : 'sans-serif')};
7799
font-style:${(props) => (props.$fontStyle ? props.$fontStyle : 'normal')};
@@ -81,7 +103,9 @@ const Item = styled.div<{
81103
margin:${(props) => props.$margin ? props.$margin : '0px'};
82104
83105
&:hover {
84-
color: ${(props) => props.$disabled ? (props.$active ? props.$activeColor : props.$color) : props.$activeColor};
106+
color: ${(props) => props.$disabled ? (props.$active ? props.$activeColor : props.$color) : (props.$hoverColor || props.$activeColor)};
107+
background-color: ${(props) => props.$disabled ? (props.$active ? (props.$activeBg || 'transparent') : (props.$bg || 'transparent')) : (props.$hoverBg || props.$activeBg || props.$bg || 'transparent')};
108+
border: ${(props) => props.$hoverBorder ? `1px solid ${props.$hoverBorder}` : (props.$activeBorder ? `1px solid ${props.$activeBorder}` : (props.$border ? `1px solid ${props.$border}` : '1px solid transparent'))};
85109
cursor: ${(props) => props.$disabled ? 'not-allowed' : 'pointer'};
86110
}
87111
@@ -101,10 +125,10 @@ const LogoWrapper = styled.div`
101125
}
102126
`;
103127

104-
const ItemList = styled.div<{ $align: string }>`
128+
const ItemList = styled.div<{ $align: string, $orientation?: string }>`
105129
flex: 1;
106130
display: flex;
107-
flex-direction: row;
131+
flex-direction: ${(props) => (props.$orientation === "vertical" ? "column" : "row")};
108132
justify-content: ${(props) => props.$align};
109133
`;
110134

@@ -114,6 +138,37 @@ const StyledMenu = styled(Menu) <MenuProps>`
114138
}
115139
`;
116140

141+
const FloatingHamburgerButton = styled.button<{
142+
$size: string;
143+
$position: string; // top-right | top-left | bottom-right | bottom-left
144+
$zIndex: number;
145+
$background?: string;
146+
$borderColor?: string;
147+
$radius?: string;
148+
$margin?: string;
149+
$padding?: string;
150+
$borderWidth?: string;
151+
$iconColor?: string;
152+
}>`
153+
position: fixed;
154+
${(props) => (props.$position.includes('bottom') ? 'bottom: 16px;' : 'top: 16px;')}
155+
${(props) => (props.$position.includes('right') ? 'right: 16px;' : 'left: 16px;')}
156+
width: ${(props) => props.$size};
157+
height: ${(props) => props.$size};
158+
border-radius: ${(props) => props.$radius || '50%'};
159+
border: ${(props) => props.$borderWidth || '1px'} solid ${(props) => props.$borderColor || 'rgba(0,0,0,0.1)'};
160+
background: ${(props) => props.$background || 'white'};
161+
margin: ${(props) => props.$margin || '0px'};
162+
padding: ${(props) => props.$padding || '0px'};
163+
display: flex;
164+
align-items: center;
165+
justify-content: center;
166+
z-index: ${(props) => props.$zIndex};
167+
cursor: pointer;
168+
box-shadow: 0 6px 16px rgba(0,0,0,0.15);
169+
color: ${(props) => props.$iconColor || 'inherit'};
170+
`;
171+
117172
const logoEventHandlers = [clickEvent];
118173

119174
// Compatible with historical style data 2022-8-26
@@ -154,8 +209,33 @@ function fixOldItemsData(oldData: any) {
154209
const childrenMap = {
155210
logoUrl: StringControl,
156211
logoEvent: withDefault(eventHandlerControl(logoEventHandlers), [{ name: "click" }]),
212+
orientation: dropdownControl([
213+
{ label: "Horizontal", value: "horizontal" },
214+
{ label: "Vertical", value: "vertical" },
215+
], "horizontal"),
216+
displayMode: dropdownControl([
217+
{ label: "Bar", value: "bar" },
218+
{ label: "Hamburger", value: "hamburger" },
219+
], "bar"),
220+
hamburgerPosition: dropdownControl([
221+
{ label: "Top Right", value: "top-right" },
222+
{ label: "Top Left", value: "top-left" },
223+
{ label: "Bottom Right", value: "bottom-right" },
224+
{ label: "Bottom Left", value: "bottom-left" },
225+
], "top-right"),
226+
hamburgerSize: withDefault(StringControl, "56px"),
227+
drawerPlacement: dropdownControl([
228+
{ label: "Left", value: "left" },
229+
{ label: "Right", value: "right" },
230+
], "right"),
231+
shadowOverlay: withDefault(BoolCodeControl, true),
157232
horizontalAlignment: alignWithJustifyControl(),
158233
style: migrateOldData(styleControl(NavigationStyle, 'style'), fixOldStyleData),
234+
navItemStyle: styleControl(NavLayoutItemStyle, 'navItemStyle'),
235+
navItemHoverStyle: styleControl(NavLayoutItemHoverStyle, 'navItemHoverStyle'),
236+
navItemActiveStyle: styleControl(NavLayoutItemActiveStyle, 'navItemActiveStyle'),
237+
hamburgerButtonStyle: styleControl(HamburgerButtonStyle, 'hamburgerButtonStyle'),
238+
drawerContainerStyle: styleControl(DrawerContainerStyle, 'drawerContainerStyle'),
159239
animationStyle: styleControl(AnimationStyle, 'animationStyle'),
160240
items: withDefault(migrateOldData(createNavItemsControl(), fixOldItemsData), {
161241
optionType: "manual",
@@ -168,6 +248,7 @@ const childrenMap = {
168248
};
169249

170250
const NavCompBase = new UICompBuilder(childrenMap, (props) => {
251+
const [drawerVisible, setDrawerVisible] = useState(false);
171252
const data = props.items;
172253
const items = (
173254
<>
@@ -207,16 +288,24 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => {
207288
<Item
208289
key={idx}
209290
$active={active || subMenuSelectedKeys.length > 0}
210-
$color={props.style.text}
211-
$activeColor={props.style.accent}
291+
$color={(props.navItemStyle && props.navItemStyle.text) || props.style.text}
292+
$hoverColor={(props.navItemHoverStyle && props.navItemHoverStyle.text) || props.style.accent}
293+
$activeColor={(props.navItemActiveStyle && props.navItemActiveStyle.text) || props.style.accent}
212294
$fontFamily={props.style.fontFamily}
213295
$fontStyle={props.style.fontStyle}
214296
$textWeight={props.style.textWeight}
215297
$textSize={props.style.textSize}
216-
$padding={props.style.padding}
298+
$padding={(props.navItemStyle && props.navItemStyle.padding) || props.style.padding}
217299
$textTransform={props.style.textTransform}
218300
$textDecoration={props.style.textDecoration}
219-
$margin={props.style.margin}
301+
$margin={(props.navItemStyle && props.navItemStyle.margin) || props.style.margin}
302+
$bg={(props.navItemStyle && props.navItemStyle.background) || undefined}
303+
$hoverBg={(props.navItemHoverStyle && props.navItemHoverStyle.background) || undefined}
304+
$activeBg={(props.navItemActiveStyle && props.navItemActiveStyle.background) || undefined}
305+
$border={(props.navItemStyle && props.navItemStyle.border) || undefined}
306+
$hoverBorder={(props.navItemHoverStyle && props.navItemHoverStyle.border) || undefined}
307+
$activeBorder={(props.navItemActiveStyle && props.navItemActiveStyle.border) || undefined}
308+
$radius={(props.navItemStyle && props.navItemStyle.radius) || undefined}
220309
$disabled={disabled}
221310
onClick={() => { if (!disabled && onEvent) onEvent("click"); }}
222311
>
@@ -255,6 +344,8 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => {
255344
);
256345

257346
const justify = props.horizontalAlignment === "justify";
347+
const isVertical = props.orientation === "vertical";
348+
const isHamburger = props.displayMode === "hamburger";
258349

259350
return (
260351
<Wrapper
@@ -265,14 +356,46 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => {
265356
$borderWidth={props.style.borderWidth}
266357
$borderRadius={props.style.radius}
267358
>
268-
<NavInner $justify={justify}>
269-
{props.logoUrl && (
270-
<LogoWrapper onClick={() => props.logoEvent("click")}>
271-
<img src={props.logoUrl} alt="LOGO" />
272-
</LogoWrapper>
273-
)}
274-
{!justify ? <ItemList $align={props.horizontalAlignment}>{items}</ItemList> : items}
275-
</NavInner>
359+
{!isHamburger && (
360+
<NavInner $justify={justify} $orientation={isVertical ? "vertical" : "horizontal"}>
361+
{props.logoUrl && (
362+
<LogoWrapper onClick={() => props.logoEvent("click")}>
363+
<img src={props.logoUrl} alt="LOGO" />
364+
</LogoWrapper>
365+
)}
366+
{!justify ? <ItemList $align={props.horizontalAlignment} $orientation={isVertical ? "vertical" : "horizontal"}>{items}</ItemList> : items}
367+
</NavInner>
368+
)}
369+
{isHamburger && (
370+
<>
371+
<FloatingHamburgerButton
372+
$size={props.hamburgerSize || "56px"}
373+
$position={props.hamburgerPosition || "top-right"}
374+
$zIndex={Layers.tabBar + 1}
375+
$background={props.hamburgerButtonStyle?.background}
376+
$borderColor={props.hamburgerButtonStyle?.border}
377+
$radius={props.hamburgerButtonStyle?.radius}
378+
$margin={props.hamburgerButtonStyle?.margin}
379+
$padding={props.hamburgerButtonStyle?.padding}
380+
$borderWidth={props.hamburgerButtonStyle?.borderWidth}
381+
$iconColor={props.hamburgerButtonStyle?.iconFill}
382+
onClick={() => setDrawerVisible(true)}
383+
>
384+
<MenuOutlined />
385+
</FloatingHamburgerButton>
386+
<Drawer
387+
placement={(props.drawerPlacement as any) || "right"}
388+
closable={true}
389+
onClose={() => setDrawerVisible(false)}
390+
open={drawerVisible}
391+
mask={props.shadowOverlay}
392+
styles={{ body: { padding: "8px", background: props.drawerContainerStyle?.background } }}
393+
destroyOnClose
394+
>
395+
<ItemList $align={"flex-start"} $orientation={"vertical"}>{items}</ItemList>
396+
</Drawer>
397+
</>
398+
)}
276399
</Wrapper>
277400
);
278401
})
@@ -292,10 +415,21 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => {
292415

293416
{(useContext(EditorContext).editorModeStatus === "layout" || useContext(EditorContext).editorModeStatus === "both") && (
294417
<Section name={sectionNames.layout}>
295-
{children.horizontalAlignment.propertyView({
296-
label: trans("navigation.horizontalAlignment"),
297-
radioButton: true,
298-
})}
418+
{children.orientation.propertyView({ label: "Orientation", radioButton: true })}
419+
{children.displayMode.propertyView({ label: "Display Mode", radioButton: true })}
420+
{children.displayMode.getView() === 'hamburger' ? (
421+
[
422+
children.hamburgerPosition.propertyView({ label: "Hamburger Position" }),
423+
children.hamburgerSize.propertyView({ label: "Hamburger Size" }),
424+
children.drawerPlacement.propertyView({ label: "Drawer Placement", radioButton: true }),
425+
children.shadowOverlay.propertyView({ label: "Shadow Overlay" }),
426+
]
427+
) : (
428+
children.horizontalAlignment.propertyView({
429+
label: trans("navigation.horizontalAlignment"),
430+
radioButton: true,
431+
})
432+
)}
299433
{hiddenPropertyView(children)}
300434
</Section>
301435
)}
@@ -313,6 +447,25 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => {
313447
<Section name={sectionNames.style}>
314448
{children.style.getPropertyView()}
315449
</Section>
450+
<Section name={"Item Style"}>
451+
{children.navItemStyle.getPropertyView()}
452+
</Section>
453+
<Section name={"Item Hover Style"}>
454+
{children.navItemHoverStyle.getPropertyView()}
455+
</Section>
456+
<Section name={"Item Active Style"}>
457+
{children.navItemActiveStyle.getPropertyView()}
458+
</Section>
459+
{children.displayMode.getView() === 'hamburger' && (
460+
<>
461+
<Section name={"Hamburger Button Style"}>
462+
{children.hamburgerButtonStyle.getPropertyView()}
463+
</Section>
464+
<Section name={"Drawer Container Style"}>
465+
{children.drawerContainerStyle.getPropertyView()}
466+
</Section>
467+
</>
468+
)}
316469
<Section name={sectionNames.animationStyle} hasTooltip={true}>
317470
{children.animationStyle.getPropertyView()}
318471
</Section>

0 commit comments

Comments
 (0)