Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"MLRun",
"mlrun"
],
"dependencies": {
"@reduxjs/toolkit": "^2.9.0"
},
"peerDependencies": {
"@reduxjs/toolkit": "*",
"classnames": "*",
Expand All @@ -59,7 +62,6 @@
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@reduxjs/toolkit": "^2.8.2",
"@storybook/addon-actions": "^8.0.0",
"@storybook/addon-essentials": "^8.0.0",
"@storybook/addon-interactions": "^8.0.0",
Expand Down
98 changes: 98 additions & 0 deletions src/lib/components/Navbar/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
Copyright 2019 Iguazio Systems Ltd.

Licensed under the Apache License, Version 2.0 (the "License") with
an addition restriction as set forth herein. 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.

In addition, you may not use the software for any purposes that are
illegal under applicable law, and the grant of the foregoing license
under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
import React, { useState, useRef, useEffect } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

import Tooltip from '../Tooltip/Tooltip'
import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate'

import { NAVBAR_WIDTH_OPENED } from '../../constants'
import { localStorageService } from '../../utils'

import NavbarClosedIcon from '../../images/navbar/navbar-closed-icon.svg?react'
import NavbarOpenedIcon from '../../images/navbar/navbar-opened-icon.svg?react'

import './Navbar.scss'

const Navbar = ({ children, id = 'navbar', setNavbarIsPinned }) => {
const [isPinned, setIsPinned] = useState(
localStorageService.getStorageValue('isNavbarStatic', false) === 'true'
)
const navbarRef = useRef(null)

const navbarClasses = classNames('navbar', isPinned && 'navbar_pinned')
const navbarStyles = {
maxWidth: isPinned && NAVBAR_WIDTH_OPENED
}

const handlePinClick = () => {
setIsPinned(prevIsPinned => {
localStorageService.setStorageValue('isNavbarStatic', !prevIsPinned)
return !prevIsPinned
})
}

useEffect(() => {
setNavbarIsPinned(isPinned)
}, [isPinned, setNavbarIsPinned])

return (
<nav className={navbarClasses} data-testid={id} style={navbarStyles} ref={navbarRef}>
<div className="navbar__pin-icon">
<div id="navbar-pin" onClick={handlePinClick}>
<Tooltip template={<TextTooltipTemplate text={`${isPinned ? 'Unpin' : 'Pin'} Menu`} />}>
{isPinned ? <NavbarOpenedIcon /> : <NavbarClosedIcon />}
</Tooltip>
</div>
</div>
{React.cloneElement(children, { navbarIsPinned: isPinned })}
</nav>
)
}

Navbar.Body = ({ children }) => (
<div className="navbar__body" data-testid="navbar-body">
{children}
</div>
)

Navbar.Body.displayName = 'Navbar.Body'

Navbar.Divider = () => <div className="navbar__divider" />

Navbar.Divider.displayName = 'Navbar.Divider'

Navbar.Body.propTypes = {
children: PropTypes.node.isRequired
}

Navbar.propTypes = {
children: PropTypes.oneOfType([
PropTypes.element,
PropTypes.object,
PropTypes.node,
PropTypes.string
]).isRequired,
id: PropTypes.string,
setNavbarIsPinned: PropTypes.func
}

export default Navbar
135 changes: 135 additions & 0 deletions src/lib/components/Navbar/Navbar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
@use '../../scss/variables';
@use '../../scss/colors';
@use '../../scss/borders';

.navbar {
display: flex;
flex-flow: column nowrap;
flex-grow: 1;
flex-shrink: 0;
flex-basis: 245px;
position: absolute;
top: 0;
left: 0;
z-index: 11;
height: 100%;
width: 245px;
max-width: 57px;
border-right: borders.$tertiaryBorder;
background-color: colors.$cultured;
transition: max-width 0.3s ease-in-out;

&:hover {
max-width: 245px;

.navbar__pin-icon {
opacity: 1;
visibility: visible;
}
}

&:hover,
&.navbar_pinned {
.navbar__body {
overflow-y: auto;
}

.nav-link__button {
padding: 0 8px 0 24px;
}

.nav-link__button.expanded {
& + .navbar-links_nested {
max-height: 30rem;
transition: max-height 0.3s ease-in-out;
}
}
}

.nav-link {
&__icon {
display: flex;
opacity: 1;
flex: 0 0 auto;
transition: opacity 0.3s ease-in-out;

svg {
flex: 0 0 20px;
width: 20px;
height: 20px;

& * {
fill: colors.$topaz;
}
}
}
}

&__divider {
border-top: 1px solid colors.$mischka;
margin: 0 1rem;
}

&__body {
position: relative;
z-index: 2;
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
width: 100%;
height: 100%;
overflow: hidden;
transition: padding 0.3s ease-in-out;
}

&-links {
display: flex;
flex: 0 0 auto;
flex-flow: column nowrap;
max-width: 238px;
margin: 0.5rem 0;
padding: 0 10px 0 0;
width: 100%;
list-style-type: none;

&.navbar-links_nested {
max-height: 0;
padding: 0;
margin: 0;
overflow: hidden;
transition: max-height 0.3s ease-in-out;

.nav-link__button {
padding-left: 44px;
}

& > *:first-child {
margin-top: 5px;
}
}
}

&__pin {
&-icon {
position: absolute;
display: flex;
justify-content: flex-end;
padding: 5px 10px;
opacity: 0;
top: 10px;
right: -45px;
background-color: colors.$cultured;
border-radius: 0 8px 8px 0;
border-width: 1px 1px 1px 0;
border-color: colors.$mischka;
border-style: solid;
visibility: hidden;
transition: all 0.3s ease-in-out;

&:hover {
cursor: pointer;
background-color: colors.$crystalBell;
}
}
}
}
2 changes: 2 additions & 0 deletions src/lib/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import LoadButton from './LoadButton/LoadButton'
import Loader from './Loader/Loader'
import LoaderForSuspenseFallback from './Loader/LoaderForSuspenseFallback'
import Modal from './Modal/Modal'
import Navbar from './Navbar/Navbar'
import PopUpDialog from './PopUpDialog/PopUpDialog'
import RoundedIcon from './RoundedIcon/RoundedIcon'
import TableCell from './TableCell/TableCell'
Expand Down Expand Up @@ -69,6 +70,7 @@ export {
Loader,
LoaderForSuspenseFallback,
Modal,
Navbar,
PopUpDialog,
RoundedIcon,
TableCell,
Expand Down
5 changes: 5 additions & 0 deletions src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,8 @@ export const MODAL_MAX = 'max'

export const MAIN_TABLE_ID = 'main-table'
export const MAIN_TABLE_BODY_ID = 'main-table-body'

/*=========== NAVBAR =============*/

export const NAVBAR_WIDTH_CLOSED = 57
export const NAVBAR_WIDTH_OPENED = 245
98 changes: 98 additions & 0 deletions src/lib/elements/NavbarLink/NavbarLink.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
Copyright 2019 Iguazio Systems Ltd.

Licensed under the Apache License, Version 2.0 (the "License") with
an addition restriction as set forth herein. 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.

In addition, you may not use the software for any purposes that are
illegal under applicable law, and the grant of the foregoing license
under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
import React from 'react'
import { NavLink, useLocation } from 'react-router-dom'
import classNames from 'classnames'
import PropTypes from 'prop-types'

import ArrowIcon from '../../images/arrow.svg?react'

import './NavbarLink.scss'

const NavbarLink = ({
externalLink = false,
icon = null,
index = null,
label,
link = '',
selectedIndex = null,
setSelectedIndex,
...props
}) => {
const { pathname } = useLocation()
const [, , page] = pathname.split('/').slice(1, 4)

const parentLinkClasses = classNames(
'nav-link__button btn nav-link__parent',
props.screens && props.screens.includes(page) && 'active',
index === selectedIndex && 'expanded'
)

const handleExpanded = () => {
if (setSelectedIndex) {
if (index !== selectedIndex) {
setSelectedIndex(index)
} else {
setSelectedIndex(null)
}
}
}

return externalLink ? (
<a href={link} target="_top" className="nav-link__button btn">
<span className="nav-link__icon">{icon}</span>
<span className="nav-link__label">{label}</span>
</a>
) : props.nestedLinks ? (
<div onClick={handleExpanded} className={parentLinkClasses}>
<span className="nav-link__icon">{icon}</span>
<span className="nav-link__label">{label}</span>

<span className="nav-link__arrow">
<ArrowIcon />
</span>
</div>
) : (
<NavLink
to={link}
onClick={handleExpanded}
className="nav-link__button btn"
activeclassname="active"
>
<span className="nav-link__icon">{icon}</span>
<span className="nav-link__label">{label}</span>
</NavLink>
)
}

NavbarLink.propTypes = {
externalLink: PropTypes.bool,
icon: PropTypes.object,
id: PropTypes.string,
index: PropTypes.number,
label: PropTypes.string.isRequired,
link: PropTypes.string,
nestedLinks: PropTypes.array,
screens: PropTypes.array,
selectedIndex: PropTypes.number,
setSelectedIndex: PropTypes.func
}

export default React.memo(NavbarLink)
Loading
Loading