diff --git a/config/eslintrc.yaml b/config/eslintrc.yaml index 1c338b90b7..c10c940903 100644 --- a/config/eslintrc.yaml +++ b/config/eslintrc.yaml @@ -141,6 +141,8 @@ rules: react/forbid-foreign-prop-types: off # Prevent undefined components. react/jsx-no-undef: warn + # Allow arrow functions as props + react/jsx-no-bind: off # Prevent vague prop types. react/forbid-prop-types: - warn diff --git a/pkg/webui/account/containers/clients-table/index.js b/pkg/webui/account/containers/clients-table/index.js index 36d2c5f33c..d09437f5b8 100644 --- a/pkg/webui/account/containers/clients-table/index.js +++ b/pkg/webui/account/containers/clients-table/index.js @@ -151,7 +151,7 @@ const ClientsTable = () => { }), render: details => ( - - ) : external ? ( - - {Boolean(iconElement) ? iconElement : null} - - ) : ( - - {iconElement} + + {Boolean(iconElement) ? iconElement : null} - + ) - const handleMouseEnter = useCallback(() => { - setExpandedSubmenu(true) - }, [setExpandedSubmenu]) - - const handleMouseLeave = useCallback(() => { - setExpandedSubmenu(false) - }, [setExpandedSubmenu]) - - const withSubmenu = ( - + + + + {submenuItems} + + ) return ( -
  • - {Boolean(submenuItems) ? withSubmenu : ItemElement} +
  • + {submenu || ItemElement}
  • ) } @@ -211,5 +202,6 @@ DropdownHeaderItem.propTypes = { Dropdown.Item = DropdownItem Dropdown.HeaderItem = DropdownHeaderItem +Dropdown.Attached = AttachedDropdown export default Dropdown diff --git a/pkg/webui/components/dropdown/story.js b/pkg/webui/components/dropdown/story.js index f72aa821de..67f236fd87 100644 --- a/pkg/webui/components/dropdown/story.js +++ b/pkg/webui/components/dropdown/story.js @@ -14,8 +14,6 @@ import React from 'react' -import style from './dropdown.styl' - import Dropdown from '.' export default { @@ -25,7 +23,7 @@ export default { export const Default = () => (
    - + diff --git a/pkg/webui/components/header/header.styl b/pkg/webui/components/header/header.styl index 4f555e5b5b..ea29eeb183 100644 --- a/pkg/webui/components/header/header.styl +++ b/pkg/webui/components/header/header.styl @@ -13,6 +13,7 @@ // limitations under the License. .container + --dropdown-offset: $cs.s padding: 0 $cs.m background: $c['tts-primary-050'] z-index: $zi.nav @@ -21,6 +22,10 @@ justify-content: space-between align-items: center height: 4rem + +media-query($bp.xs) + padding: 0 $cs.xs .logo height: $cs.l + +media-query($bp.xs) + width: 3rem diff --git a/pkg/webui/components/header/index.js b/pkg/webui/components/header/index.js index fbe07991a8..6fb396579b 100644 --- a/pkg/webui/components/header/index.js +++ b/pkg/webui/components/header/index.js @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useRef } from 'react' +import React from 'react' import classnames from 'classnames' import Button from '@ttn-lw/components/button' import ProfileDropdown from '@ttn-lw/components/profile-dropdown' -import Link from '@ttn-lw/components/link' import PropTypes from '@ttn-lw/lib/prop-types' @@ -25,8 +24,7 @@ import style from './header.styl' const Header = ({ brandLogo, - logo, - breadcrumbs, + Logo, className, addDropdownItems, starDropdownItems, @@ -34,43 +32,34 @@ const Header = ({ user, onMenuClick, ...rest -}) => { - const addRef = useRef(null) - const starRef = useRef(null) +}) => ( +
    +
    +
    +
    - // Const isGuest = !Boolean(user) - - return ( -
    -
    -
    -
    - -
    -
    -
    - ) -} +
    +
    +
    +) const imgPropType = PropTypes.shape({ src: PropTypes.string.isRequired, @@ -78,15 +67,12 @@ const imgPropType = PropTypes.shape({ }) Header.propTypes = { + Logo: PropTypes.elementType.isRequired, /** The dropdown items when the add button is clicked. */ addDropdownItems: PropTypes.node.isRequired, brandLogo: imgPropType, - /** A list of breadcrumb elements. */ - breadcrumbs: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.element])) - .isRequired, /** The classname applied to the component. */ className: PropTypes.string, - logo: imgPropType.isRequired, /** A handler for when the menu button is clicked. */ onMenuClick: PropTypes.func.isRequired, /** The dropdown items when the profile button is clicked. */ diff --git a/pkg/webui/components/input/input.styl b/pkg/webui/components/input/input.styl index dc1da341cb..7dd975485e 100644 --- a/pkg/webui/components/input/input.styl +++ b/pkg/webui/components/input/input.styl @@ -20,7 +20,7 @@ input-width-classes() box-sizing: border-box background: white - border-radius: $br.xs + border-radius: $br.m padding: 0 position: relative transition: border-color $ad.s diff --git a/pkg/webui/components/key-value-map/entry.js b/pkg/webui/components/key-value-map/entry.js index 91c1a778de..d7d1552ffe 100644 --- a/pkg/webui/components/key-value-map/entry.js +++ b/pkg/webui/components/key-value-map/entry.js @@ -143,7 +143,8 @@ const Entry = ({ title={m.deleteEntry} message={removeMessage} disabled={readOnly} - danger={!Boolean(removeMessage)} + danger + naked /> )}
    diff --git a/pkg/webui/components/key-value-map/index.js b/pkg/webui/components/key-value-map/index.js index 9ae7679f93..9de98697bb 100644 --- a/pkg/webui/components/key-value-map/index.js +++ b/pkg/webui/components/key-value-map/index.js @@ -107,6 +107,7 @@ const KeyValueMap = ({ onClick={addEmptyEntry} disabled={disabled} icon="add" + secondary /> diff --git a/pkg/webui/components/logo/index.js b/pkg/webui/components/logo/index.js index 570da164e3..5cec3dbd3c 100644 --- a/pkg/webui/components/logo/index.js +++ b/pkg/webui/components/logo/index.js @@ -15,48 +15,18 @@ import React from 'react' import classnames from 'classnames' -import Link from '@ttn-lw/components/link' - import PropTypes from '@ttn-lw/lib/prop-types' import style from './logo.styl' -const LogoLink = ({ safe, to, ...rest }) => { - if (safe) { - return - } - - return -} - -LogoLink.propTypes = { - safe: PropTypes.bool, - to: PropTypes.string.isRequired, -} - -LogoLink.defaultProps = { - safe: false, -} - -const Logo = ({ className, logo, brandLogo, vertical, safe }) => { - const classname = classnames(style.container, className, { - [style.vertical]: vertical, - [style.customBranding]: Boolean(brandLogo), - }) +const Logo = ({ className, logo, miniLogo }) => { + const classname = classnames(style.container, className) return (
    - {Boolean(brandLogo) && ( -
    - -
    - )}
    - - - + +
    ) @@ -71,6 +41,7 @@ Logo.propTypes = { brandLogo: imgPropType, className: PropTypes.string, logo: imgPropType.isRequired, + miniLogo: imgPropType.isRequired, safe: PropTypes.bool, vertical: PropTypes.bool, } diff --git a/pkg/webui/components/logo/logo.styl b/pkg/webui/components/logo/logo.styl index f24babdd7e..4d69f34d1d 100644 --- a/pkg/webui/components/logo/logo.styl +++ b/pkg/webui/components/logo/logo.styl @@ -26,104 +26,22 @@ display: flex align-items: center line-height: 0 - height: 2.3rem - padding: $cs.m 0 img width: 100% - +media-query($bp.s) - width: 9rem - height: 2.1rem - .logo display: flex align-items: center - .brand-logo - display: flex - align-items: center - justify-content: center - flex-direction: column - max-width: 9rem - - +media-query-min($bp.s) - height: 3rem - - +media-query($bp.s) - height: 2rem - - &-container - display: flex - align-items: center - height: 100% - width: 100% - - &:not(.vertical) - &:not(.custom-branding) - .logo-container - width: 13rem - +media-query($bp.s) - width: 12rem - - &.custom-branding .logo - display: flex - align-items: center - padding-left: 'calc(%s - .15rem)' % $ls.xs - +media-query($bp.s) - padding-left: $cs.s - - .logo-container - nudge('down', .15rem) - width: 11rem - +media-query($bp.s) - width: 8rem - - .brand-logo - border-dark('right') - align-items: center - padding-right: $ls.xs - +media-query($bp.s) - padding-right: $cs.s - - &-container - +media-query($bp.s) - max-width: 6rem - height: 2.5rem - - img - width: 100% - - img - object-position: left - - &.vertical - width: 15rem - flex-direction: column - margin-bottom: $ls.s - - &.custom-branding .logo - border-dark('top') - padding-top: $ls.xs - margin-top: $ls.xs - height: auto - width: 100% - - &-container - width: 100% - height: auto - - .brand-logo - margin-left: auto - margin-right: auto - width: 100% - - &-container + +media-query-min($bp.xs) + img:first-child display: block - max-height: 3rem - max-width: 10rem - width: 100% + img:last-child + display: none - img - object-position: center - width: 100% + +media-query($bp.xs) + img:first-child + display: none + img:last-child + display: block diff --git a/pkg/webui/components/navigation/side/item/item.styl b/pkg/webui/components/navigation/side/item/item.styl deleted file mode 100644 index ae017616e8..0000000000 --- a/pkg/webui/components/navigation/side/item/item.styl +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2019 The Things Network Foundation, The Things Industries B.V. -// -// Licensed 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. - -.item - &:hover - .fly-out-list - display: block - -.button - reset-button() - width: 100% - transition: 0.2s background-color ease-in, 0.2s color ease-in - text-decoration: none - display: flex - align-items: center - position: relative - gap: $cs.xs !important - - span - &:not(:first-child) - color: $c.grey-700 !important - - &:hover&:not(&-active) - background: none !important - color: $tc-deep-gray !important - text-decoration: none - - span - &:not(:first-child) - color: $c.grey-900 !important - - .icon - color: inherit - - &-active - background: $c.tts-primary-150 - color: $c.grey-900 !important - - &:hover - background: $c.tts-primary-150 !important - color: $c.grey-900 !important - - span - &:not(:first-child) - color: $c.grey-900 !important - - .icon - color: inherit - - .icon - color: inherit - -.message - vertical-align: middle - -.expand-icon - position: absolute - right: $cs.xs - top: 50% - transform: translateY(-50%) - transition: transform $ad.m ease-in - - &-open - transform: translateY(-50%) rotate(180deg) - -.fly-out-list-container - top: 0 - left: 0 - position: absolute - width: 180% - height: 13rem - -.fly-out-list - display: none - left: 5rem !important - top: 0 !important diff --git a/pkg/webui/components/profile-dropdown/index.js b/pkg/webui/components/profile-dropdown/index.js index 6b9046b837..b949d3edec 100644 --- a/pkg/webui/components/profile-dropdown/index.js +++ b/pkg/webui/components/profile-dropdown/index.js @@ -12,59 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useCallback, useRef, useState } from 'react' +import React from 'react' import classnames from 'classnames' -import Icon from '@ttn-lw/components/icon' -import Dropdown from '@ttn-lw/components/dropdown' import ProfilePicture from '@ttn-lw/components/profile-picture' import Button from '@ttn-lw/components/button' -import style from '@ttn-lw/components/button/button.styl' import PropTypes from '@ttn-lw/lib/prop-types' import styles from './profile-dropdown.styl' const ProfileDropdown = props => { - const [expanded, setExpanded] = useState(false) - const node = useRef(null) const { brandLogo, className, children, profilePicture, ...rest } = props - const handleClickOutside = useCallback(e => { - if (node.current && !node.current.contains(e.target)) { - setExpanded(false) - } - }, []) - - const toggleDropdown = useCallback(() => { - setExpanded(oldExpanded => { - const newState = !oldExpanded - if (newState) document.addEventListener('mousedown', handleClickOutside) - else document.removeEventListener('mousedown', handleClickOutside) - return newState - }) - }, [handleClickOutside]) - return ( - +
    + +
    ) } diff --git a/pkg/webui/components/profile-dropdown/profile-dropdown.styl b/pkg/webui/components/profile-dropdown/profile-dropdown.styl index e22504b7f5..eff28cb81d 100644 --- a/pkg/webui/components/profile-dropdown/profile-dropdown.styl +++ b/pkg/webui/components/profile-dropdown/profile-dropdown.styl @@ -13,10 +13,15 @@ // limitations under the License. .container - padding: 0 0.25rem 0 0.5rem + --dropdown-offset: $cs.s position: relative + +.profile-dropdown gap: 0 + button + padding: 0 $cs.s !important + .brand-logo height: 1.14rem @@ -24,5 +29,5 @@ display: none .profile-picture - height: 1.5rem !important - width: 1.5rem !important + height: 1.5rem + width: 1.5rem diff --git a/pkg/webui/components/sidebar/dedicated-entity/dedicated-entity.styl b/pkg/webui/components/sidebar/dedicated-entity/dedicated-entity.styl index 77937e4698..00c448ea7e 100644 --- a/pkg/webui/components/sidebar/dedicated-entity/dedicated-entity.styl +++ b/pkg/webui/components/sidebar/dedicated-entity/dedicated-entity.styl @@ -16,9 +16,35 @@ text-decoration: none line-height: 1.6rem color: $c.grey-900 + position: relative + height: 2.5rem + + &-curtain + position: absolute + overflow: hidden + border-radius: $br.l + width: 2.5rem + transition: 0.3s 0s all cubic-bezier(0.770, 0.000, 0.175, 1.000) + top: 0 + left: 0 + z-index: $zi.slight + + &:hover + transition: 0.3s 0.2s all cubic-bezier(0.770, 0.000, 0.175, 1.000) + width: 100% + + // Reveal the button label. + button span:last-child + opacity: 1 &-item display: flex + color: $c.grey-900 + opacity: 1 + position: absolute + left: 2.5rem + height: 100% + text-decoration: none &-label font-size: $fs.m @@ -27,30 +53,35 @@ &-divider display: list-item - height: 1.8rem + height: 100% margin: 0 $cs.xs 0 $cs.s border-left: 1px solid $c.grey-200 background-color: $c.grey-200 &-button border-radius: $br.l - max-width: 2.5rem padding: 0 $cs.xs 0 0.73rem + background-color: $c.grey-900 + position: relative + display: inline-flex + border-radius: $br.l + outline: 0 + cursor: pointer + justify-content: center + align-items: center + gap: $cs.xxs + height: 2.5rem + text-decoration: none + padding: 0 .75rem + white-space: nowrap + color: white + width: 100% + justify-content: flex-start - span - &:not(:first-child) - display: none - - &:hover - .dedicated-entity-button - justify-content: start - max-width: 100% - width: 100% - padding: 0 $cs.m 0 0.73rem - - span - &:not(:first-child) - display: flex + span:last-child + transition: 0.2s 0.2s all ease-in-out + opacity: 0 - .dedicated-entity-item - display: none + &:not(:disabled) + +focus-visible() + background-color: hoverize($c.grey-500) diff --git a/pkg/webui/components/sidebar/dedicated-entity/index.js b/pkg/webui/components/sidebar/dedicated-entity/index.js index 9255137997..662e279b20 100644 --- a/pkg/webui/components/sidebar/dedicated-entity/index.js +++ b/pkg/webui/components/sidebar/dedicated-entity/index.js @@ -13,7 +13,7 @@ // limitations under the License. import React from 'react' -import { NavLink } from 'react-router-dom' +import { Link } from 'react-router-dom' import classnames from 'classnames' import Button from '@ttn-lw/components/button' @@ -24,41 +24,32 @@ import PropTypes from '@ttn-lw/lib/prop-types' import style from './dedicated-entity.styl' -const DedicatedEntity = ({ label, icon, className, buttonMessage, path, exact, handleClick }) => ( - - ) } diff --git a/pkg/webui/components/sidebar/search-button/search-button.styl b/pkg/webui/components/sidebar/search-button/search-button.styl index 7fbec4bd3a..a85444aa24 100644 --- a/pkg/webui/components/sidebar/search-button/search-button.styl +++ b/pkg/webui/components/sidebar/search-button/search-button.styl @@ -14,36 +14,58 @@ .search-button font-size: $fs.m - color: $c.grey-500 !important + color: $c.grey-500 width: 100% position: relative display: flex justify-content: space-between - padding: $cs.xs $cs.xxs + align-items: center + padding: $cs.xxs $cs.xxs gap: $cs.xxs - box-shadow: 0px 2px 7px 0px rgba(0, 0, 0, 0.05) !important - background: white !important - border: 1px solid $c.grey-100 !important + box-shadow: 0px 2px 7px 1px rgba(0, 0, 0, 0.06) + background: white + border: 1px solid $c.grey-200 + border-radius: $br.l + transition: border-color 0.08s linear, box-shadow 0.08s linear, color 0.08s linear + &:hover - background-color: $c-focus !important + border: 1px solid $c.grey-200 + color: $c.grey-700 + box-shadow: 0px 2px 7px 0px rgba(0, 0, 0, 0.08) - &-minimized + &.is-minimized justify-content: center + padding: $cs.xs $cs.xxs + + & > div p + display: none + + .backslash + display: none p line-height: 1.4 .icon font-size: $fs.xl + position: relative + top: 1px - .backslash-container - height: 1.5rem - width: 1.5rem + .backslash + margin: 0 + height: 1.65rem // 24px + width: 1.65rem // 24px text-align: center border-radius: $br.m border: 1px solid $c.grey-200 - font-size: $fs.s + font-size: $fs.m display: flex justify-content: center align-items: center + padding-bottom: 1px + + .fly-out-list + --dropdown-offset: calc(.75rem - 2px) + top: - $cs.xs + color: $c.grey-900 diff --git a/pkg/webui/components/sidebar/section-label/index.js b/pkg/webui/components/sidebar/section-label/index.js index a1a9fcfae8..0fe80aa179 100644 --- a/pkg/webui/components/sidebar/section-label/index.js +++ b/pkg/webui/components/sidebar/section-label/index.js @@ -42,7 +42,7 @@ const SectionLabel = ({ data-test-id={dataTestId} > - + + {isMinimized && ( + + + {subItems.map(item => ( + + ))} + + )} + + {children} + + + ) +} + +CollapsibleItem.propTypes = { + children: PropTypes.node, + currentPathName: PropTypes.string.isRequired, + depth: PropTypes.number.isRequired, + icon: PropTypes.string, + isExpanded: PropTypes.bool.isRequired, + isMinimized: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, + onDropdownItemsClick: PropTypes.func, + title: PropTypes.message.isRequired, +} + +CollapsibleItem.defaultProps = { + children: undefined, + icon: undefined, + onDropdownItemsClick: () => null, +} + +export default CollapsibleItem diff --git a/pkg/webui/components/navigation/side/item/index.js b/pkg/webui/components/sidebar/side-menu/item/index.js similarity index 57% rename from pkg/webui/components/navigation/side/item/index.js rename to pkg/webui/components/sidebar/side-menu/item/index.js index b5ad39610a..b9b7768230 100644 --- a/pkg/webui/components/navigation/side/item/index.js +++ b/pkg/webui/components/sidebar/side-menu/item/index.js @@ -15,16 +15,10 @@ import React, { useCallback, useEffect, useState } from 'react' import classnames from 'classnames' -import Dropdown from '@ttn-lw/components/dropdown' -import MenuLink from '@ttn-lw/components/sidebar/side-menu-link' -import Button from '@ttn-lw/components/button' -import Icon from '@ttn-lw/components/icon' - -import Message from '@ttn-lw/lib/components/message' - import PropTypes from '@ttn-lw/lib/prop-types' -import SideNavigationList from '../list' +import MenuLink from './link' +import CollapsibleItem from './collapsible' import style from './item.styl' @@ -63,7 +57,7 @@ const SideNavigationItem = props => { return (
  • {Boolean(children) ? ( - { - const subItems = children - .filter(item => Boolean(item) && 'props' in item) - .map(item => ({ - title: item.props.title, - path: item.props.path, - icon: item.props.icon, - })) - - const subItemActive = subItems.some(item => currentPathName.includes(item.path)) - - return ( - <> - - {!isMinimized && ( - - {children} - - )} - - ) -} - -CollapsableItem.propTypes = { - children: PropTypes.node, - currentPathName: PropTypes.string.isRequired, - depth: PropTypes.number.isRequired, - icon: PropTypes.string, - isExpanded: PropTypes.bool.isRequired, - isMinimized: PropTypes.bool.isRequired, - onClick: PropTypes.func.isRequired, - onDropdownItemsClick: PropTypes.func, - title: PropTypes.message.isRequired, -} - -CollapsableItem.defaultProps = { - children: undefined, - icon: undefined, - onDropdownItemsClick: () => null, -} - const LinkItem = ({ onClick, title, icon, exact, path }) => { const handleLinkItemClick = useCallback( event => { @@ -216,9 +121,7 @@ const LinkItem = ({ onClick, title, icon, exact, path }) => { ) return ( - <> - - + ) } diff --git a/pkg/webui/components/sidebar/side-menu-link/side-menu-link.styl b/pkg/webui/components/sidebar/side-menu/item/item.styl similarity index 55% rename from pkg/webui/components/sidebar/side-menu-link/side-menu-link.styl rename to pkg/webui/components/sidebar/side-menu/item/item.styl index fdab8ef356..01ca8f5eb6 100644 --- a/pkg/webui/components/sidebar/side-menu-link/side-menu-link.styl +++ b/pkg/webui/components/sidebar/side-menu/item/item.styl @@ -1,4 +1,4 @@ -// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2024 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,9 +12,41 @@ // See the License for the specific language governing permissions and // limitations under the License. +.container + position: relative + + &.is-minimized, &.is-minimized .link + justify-content: center + + .title + display: none + + .expand-icon + display: none + + .icon + transition: color 50ms linear + color: $c.grey-500 + + .sub-item-list + display: none + +.message + vertical-align: middle + +.expand-icon + position: absolute + right: $cs.xs + top: 50% + transform: translateY(-50%) + transition: transform $ad.m ease-in-out + + &-open + transform: translateY(-50%) rotate(180deg) + .link - transition: 0.2s all ease-in-out - border-radius: $br.l + transition: color 50ms linear + border-radius: $br.m text-decoration: none color: $c.grey-700 display: flex @@ -22,9 +54,9 @@ gap: $cs.xs padding: $cs.xs position: relative - - .icon - color: $c.grey-500 + box-sizing: border-box + width: 100% + padding-left: $cs.xs &.active background: $c.tts-primary-150 @@ -41,7 +73,11 @@ &:hover color: $c.grey-900 - text-decoration: none .icon color: inherit + +.fly-out-list + --dropdown-offset: $cs.xs + top: - $cs.xs + z-index: $zi.dropdown diff --git a/pkg/webui/components/sidebar/side-menu-link/index.js b/pkg/webui/components/sidebar/side-menu/item/link.js similarity index 75% rename from pkg/webui/components/sidebar/side-menu-link/index.js rename to pkg/webui/components/sidebar/side-menu/item/link.js index 9171362354..4723dc8348 100644 --- a/pkg/webui/components/sidebar/side-menu-link/index.js +++ b/pkg/webui/components/sidebar/side-menu/item/link.js @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useCallback, useContext } from 'react' +import React, { useCallback, useContext, useRef } from 'react' import { NavLink } from 'react-router-dom' import classnames from 'classnames' import Icon from '@ttn-lw/components/icon' +import Dropdown from '@ttn-lw/components/dropdown' import Message from '@ttn-lw/lib/components/message' @@ -24,25 +25,31 @@ import SidebarContext from '@console/containers/side-bar/context' import PropTypes from '@ttn-lw/lib/prop-types' -import style from './side-menu-link.styl' +import style from './item.styl' const MenuLink = ({ icon, title, path, onClick, exact, disabled }) => { + const ref = useRef() const { isMinimized } = useContext(SidebarContext) const className = useCallback( ({ isActive }) => - classnames(style.link, { + classnames(style.link, style.container, { [style.active]: isActive, [style.disabled]: disabled, - 'j-center': isMinimized, + [style.isMinimized]: isMinimized, }), [disabled, isMinimized], ) return ( - + {icon && }{' '} - {!isMinimized && } + + {isMinimized && ( + + + + )} ) } diff --git a/pkg/webui/components/sidebar/side-menu-link/story.js b/pkg/webui/components/sidebar/side-menu/item/story.js similarity index 98% rename from pkg/webui/components/sidebar/side-menu-link/story.js rename to pkg/webui/components/sidebar/side-menu/item/story.js index ddc8c6077e..32c0b185b3 100644 --- a/pkg/webui/components/sidebar/side-menu-link/story.js +++ b/pkg/webui/components/sidebar/side-menu/item/story.js @@ -16,7 +16,7 @@ import React from 'react' import SidebarContext from '@console/containers/side-bar/context' -import Link from '.' +import Link from './link' export default { title: 'Side Menu Link', diff --git a/pkg/webui/components/navigation/side/list/index.js b/pkg/webui/components/sidebar/side-menu/list/index.js similarity index 100% rename from pkg/webui/components/navigation/side/list/index.js rename to pkg/webui/components/sidebar/side-menu/list/index.js diff --git a/pkg/webui/components/navigation/side/list/list.styl b/pkg/webui/components/sidebar/side-menu/list/list.styl similarity index 78% rename from pkg/webui/components/navigation/side/list/list.styl rename to pkg/webui/components/sidebar/side-menu/list/list.styl index baddbf7c23..c4af86598d 100644 --- a/pkg/webui/components/navigation/side/list/list.styl +++ b/pkg/webui/components/sidebar/side-menu/list/list.styl @@ -22,23 +22,21 @@ & > li:not(:last-child) margin-bottom: 0 - // We need to rely on the child list items for the reveal animation, the - // height of the list is variable, making proper slide up transitions - // impossible. - & > li - transition: height $ad.m ease-in-out - &-nested > li height: 0px transition: height $ad.m ease-in-out - &-nested:not(.list-expanded) > li - visibility: hidden - &-expanded > li height: 2.4rem + &-nested + transition: opacity $ad.m ease-in-out + + &:not(.list-expanded) + opacity: 0 + &-expanded, &-nested + opacity: 1 & li a padding-left: $cs.l + $cs.xs diff --git a/pkg/webui/components/navigation/side/side.styl b/pkg/webui/components/sidebar/side-menu/side.styl similarity index 100% rename from pkg/webui/components/navigation/side/side.styl rename to pkg/webui/components/sidebar/side-menu/side.styl diff --git a/pkg/webui/components/navigation/side/story.js b/pkg/webui/components/sidebar/side-menu/story.js similarity index 100% rename from pkg/webui/components/navigation/side/story.js rename to pkg/webui/components/sidebar/side-menu/story.js diff --git a/pkg/webui/components/sidebar/switcher/index.js b/pkg/webui/components/sidebar/switcher/index.js index c77d8bc723..95c6ff6bf7 100644 --- a/pkg/webui/components/sidebar/switcher/index.js +++ b/pkg/webui/components/sidebar/switcher/index.js @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useCallback } from 'react' +import React, { useCallback, useRef } from 'react' import { NavLink, useLocation } from 'react-router-dom' import classnames from 'classnames' import Icon from '@ttn-lw/components/icon' +import Dropdown from '@ttn-lw/components/dropdown' import Message from '@ttn-lw/lib/components/message' @@ -26,40 +27,70 @@ import PropTypes from '@ttn-lw/lib/prop-types' import style from './switcher.styl' const Switcher = ({ isMinimized }) => { + const overviewRef = useRef(null) + const applicationsRef = useRef(null) + const gatewaysRef = useRef(null) const { pathname } = useLocation() - // effectively ignores the end prop and only matches when you're at the root route. - // https://reactrouter.com/en/main/components/nav-link - const overviewClassName = classnames( - style.link, - { [style.active]: !pathname.includes('/applications') && !pathname.includes('/gateways') }, - 'p-vert-cs-s', - 'p-sides-0', - ) - const className = useCallback( + const getNavLinkClass = useCallback( ({ isActive }) => - classnames(style.link, { [style.active]: isActive }, 'p-vert-cs-s', 'p-sides-0'), + classnames(style.link, { + [style.active]: isActive, + }), [], ) + const getOverviewNavLinkClass = classnames(style.link, { + [style.active]: !pathname.startsWith('/applications') && !pathname.startsWith('/gateways'), + }) + return (
    - - {isMinimized ? : } + + + + {isMinimized && ( + + + + )} - - {isMinimized ? ( - - ) : ( - + + + + {isMinimized && ( + + + )} - - {isMinimized ? : } + + + + {isMinimized && ( + + + + )}
    ) diff --git a/pkg/webui/components/sidebar/switcher/switcher.styl b/pkg/webui/components/sidebar/switcher/switcher.styl index 9ccca11953..6631298bb0 100644 --- a/pkg/webui/components/sidebar/switcher/switcher.styl +++ b/pkg/webui/components/sidebar/switcher/switcher.styl @@ -14,24 +14,29 @@ .switcher-container border-radius: $br.l - border: 1px solid $c.grey-100 + border: 1px solid $c.grey-200 background: white - box-shadow: 0px 2px 7px 0px rgba(0, 0, 0, 0.05) - font-weight: $fw.light + box-shadow: 0px 2px 7px 1px rgba(0, 0, 0, 0.06) + display: flex + gap: $cs.xxs + justify-content: center + padding: $cs.xxs .link - transition: 0.2s all ease-in-out border-radius: $br.m text-decoration: none color: $c.grey-700 display: flex justify-content: center width: 100% - font-weight: $fw.bold + transition: background 80ms ease-in-out + position: relative + padding: $cs.s $cs.xxs &.active background: $c.grey-900 color: $c.white + font-weight: $fw.bold &:visited text-decoration: none @@ -42,3 +47,25 @@ text-decoration: none &:not(.active) background: $c.grey-100 + + &.is-minimized + flex-direction: column + + .link + padding: $cs.xs 0 + + .caption + display: none + + &:not(.is-minimized) + .icon + display: none + + .icon + font-size: $fs.xl + font-weight: 300 + + .fly-out-list + --dropdown-offset: calc(1rem - 2px) + top: - $cs.xs + color: $c.grey-900 diff --git a/pkg/webui/console/components/gateway-api-keys-modal/index.js b/pkg/webui/console/components/gateway-api-keys-modal/index.js index df1ebfc799..e941a43576 100644 --- a/pkg/webui/console/components/gateway-api-keys-modal/index.js +++ b/pkg/webui/console/components/gateway-api-keys-modal/index.js @@ -50,7 +50,13 @@ const GatewayApiKeysModal = ({ > {lnsKey && ( -
    + icon={action.icon} + message={action.message} + secondary + /> )} {hasSupportLink && !isNotFound && ( <> - - - - + className={style.actionButton} + icon="contact_support" + message={sharedMessages.getSupport} + secondary + /> {hasErrorDetails && ( )} @@ -276,19 +277,17 @@ const FullViewErrorInner = ({ error, safe, action, unexpected }) => {
    {errorDetails}
    - + icon={copied ? 'done' : 'file_copy'} + message={ + copied ? sharedMessages.copiedToClipboard : sharedMessages.copyToClipboard + } + secondary + /> )} diff --git a/pkg/webui/locales/en.json b/pkg/webui/locales/en.json index 405156007f..07963e3ffa 100644 --- a/pkg/webui/locales/en.json +++ b/pkg/webui/locales/en.json @@ -145,8 +145,6 @@ "components.key-value-map.index.addEntry": "Add entry", "components.link.index.glossaryTitle": "See \"{term}\" in the glossary", "components.link.index.defaultGlossaryTitle": "See in the glossary", - "components.mobile-menu.index.loggedInAs": "Logged in as {userId}", - "components.navigation.side.index.hideSidebar": "Hide sidebar", "components.notification.details.index.showDetails": "Show details", "components.notification.details.index.details": "Details", "components.notification.details.index.errorDetails": "Error details", diff --git a/pkg/webui/locales/ja.json b/pkg/webui/locales/ja.json index 4f21652658..1ca2b73a32 100644 --- a/pkg/webui/locales/ja.json +++ b/pkg/webui/locales/ja.json @@ -145,8 +145,6 @@ "components.key-value-map.index.addEntry": "エントリー追加", "components.link.index.glossaryTitle": "用語集「{term}」を参照してください", "components.link.index.defaultGlossaryTitle": "用語集を見る", - "components.mobile-menu.index.loggedInAs": "{userId}でログインしました", - "components.navigation.side.index.hideSidebar": "サイドバーを隠す", "components.notification.details.index.showDetails": "詳細を表示", "components.notification.details.index.details": "詳細", "components.notification.details.index.errorDetails": "エラー詳細", diff --git a/pkg/webui/styles/mixins.styl b/pkg/webui/styles/mixins.styl index 1116d440f2..bb136da961 100644 --- a/pkg/webui/styles/mixins.styl +++ b/pkg/webui/styles/mixins.styl @@ -361,7 +361,7 @@ transition-color() sidebar-transition($properties) transition-timing-function: cubic-bezier(.18,.71,.3,.99) - transition-duration: 330ms + transition-duration: 0ms transition-property: $properties // Global classes attached by the sidebar component. diff --git a/pkg/webui/styles/utilities/general.styl b/pkg/webui/styles/utilities/general.styl index a29660c299..3a3f1c6f2d 100644 --- a/pkg/webui/styles/utilities/general.styl +++ b/pkg/webui/styles/utilities/general.styl @@ -120,6 +120,9 @@ .h-auto height: auto + .h-vh + height: 100vh + for $num in (1..9) $percentage = percentage($num / 10) .w-{$num}0