@@ -5,26 +5,34 @@ import { Section, sectionNames } from "lowcoder-design";
55import styled from "styled-components" ;
66import { clickEvent , eventHandlerControl } from "comps/controls/eventHandlerControl" ;
77import { BoolCodeControl , StringControl } from "comps/controls/codeControl" ;
8+ import { dropdownControl } from "comps/controls/dropdownControl" ;
89import { alignWithJustifyControl } from "comps/controls/alignControl" ;
910import { navListComp } from "./navItemComp" ;
1011import { menuPropertyView } from "./components/MenuItemList" ;
1112import { default as DownOutlined } from "@ant-design/icons/DownOutlined" ;
13+ import { default as MenuOutlined } from "@ant-design/icons/MenuOutlined" ;
1214import { default as Dropdown } from "antd/es/dropdown" ;
1315import { default as Menu , MenuProps } from "antd/es/menu" ;
16+ import { default as Drawer } from "antd/es/drawer" ;
1417import { migrateOldData } from "comps/generators/simpleGenerators" ;
1518import { styleControl } from "comps/controls/styleControl" ;
1619import {
1720AnimationStyle ,
1821AnimationStyleType ,
1922NavigationStyle ,
23+ HamburgerButtonStyle ,
24+ DrawerContainerStyle ,
25+ NavLayoutItemStyle ,
26+ NavLayoutItemHoverStyle ,
27+ NavLayoutItemActiveStyle ,
2028} from "comps/controls/styleControlConstants" ;
2129import { hiddenPropertyView , showDataLoadingIndicatorsPropertyView } from "comps/utils/propertyUtils" ;
2230import { trans } from "i18n" ;
2331
24- import { useContext } from "react" ;
32+ import { useContext , useState } from "react" ;
2533import { EditorContext } from "comps/editorState" ;
26- import { controlItem } from "lowcoder-design" ;
2734import { createNavItemsControl } from "./components/NavItemsControl" ;
35+ import { Layers } from "constants/Layers" ;
2836
2937type 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
3948const 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
5768const 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+
117172const logoEventHandlers = [ clickEvent ] ;
118173
119174// Compatible with historical style data 2022-8-26
@@ -154,8 +209,33 @@ function fixOldItemsData(oldData: any) {
154209const childrenMap = {
155210logoUrl :StringControl ,
156211logoEvent :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 ) ,
157232horizontalAlignment :alignWithJustifyControl ( ) ,
158233style :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' ) ,
159239animationStyle :styleControl ( AnimationStyle , 'animationStyle' ) ,
160240items :withDefault ( migrateOldData ( createNavItemsControl ( ) , fixOldItemsData ) , {
161241optionType :"manual" ,
@@ -168,6 +248,7 @@ const childrenMap = {
168248} ;
169249
170250const NavCompBase = new UICompBuilder ( childrenMap , ( props ) => {
251+ const [ drawerVisible , setDrawerVisible ] = useState ( false ) ;
171252const data = props . items ;
172253const items = (
173254< >
@@ -207,16 +288,24 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => {
207288< Item
208289key = { 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 }
221310onClick = { ( ) => { if ( ! disabled && onEvent ) onEvent ( "click" ) ; } }
222311>
@@ -255,6 +344,8 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => {
255344) ;
256345
257346const justify = props . horizontalAlignment === "justify" ;
347+ const isVertical = props . orientation === "vertical" ;
348+ const isHamburger = props . displayMode === "hamburger" ;
258349
259350return (
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 >