feat: Added Responsive Sidebar (#3303)
* feat: UIContext Added
* feat: window size hook added
* feat: UIContext provider attached to app
* fix: UI Changes for Responsive Sidebar
* fix: underscore removed from toggle option
* fix: dependency issue resolved
* fix: window size hooks resolved
* fix: handle resize function position changed
diff --git a/config-ui/src/components/Nav.jsx b/config-ui/src/components/Nav.jsx
index 403c9d7..347865f 100644
--- a/config-ui/src/components/Nav.jsx
+++ b/config-ui/src/components/Nav.jsx
@@ -15,73 +15,108 @@
* limitations under the License.
*
*/
-import React from 'react'
-import { Alignment, Position, Popover, Navbar, Icon } from '@blueprintjs/core'
+import React, { useCallback, useContext, useEffect, useState } from 'react'
+import { Position, Popover, Navbar, Icon } from '@blueprintjs/core'
import '@/styles/nav.scss'
import { ReactComponent as SlackIcon } from '@/images/slack-mark-monochrome-black.svg'
import { ReactComponent as SlackLogo } from '@/images/slack-rgb.svg'
+import UIContext from '@/store/UIContext'
+import useWindowSize from '@/hooks/useWIndowSize'
const Nav = () => {
+ const { changeSidebarVisibility, desktopBreakpointWidth, sidebarVisible } =
+ useContext(UIContext)
+ const [menuClass, setMenuClass] = useState('navbarMenuButton')
+ const size = useWindowSize()
+
+ const toggleSidebarOpen = useCallback(
+ (open) => {
+ changeSidebarVisibility(open)
+ setMenuClass(() =>
+ open ? 'navbarMenuButtonSidebarOpened' : 'navbarMenuButton'
+ )
+ },
+ [changeSidebarVisibility]
+ )
+
+ useEffect(
+ () =>
+ desktopBreakpointWidth <= size.width
+ ? toggleSidebarOpen(true)
+ : toggleSidebarOpen(false),
+ [size, toggleSidebarOpen, desktopBreakpointWidth]
+ )
+
return (
<Navbar className='navbar'>
- <Navbar.Group align={Alignment.RIGHT}>
- <a
- href='https://github.com/apache/incubator-devlake'
- rel='noreferrer'
- target='_blank'
- className='navIconLink'
- >
- <Icon icon='git-branch' size={16} />
- </a>
- <Navbar.Divider />
- <a
- href='mailto:hello@merico.dev'
- rel='noreferrer'
- target='_blank'
- className='navIconLink'
- >
- <Icon icon='envelope' size={16} />
- </a>
- <Navbar.Divider />
- {/* DISCORD: !DISABLED! */}
- {/* <a href='https://discord.com/invite/83rDG6ydVZ' rel='noreferrer' target='_blank' className='navIconLink'>
+ <Navbar.Group className={menuClass}>
+ <Icon
+ icon={sidebarVisible ? 'menu-closed' : 'menu-open'}
+ onClick={(e) => toggleSidebarOpen(!sidebarVisible)}
+ size={16}
+ />
+ </Navbar.Group>
+ {!sidebarVisible && (
+ <Navbar.Group className='navbarItems'>
+ <a
+ href='https://github.com/apache/incubator-devlake'
+ rel='noreferrer'
+ target='_blank'
+ className='navIconLink'
+ >
+ <Icon icon='git-branch' size={16} />
+ </a>
+ <Navbar.Divider />
+ <a
+ href='mailto:hello@merico.dev'
+ rel='noreferrer'
+ target='_blank'
+ className='navIconLink'
+ >
+ <Icon icon='envelope' size={16} />
+ </a>
+ <Navbar.Divider />
+ {/* DISCORD: !DISABLED! */}
+ {/* <a href='https://discord.com/invite/83rDG6ydVZ' rel='noreferrer' target='_blank' className='navIconLink'>
<DiscordIcon className='discordIcon' width={16} height={16} />
</a> */}
- {/* SLACK: ENABLED (Primary) */}
- <Popover position={Position.LEFT}>
- <SlackIcon
- className='slackIcon'
- width={16}
- height={16}
- style={{ cursor: 'pointer' }}
- />
- <>
- <div
- style={{ maxWidth: '200px', padding: '10px', fontSize: '11px' }}
- >
- <SlackLogo
- width={131}
- height={49}
- style={{ display: 'block', margin: '0 auto' }}
- />
- <p style={{ textAlign: 'center' }}>
- Want to interact with the <strong>Merico Community</strong>?
- Join us on our Slack Channel.
- <br />
- <a
- href='https://join.slack.com/t/devlake-io/shared_invite/zt-17b6vuvps-x98pqseoUagM7EAmKC82xQ'
- rel='noreferrer'
- target='_blank'
- className='bp3-button bp3-intent-warning bp3-elevation-1 bp3-small'
- style={{ marginTop: '10px' }}
- >
- Message us on <strong>Slack</strong>
- </a>
- </p>
- </div>
- </>
- </Popover>
- </Navbar.Group>
+ {/* SLACK: ENABLED (Primary) */}
+ <Popover position={Position.LEFT}>
+ <SlackIcon
+ className='slackIcon'
+ width={16}
+ height={16}
+ style={{ cursor: 'pointer' }}
+ />
+ <>
+ <div
+ style={{ maxWidth: '200px', padding: '10px', fontSize: '11px' }}
+ >
+ <SlackLogo
+ width={131}
+ height={49}
+ style={{ display: 'block', margin: '0 auto' }}
+ />
+ <p style={{ textAlign: 'center' }}>
+ Want to interact with the <strong>Merico Community</strong>?
+ Join us on our Slack Channel.
+ <br />
+ <a
+ href='https://join.slack.com/t/devlake-io/shared_invite/zt-17b6vuvps-x98pqseoUagM7EAmKC82xQ'
+ rel='noreferrer'
+ target='_blank'
+ className='bp3-button bp3-intent-warning bp3-elevation-1 bp3-small'
+ style={{ marginTop: '10px' }}
+ >
+ Message us on 
+ <strong>Slack</strong>
+ </a>
+ </p>
+ </div>
+ </>
+ </Popover>
+ </Navbar.Group>
+ )}
</Navbar>
)
}
diff --git a/config-ui/src/components/Sidebar.jsx b/config-ui/src/components/Sidebar.jsx
index 496a4b4..3c3356d 100644
--- a/config-ui/src/components/Sidebar.jsx
+++ b/config-ui/src/components/Sidebar.jsx
@@ -15,7 +15,7 @@
* limitations under the License.
*
*/
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState, useContext } from 'react'
import {
// BrowserRouter as Router,
useRouteMatch
@@ -29,9 +29,11 @@
import { ReactComponent as LogoText } from '@/images/devlake-textmark.svg'
import '@/styles/sidebar.scss'
+import UIContext from '../store/UIContext'
const Sidebar = () => {
const activeRoute = useRouteMatch()
+ const uiContext = useContext(UIContext)
const [menu, setMenu] = useState(MenuConfiguration(activeRoute))
const [versionTag, setVersionTag] = useState('')
@@ -56,7 +58,7 @@
fetchVersion()
}, [])
- return (
+ return uiContext.sidebarVisible ? (
<Card
interactive={false}
elevation={Elevation.ZERO}
@@ -90,6 +92,8 @@
<br />
</span>
</Card>
+ ) : (
+ <></>
)
}
diff --git a/config-ui/src/hooks/useWIndowSize.jsx b/config-ui/src/hooks/useWIndowSize.jsx
new file mode 100644
index 0000000..b26c050
--- /dev/null
+++ b/config-ui/src/hooks/useWIndowSize.jsx
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { useEffect, useState } from 'react'
+
+function useWindowSize() {
+ const [windowSize, setWindowSize] = useState({
+ width: 320,
+ height: 240
+ })
+
+ useEffect(() => {
+ function handleResize() {
+ setWindowSize({
+ width: window.innerWidth,
+ height: window.innerHeight
+ })
+ }
+ if (typeof window !== 'undefined') {
+ window.addEventListener('resize', handleResize)
+ handleResize()
+ }
+ return () => window.removeEventListener('resize', handleResize)
+ }, [])
+
+ return windowSize
+}
+
+export default useWindowSize
diff --git a/config-ui/src/index.js b/config-ui/src/index.js
index bc85b9d..0a1693a 100644
--- a/config-ui/src/index.js
+++ b/config-ui/src/index.js
@@ -18,7 +18,12 @@
import React from 'react'
import ReactDOM from 'react-dom'
-
import App from './App'
+import { UIContextProvider } from '@/store/UIContext'
-ReactDOM.render(<App />, document.getElementById('app'))
+ReactDOM.render(
+ <UIContextProvider>
+ <App />
+ </UIContextProvider>,
+ document.getElementById('app')
+)
diff --git a/config-ui/src/store/UIContext.jsx b/config-ui/src/store/UIContext.jsx
new file mode 100644
index 0000000..6e1e22f
--- /dev/null
+++ b/config-ui/src/store/UIContext.jsx
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React, { useState } from 'react'
+
+const UIContext = React.createContext({
+ sidebarVisible: false,
+ desktopBreakpointWidth: 850,
+ changeSidebarVisibility: () => {}
+})
+
+export const UIContextProvider = (props) => {
+ const [sidebarVisible, setSidebarVisible] = useState(false)
+
+ const contextValue = {
+ sidebarVisible: sidebarVisible,
+ changeSidebarVisibility: setSidebarVisible,
+ desktopBreakpointWidth: 850
+ }
+
+ return (
+ <UIContext.Provider value={contextValue}>
+ {props.children}
+ </UIContext.Provider>
+ )
+}
+
+export default UIContext
\ No newline at end of file
diff --git a/config-ui/src/styles/common.scss b/config-ui/src/styles/common.scss
index 2e675f4..5d0077d 100644
--- a/config-ui/src/styles/common.scss
+++ b/config-ui/src/styles/common.scss
@@ -19,6 +19,11 @@
padding-top: 50px;
min-height: 100vh;
overflow: hidden;
+
+ @media only screen and (max-width: 850px) {
+ justify-content: flex-start;
+ padding: 50px 30px;
+ }
}
.main {
@@ -31,6 +36,12 @@
flex-direction: column;
justify-content: flex-start;
align-items: center;
+
+ @media only screen and (max-width: 850px) {
+ margin-left: 0;
+ max-width: 100vw;
+ padding: 0rem;
+ }
}
.description {
diff --git a/config-ui/src/styles/nav.scss b/config-ui/src/styles/nav.scss
index 501dbbb..e4f625a 100644
--- a/config-ui/src/styles/nav.scss
+++ b/config-ui/src/styles/nav.scss
@@ -22,6 +22,39 @@
z-index: 1;
}
+.navbarMenuButton {
+ position: absolute;
+ display: none;
+ color: #7497F7;
+ &:hover {
+ color: #E8471C;
+ }
+
+ @media only screen and (max-width: 850px) {
+ display: flex;
+ left: 0px;
+ }
+}
+
+.navbarMenuButtonSidebarOpened {
+ position: absolute;
+ display: none;
+ color: #7497F7;
+ &:hover {
+ color: #E8471C;
+ }
+
+ @media only screen and (max-width: 850px) {
+ display: flex;
+ left: 240px;
+ }
+}
+
+.navbarItems {
+ position:absolute;
+ right:40px;
+}
+
.navIconLink:hover {
color: #E8471C;
}