diff --git a/pkg/webui/components/breadcrumbs/breadcrumbs.styl b/pkg/webui/components/breadcrumbs/breadcrumbs.styl index 0429d38e0f..8c23a52e43 100644 --- a/pkg/webui/components/breadcrumbs/breadcrumbs.styl +++ b/pkg/webui/components/breadcrumbs/breadcrumbs.styl @@ -28,6 +28,5 @@ display: none &-container - border-normal('bottom') box-sizing: border-box height: $breadcrumbs-bar-height diff --git a/pkg/webui/components/button-v2/button.styl b/pkg/webui/components/button-v2/button.styl deleted file mode 100644 index 3cb57a9b1d..0000000000 --- a/pkg/webui/components/button-v2/button.styl +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright © 2023 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. - -.button - reset-button() - position: relative - display: inline-flex - transition: 0.2s all ease-in-out - border-radius: $br.l - outline: 0 - cursor: pointer - align-items: center - gap: $cs.xxs - height: 2.5rem - text-decoration: none - padding: 0 $cs.m - white-space: nowrap - - .arrow-icon - transform: rotate(0deg) - transition: transform .2s - - .arrow-icon-expanded - transform: rotate(180deg) - transition: transform .2s - - &.primary - transition: 0.2s all ease-in-out - color: white - background-color: $c-active-blue - - &.disabled - background-color: $c-divider-dark - - &:not(.disabled) - &:hover - transition: 0.2s all ease-in-out - background-color: hoverize($c-active-blue) - - +focus-visible() - background-color: hoverize($c-active-blue) - - &:active - transition: 0.2s all ease-in-out - background-color: activize($c-active-blue) - - &.grey - background-color: $c.grey-500 - - &:not(.disabled) - &:hover - background-color: hoverize($c.grey-500) - - +focus-visible() - background-color: hoverize($c.grey-500) - - &:active - background-color: activize($c.grey-500) - - &.disabled - background-color: transparentify($c.grey-500, #fff, .25) - - &.secondary - transition: 0.2s all ease-in-out - color: $tc-deep-gray - background-color: white - box-shadow: 0px 2px 2px 0px rgba(0 0 0 4.5%), inset 0px 0px 0px 1px white - background: linear-gradient(to bottom, white, white) padding-box, linear-gradient(to bottom, #E8E7EC, #D7D7D7) border-box - border: 1px solid transparent - - - &:hover - transition: 0.2s all ease-in-out - background: linear-gradient(to bottom, $c-focus, $c-focus) padding-box, linear-gradient(to bottom, #E8E7EC, #D7D7D7) border-box - - +focus-visible() - background: linear-gradient(to bottom, $c-focus, $c-focus) padding-box, linear-gradient(to bottom, #E8E7EC, #D7D7D7) border-box - - &:active - transition: 0.2s all ease-in-out - background: linear-gradient(to bottom, $c-focus, $c-focus) padding-box, linear-gradient(to bottom, #E8E7EC, #D7D7D7) border-box - box-shadow: none - - &.naked - transition: 0.2s all ease-in-out - border: none - background-color: none - box-shadow: unset - color: $tc-subtle-gray - - &:not(.disabled) - &:hover - background-color: $c-backdrop - - +focus-visible() - background-color: $c-backdrop - - &:active - transition-duration: $ad.xs - background-color: $c-backdrop - - &.only-icon - width: 2.5rem - padding: 0 $cs.xs 0 0.73rem - - &.with-dropdown - padding: 0 $cs.xxs 0 $cs.xs - - &.with-dropdown&.only-icon - width: fit-content - .icon - margin-left: 0 - - &.with-icon - line-height: 1.3 - - &.responsive-label - padding: $cs.xs - - +media-query($bp.xxs) - .icon - margin-left: 0 - margin-right: 0 - - .link-button-message - display: none - -.content - position: relative - -.icon - margin-left: - $cs.xxs - diff --git a/pkg/webui/components/button-v2/index.js b/pkg/webui/components/button-v2/index.js deleted file mode 100644 index 3e68834e78..0000000000 --- a/pkg/webui/components/button-v2/index.js +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright © 2023 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. - -import React, { useCallback, forwardRef, useMemo, useState } from 'react' -import classnames from 'classnames' -import { useIntl } from 'react-intl' - -import Icon from '@ttn-lw/components/icon' -import Dropdown from '@ttn-lw/components/dropdown-v2' - -import Message from '@ttn-lw/lib/components/message' - -import PropTypes from '@ttn-lw/lib/prop-types' - -import style from './button.styl' - -const filterDataProps = props => - Object.keys(props) - .filter(key => key.startsWith('data-')) - .reduce((acc, key) => { - acc[key] = props[key] - return acc - }, {}) - -const assembleClassnames = ({ - message, - primary, - secondary, - naked, - grey, - icon, - dropdownItems, - className, -}) => - classnames( - style.button, - { - [style.primary]: primary, - [style.secondary]: secondary, - [style.naked]: naked, - [style.grey]: grey, - [style.withIcon]: icon !== undefined && message, - [style.onlyIcon]: icon !== undefined && !message, - [style.withDropdown]: Boolean(dropdownItems), - }, - className, - ) - -const buttonChildren = props => { - const { dropdownItems, icon, message, expanded, noDropdownIcon, dropdownClassName, children } = - props - - const content = Boolean(children) ? ( - children - ) : ( - <> - {icon ? : null} - {message ? : null} - {dropdownItems ? ( - <> - {!noDropdownIcon && ( - - )} - - {dropdownItems} - - - ) : null} - - ) - - return content -} - -const Button = forwardRef((props, ref) => { - const { - autoFocus, - dropdownItems, - name, - type, - value, - title: rawTitle, - onBlur, - onClick, - form, - ...rest - } = props - const [expanded, setExpanded] = useState(false) - - const dataProps = useMemo(() => filterDataProps(rest), [rest]) - - const handleClickOutside = useCallback( - e => { - if (ref.current && !ref.current.contains(e.target)) { - setExpanded(false) - } - }, - [ref], - ) - - const toggleDropdown = useCallback(() => { - setExpanded(oldExpanded => { - const newState = !oldExpanded - if (newState) document.addEventListener('mousedown', handleClickOutside) - else document.removeEventListener('mousedown', handleClickOutside) - return newState - }) - }, [handleClickOutside]) - - const handleClick = useCallback( - evt => { - if (dropdownItems) { - toggleDropdown() - return - } - // Passing a value to the onClick handler is useful for components that - // are rendered multiple times, e.g. in a list. The value can be used to - // identify the component that was clicked. - onClick(evt, value) - }, - [dropdownItems, onClick, toggleDropdown, value], - ) - - const intl = useIntl() - - let title = rawTitle - if (typeof rawTitle === 'object' && rawTitle.id && rawTitle.defaultMessage) { - title = intl.formatMessage(title) - } - - const htmlProps = { autoFocus, name, type, value, title, onBlur, form, ...dataProps } - const buttonClassNames = assembleClassnames(props) - return ( -   -   - - - ) - } - - @bind - toggle() { - this.setState(state => ({ - busy: !state.busy, - })) - } - - @bind - disable() { - this.setState(state => ({ - disabled: !state.disabled, - })) - } - - @bind - error() { - this.setState({ - error: true, - }) - setTimeout( - function () { - this.setState({ - error: false, - }) - }.bind(this), - 1200, - ) - } -} - export default { - title: 'Button', + title: 'Button V2', + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/7pBLWK4tsjoAbyJq2viMAQ/2023-console-redesign?type=design&node-id=590%3A51411&mode=design&t=Hbk2Qngeg1xqg4V3-1', + }, + }, } -export const Default = () => ( -
-
-) - -export const Warning = () => ( -
-
-) - -export const Danger = () => ( -
-
+const dropdownItems = ( + + + + ) export const Primary = () => ( -
+
) export const WithIcon = () => ( -
-
) -export const Naked = () => ( -
-
) -export const NakedWithIcon = () => ( -
-
+ ) +} + +export const PrimayOnlyIconDropdown = () => { + const ref = useRef() + + return ( +
+
+ ) +} + +export const Secondary = () => ( +
+
) -export const OnlyIcon = () => ( -
-
) -export const CustomContent = () => ( -
- -
-
- -
-
- +export const SecondaryOnlyIcon = () => ( +
+
) -CustomContent.story = { - name: 'Custom content', +export const SecondaryDropdown = () => { + const ref = useRef() + + return ( +
+
+ ) } -export const Toggle = () => +export const SecondaryOnlyIconDropdown = () => { + const ref = useRef() + + return ( +
+
+ ) +} diff --git a/pkg/webui/components/dropdown-v2/dropdown.styl b/pkg/webui/components/dropdown-v2/dropdown.styl deleted file mode 100644 index aa1b0e9207..0000000000 --- a/pkg/webui/components/dropdown-v2/dropdown.styl +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright © 2023 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. - -ul.dropdown - font-size: $fs.m - border-radius: $br.l - border: 1px solid $c.grey-200 - list-style-type: none - background: $c.white - box-shadow: 0px 3px 16px 0px rgba(0, 0, 0, 0.06) - position: absolute - padding: $cs.s - min-width: 14rem - z-index: $zi.dropdown - margin: $cs.xxs 0 0 - opacity: 0 - visibility: hidden - transition: opacity 0.2s ease-in-out, visibility 0s linear 0.2s - - &.open - opacity: 1 - visibility: visible - transition: opacity 0.2s ease-in-out, visibility 0s linear - - &.below - bottom: auto - top: calc(100% + 7px) - - &.above - top: auto - bottom: calc(100% + 7px) - - &.left - left: 0 - right: auto - - &.right - right: 0 - left: auto - - &.example - bottom: auto - top: 0 - left: 0 - right: auto - - &.larger - li.dropdown-item - & > a.button, & > button.button - padding: $cs.l - - hr - height: 1px - background-color: $c.grey-200 - - li.dropdown-header-item - display: block - margin-bottom: 0 - font-weight: $fw.bold - - span - line-height: 1 - display: block - padding: $cs.m - - li.dropdown-item - margin-bottom: 0 - text-align: left - - button.button - reset-button() - - & > a.button, & > button.button - box-sizing: border-box - line-height: 1 - display: flex - align-items: center - text-decoration: none - padding: $cs.xs - width: 100% - min-height: 2.429rem - &:not(.active) - color: $c.grey-700 - - .icon - color: $c.grey-700 - - &.active - border-radius: $br.m - background: $c.tts-primary-150 - transition: background 0.1s ease-in-out - color: $tc-deep-gray - - .icon - color: $tc-deep-gray - - &:hover - color: $tc-deep-gray - background: $c.tts-primary-150 - transition: background 0.1s ease-in-out - border-radius: $br.m - - .icon - color: $tc-deep-gray - - .icon - margin-right: $cs.xs - - &:hover - color: $tc-deep-gray - text-decoration: none - - .submenu-container - bottom: 0px - left: 0px - position: absolute - width: 180% - height: 5rem - z-index: 9999 - - .submenu-dropdown - bottom: -0px !important - left: calc(100% + -213px) !important diff --git a/pkg/webui/components/dropdown-v2/index.js b/pkg/webui/components/dropdown-v2/index.js deleted file mode 100644 index 8cf1d800ec..0000000000 --- a/pkg/webui/components/dropdown-v2/index.js +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright © 2023 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. - -import React, { useCallback, useEffect, useRef, useState } from 'react' -import classnames from 'classnames' -import { NavLink } from 'react-router-dom' - -import Icon from '@ttn-lw/components/icon' -import Link from '@ttn-lw/components/link' - -import Message from '@ttn-lw/lib/components/message' - -import PropTypes from '@ttn-lw/lib/prop-types' - -import style from './dropdown.styl' - -const Dropdown = ({ className, children, larger, onItemsClick, open }) => { - const ref = useRef(null) - const [isBelow, setIsBelow] = useState(false) - const [isOnRight, setIsOnRight] = useState(false) - - const positionDropdown = useCallback(() => { - if (ref.current) { - const parentRect = ref.current.parentElement.getBoundingClientRect() - const spaceBelow = window.innerHeight - parentRect.bottom - const spaceAbove = parentRect.top - const dropdownHeight = ref.current.clientHeight - const dropdownWidth = ref.current.clientWidth - const spaceOnLeft = parentRect.left - - setIsBelow(spaceBelow > dropdownHeight || spaceAbove < dropdownHeight) - - setIsOnRight(spaceOnLeft > dropdownWidth) - } - }, []) - - useEffect(() => { - if (open) positionDropdown() - }, [positionDropdown, open]) - - useEffect(() => { - window.addEventListener('scroll', positionDropdown) - window.addEventListener('resize', positionDropdown) - - return () => { - window.removeEventListener('resize', positionDropdown) - window.removeEventListener('scroll', positionDropdown) - } - }, [positionDropdown]) - - return ( -
    - {children} -
- ) -} - -Dropdown.propTypes = { - children: PropTypes.node.isRequired, - className: PropTypes.string, - larger: PropTypes.bool, - onItemsClick: PropTypes.func, - open: PropTypes.bool.isRequired, -} - -Dropdown.defaultProps = { - className: undefined, - larger: false, - onItemsClick: () => null, -} - -const DropdownItem = ({ - active, - icon, - title, - path, - action, - exact, - showActive, - tabIndex, - external, - submenuItems, - ...rest -}) => { - const [expandedSubmenu, setExpandedSubmenu] = useState(false) - const iconElement = icon && - const activeClassName = classnames({ - [style.active]: (!Boolean(action) && showActive) || active, - }) - const cls = useCallback( - ({ isActive }) => classnames(style.button, { [activeClassName]: isActive }), - [activeClassName], - ) - const ItemElement = action ? ( - - ) : external ? ( - - {Boolean(iconElement) ? iconElement : null} - - - ) : ( - - {iconElement} - - - ) - - const handleMouseEnter = useCallback(() => { - setExpandedSubmenu(true) - }, [setExpandedSubmenu]) - - const handleMouseLeave = useCallback(() => { - setExpandedSubmenu(false) - }, [setExpandedSubmenu]) - - const withSubmenu = ( - - ) - - return ( -
  • - {Boolean(submenuItems) ? withSubmenu : ItemElement} -
  • - ) -} - -DropdownItem.propTypes = { - action: PropTypes.func, - active: PropTypes.bool, - exact: PropTypes.bool, - external: PropTypes.bool, - icon: PropTypes.string, - path: PropTypes.string, - showActive: PropTypes.bool, - submenuItems: PropTypes.arrayOf(PropTypes.node), - tabIndex: PropTypes.string, - title: PropTypes.message.isRequired, -} - -DropdownItem.defaultProps = { - active: false, - action: undefined, - exact: false, - external: false, - icon: undefined, - path: undefined, - showActive: true, - tabIndex: '0', - submenuItems: undefined, -} - -const DropdownHeaderItem = ({ title }) => ( -
  • - - - -
  • -) - -DropdownHeaderItem.propTypes = { - title: PropTypes.message.isRequired, -} - -Dropdown.Item = DropdownItem -Dropdown.HeaderItem = DropdownHeaderItem - -export default Dropdown diff --git a/pkg/webui/components/dropdown/dropdown.styl b/pkg/webui/components/dropdown/dropdown.styl index ef553c6f83..aa1b0e9207 100644 --- a/pkg/webui/components/dropdown/dropdown.styl +++ b/pkg/webui/components/dropdown/dropdown.styl @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 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. @@ -13,15 +13,47 @@ // limitations under the License. ul.dropdown - border-normal() - z-index: $zi.nav - right: 0 + font-size: $fs.m + border-radius: $br.l + border: 1px solid $c.grey-200 + list-style-type: none + background: $c.white + box-shadow: 0px 3px 16px 0px rgba(0, 0, 0, 0.06) position: absolute - background: white - padding: 0 + padding: $cs.s min-width: 14rem z-index: $zi.dropdown - margin: 0 + margin: $cs.xxs 0 0 + opacity: 0 + visibility: hidden + transition: opacity 0.2s ease-in-out, visibility 0s linear 0.2s + + &.open + opacity: 1 + visibility: visible + transition: opacity 0.2s ease-in-out, visibility 0s linear + + &.below + bottom: auto + top: calc(100% + 7px) + + &.above + top: auto + bottom: calc(100% + 7px) + + &.left + left: 0 + right: auto + + &.right + right: 0 + left: auto + + &.example + bottom: auto + top: 0 + left: 0 + right: auto &.larger li.dropdown-item @@ -29,9 +61,8 @@ ul.dropdown padding: $cs.l hr - margin-top: 0 - height: .1rem - background-color: $c-divider + height: 1px + background-color: $c.grey-200 li.dropdown-header-item display: block @@ -44,8 +75,8 @@ ul.dropdown padding: $cs.m li.dropdown-item - display: block margin-bottom: 0 + text-align: left button.button reset-button() @@ -53,25 +84,51 @@ ul.dropdown & > a.button, & > button.button box-sizing: border-box line-height: 1 - display: block + display: flex + align-items: center text-decoration: none - padding: $cs.m + padding: $cs.xs width: 100% + min-height: 2.429rem &:not(.active) - color: $tc-deep-gray + color: $c.grey-700 + + .icon + color: $c.grey-700 &.active - border-left: $c-active-blue 5px solid - color: $c-active-blue + border-radius: $br.m + background: $c.tts-primary-150 + transition: background 0.1s ease-in-out + color: $tc-deep-gray + + .icon + color: $tc-deep-gray &:hover - color: white + color: $tc-deep-gray + background: $c.tts-primary-150 + transition: background 0.1s ease-in-out + border-radius: $br.m .icon - color: white + color: $tc-deep-gray .icon margin-right: $cs.xs &:hover - background-color: $c-active-blue + color: $tc-deep-gray + text-decoration: none + + .submenu-container + bottom: 0px + left: 0px + position: absolute + width: 180% + height: 5rem + z-index: 9999 + + .submenu-dropdown + bottom: -0px !important + left: calc(100% + -213px) !important diff --git a/pkg/webui/components/dropdown/index.js b/pkg/webui/components/dropdown/index.js index 1a39a90e3a..8cf1d800ec 100644 --- a/pkg/webui/components/dropdown/index.js +++ b/pkg/webui/components/dropdown/index.js @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 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,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useCallback } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import classnames from 'classnames' import { NavLink } from 'react-router-dom' @@ -25,21 +25,64 @@ import PropTypes from '@ttn-lw/lib/prop-types' import style from './dropdown.styl' -const Dropdown = React.forwardRef(({ className, children, larger, onItemsClick }, ref) => ( -
      - {children} -
    -)) +const Dropdown = ({ className, children, larger, onItemsClick, open }) => { + const ref = useRef(null) + const [isBelow, setIsBelow] = useState(false) + const [isOnRight, setIsOnRight] = useState(false) + + const positionDropdown = useCallback(() => { + if (ref.current) { + const parentRect = ref.current.parentElement.getBoundingClientRect() + const spaceBelow = window.innerHeight - parentRect.bottom + const spaceAbove = parentRect.top + const dropdownHeight = ref.current.clientHeight + const dropdownWidth = ref.current.clientWidth + const spaceOnLeft = parentRect.left + + setIsBelow(spaceBelow > dropdownHeight || spaceAbove < dropdownHeight) + + setIsOnRight(spaceOnLeft > dropdownWidth) + } + }, []) + + useEffect(() => { + if (open) positionDropdown() + }, [positionDropdown, open]) + + useEffect(() => { + window.addEventListener('scroll', positionDropdown) + window.addEventListener('resize', positionDropdown) + + return () => { + window.removeEventListener('resize', positionDropdown) + window.removeEventListener('scroll', positionDropdown) + } + }, [positionDropdown]) + + return ( +
      + {children} +
    + ) +} Dropdown.propTypes = { children: PropTypes.node.isRequired, className: PropTypes.string, larger: PropTypes.bool, onItemsClick: PropTypes.func, + open: PropTypes.bool.isRequired, } Dropdown.defaultProps = { @@ -58,8 +101,10 @@ const DropdownItem = ({ showActive, tabIndex, external, + submenuItems, ...rest }) => { + const [expandedSubmenu, setExpandedSubmenu] = useState(false) const iconElement = icon && const activeClassName = classnames({ [style.active]: (!Boolean(action) && showActive) || active, @@ -90,9 +135,39 @@ const DropdownItem = ({ ) + + const handleMouseEnter = useCallback(() => { + setExpandedSubmenu(true) + }, [setExpandedSubmenu]) + + const handleMouseLeave = useCallback(() => { + setExpandedSubmenu(false) + }, [setExpandedSubmenu]) + + const withSubmenu = ( + + ) + return (
  • - {ItemElement} + {Boolean(submenuItems) ? withSubmenu : ItemElement}
  • ) } @@ -105,6 +180,7 @@ DropdownItem.propTypes = { icon: PropTypes.string, path: PropTypes.string, showActive: PropTypes.bool, + submenuItems: PropTypes.arrayOf(PropTypes.node), tabIndex: PropTypes.string, title: PropTypes.message.isRequired, } @@ -118,6 +194,7 @@ DropdownItem.defaultProps = { path: undefined, showActive: true, tabIndex: '0', + submenuItems: undefined, } const DropdownHeaderItem = ({ title }) => ( diff --git a/pkg/webui/components/dropdown-v2/story.js b/pkg/webui/components/dropdown/story.js similarity index 100% rename from pkg/webui/components/dropdown-v2/story.js rename to pkg/webui/components/dropdown/story.js diff --git a/pkg/webui/components/header-v2/header-v2.styl b/pkg/webui/components/header-v2/header-v2.styl deleted file mode 100644 index 4f555e5b5b..0000000000 --- a/pkg/webui/components/header-v2/header-v2.styl +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2023 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. - -.container - padding: 0 $cs.m - background: $c['tts-primary-050'] - z-index: $zi.nav - border-bottom: 1px solid $c-divider - display: flex - justify-content: space-between - align-items: center - height: 4rem - -.logo - height: $cs.l diff --git a/pkg/webui/components/header-v2/index.js b/pkg/webui/components/header-v2/index.js deleted file mode 100644 index d1180edde3..0000000000 --- a/pkg/webui/components/header-v2/index.js +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright © 2023 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. - -import React, { useRef } from 'react' -import classnames from 'classnames' - -import { Breadcrumbs } from '@ttn-lw/components/breadcrumbs/breadcrumbs' -import Button from '@ttn-lw/components/button-v2' -import ProfileDropdown from '@ttn-lw/components/profile-dropdown-v2' -import Link from '@ttn-lw/components/link' - -import PropTypes from '@ttn-lw/lib/prop-types' - -import style from './header-v2.styl' - -const Header = ({ - brandLogo, - logo, - breadcrumbs, - className, - addDropdownItems, - starDropdownItems, - profileDropdownItems, - user, - onMenuClick, - ...rest -}) => { - const addRef = useRef(null) - const starRef = useRef(null) - - // Const isGuest = !Boolean(user) - - return ( -
    - -
    -
    - -
    -
    -
    - ) -} - -const imgPropType = PropTypes.shape({ - src: PropTypes.string.isRequired, - alt: PropTypes.string.isRequired, -}) - -Header.propTypes = { - /** 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. */ - profileDropdownItems: PropTypes.node.isRequired, - /** The dropdown items when the star button is clicked. */ - starDropdownItems: PropTypes.node.isRequired, - /** - * The User object, retrieved from the API. If it is `undefined`, then the - * guest header is rendered. - */ - user: PropTypes.user, -} - -Header.defaultProps = { - className: undefined, - user: undefined, - brandLogo: undefined, -} - -export default Header diff --git a/pkg/webui/components/header-v2/story.js b/pkg/webui/components/header-v2/story.js deleted file mode 100644 index 5da2147ecc..0000000000 --- a/pkg/webui/components/header-v2/story.js +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright © 2023 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. - -import React from 'react' - -import TtsLogo from '@assets/static/tts-logo.svg' - -import Dropdown from '@ttn-lw/components/dropdown-v2' -import ExampleLogo from '@ttn-lw/components/logo/story-logo-new.svg' -import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' - -import Header from '.' - -const user = { - name: 'johndoe', - ids: { - user_id: 'jdoe300', - }, -} - -export default { - title: 'Header V2', -} - -const plusDropdownItems = ( - <> - - - - - -) - -const starDropdownItems = ( - <> - - - - - - - -) - -const profileDropdownItems = ( - <> - - - - -
    - - - -
    - - -) - -export const Default = () => { - const breadcrumbs = [ - , - , - , - ] - - return ( -
    -
    -
    - ) -} diff --git a/pkg/webui/components/header/header.styl b/pkg/webui/components/header/header.styl index 9433fef1fb..4f555e5b5b 100644 --- a/pkg/webui/components/header/header.styl +++ b/pkg/webui/components/header/header.styl @@ -1,4 +1,4 @@ -// Copyright © 2019 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 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,128 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -mobile-styles($breakpoint) - +media-query($breakpoint) - &.mobile-menu-open - position: fixed - z-index: $zi.mobile-menu - top: 0 - left: 0 - height: 100vh - background-color: white - - .hamburger img - width: 1.5rem - - .mobile-menu-button - display: block - - .nav-list - display: none - - .profile-dropdown - display: none - - .mobile-menu - display: flex - - +media-query-between($bp.s, $breakpoint) - .mobile-menu - height: 'calc(100vh - %s)' % $header-height - - +media-query($bp.s) - z-index: $zi.mobile-menu - position: fixed - top: 0 - - & + main - padding-top: $header-height - +media-query($bp.s) - padding-top: $header-height-mobile - - .bar - height: $header-height-mobile - background-color: white - padding: 0 $ls.xxs - - .mobile-menu - height: 'calc(100vh - %s)' % $header-height-mobile - top: $header-height-mobile - - .mobile-menu-button - width: $header-height-mobile - margin-right: $ls.xxs * -1 - .container - mobile-styles($bp.m) - background-color: white + padding: 0 $cs.m + background: $c['tts-primary-050'] z-index: $zi.nav - width: 100% - -.bar - border-normal('bottom') - background: linear-gradient(bottom left, 0% #F8F8F8, 60% white) // @stylint ignore - height: $header-height - box-sizing: border-box - position: relative + border-bottom: 1px solid $c-divider display: flex - padding: 0 $ls.s justify-content: space-between - flex: none - -.logo - display: inline-flex align-items: center - justify-content: flex-start + height: 4rem - img - nudge('up') - -.nav-list - display: flex - align-items: stretch - justify-content: flex-start - padding: 0 $ls.m - flex-grow: 2 - height: 100% - max-width: 56rem - flex-shrink: 0 - -.profile-dropdown - margin-right: -2rem - -.mobile-menu-button - reset-button() - display: none - width: $header-height - margin-right: $ls.s * -1 - height: 100% - position: relative - - .hamburger - center-absolute() - display: block - width: 2rem - height: 100% - - img - center-absolute() - width: 2rem - -.left - display: flex - align-items: center - max-width: 42rem - flex-grow: 1 - -.right - padding-left: $cs.l - display: flex - align-items: center - justify-content: flex-end - max-width: 25rem - flex-grow: 2 - - & > div:first-child - max-width: 15rem - border-color: $c-divider-dark +.logo + height: $cs.l diff --git a/pkg/webui/components/header/index.js b/pkg/webui/components/header/index.js index d7af0b5d8a..fbe07991a8 100644 --- a/pkg/webui/components/header/index.js +++ b/pkg/webui/components/header/index.js @@ -1,4 +1,4 @@ -// Copyright © 2019 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 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,107 +12,87 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useState, useCallback } from 'react' +import React, { useRef } from 'react' import classnames from 'classnames' -import hamburgerMenuNormal from '@assets/misc/hamburger-menu-normal.svg' -import hamburgerMenuClose from '@assets/misc/hamburger-menu-close.svg' - -import NavigationBar from '@ttn-lw/components/navigation/bar' +import Button from '@ttn-lw/components/button' import ProfileDropdown from '@ttn-lw/components/profile-dropdown' -import MobileMenu from '@ttn-lw/components/mobile-menu' -import Input from '@ttn-lw/components/input' +import Link from '@ttn-lw/components/link' import PropTypes from '@ttn-lw/lib/prop-types' import style from './header.styl' const Header = ({ + brandLogo, + logo, + breadcrumbs, className, - dropdownItems, - navigationEntries, + addDropdownItems, + starDropdownItems, + profileDropdownItems, user, - searchable, - logo, - mobileDropdownItems, - onLogout, - onSearchRequest, + onMenuClick, ...rest }) => { - const isGuest = !Boolean(user) - - const [mobileMenuOpen, setMobileMenuOpen] = useState(false) - const handleMobileMenuClick = useCallback(() => { - setMobileMenuOpen(!mobileMenuOpen) - }, [mobileMenuOpen]) - - const handleMobileMenuItemsClick = useCallback(() => { - setMobileMenuOpen(false) - }, []) + const addRef = useRef(null) + const starRef = useRef(null) - const classNames = classnames(className, style.container, { - [style.mobileMenuOpen]: mobileMenuOpen, - }) - - const hamburgerGraphic = mobileMenuOpen ? hamburgerMenuClose : hamburgerMenuNormal + // Const isGuest = !Boolean(user) return ( -
    -
    -
    - {logo} - {!isGuest && {navigationEntries}} -
    - {!isGuest && ( -
    - {searchable && } - - {dropdownItems} - - -
    - )} +
    +
    +
    +
    - {mobileMenuOpen && ( - +
    ) } +const imgPropType = PropTypes.shape({ + src: PropTypes.string.isRequired, + alt: PropTypes.string.isRequired, +}) + Header.propTypes = { + /** 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, - /** The child node of the dropdown component. */ - dropdownItems: PropTypes.node, - /** The logo component. */ - logo: PropTypes.node.isRequired, - /** The child node of the mobile dropdown. */ - mobileDropdownItems: PropTypes.node, - /** The Child node of the navigation bar. */ - navigationEntries: PropTypes.node, - /** A handler for when the user used the search input. */ - onLogout: PropTypes.func, - /** Handler of the search function. */ - onSearchRequest: PropTypes.func, - /* A flag indicating whether the header has a search input. */ - searchable: PropTypes.bool, + 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. */ + profileDropdownItems: PropTypes.node.isRequired, + /** The dropdown items when the star button is clicked. */ + starDropdownItems: PropTypes.node.isRequired, /** * The User object, retrieved from the API. If it is `undefined`, then the * guest header is rendered. @@ -122,13 +102,8 @@ Header.propTypes = { Header.defaultProps = { className: undefined, - dropdownItems: undefined, - navigationEntries: undefined, - mobileDropdownItems: null, - onSearchRequest: () => null, - onLogout: () => null, - searchable: false, user: undefined, + brandLogo: undefined, } export default Header diff --git a/pkg/webui/components/header/story.js b/pkg/webui/components/header/story.js index 3d1610df6c..4c48bbb031 100644 --- a/pkg/webui/components/header/story.js +++ b/pkg/webui/components/header/story.js @@ -1,4 +1,4 @@ -// Copyright © 2019 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 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. @@ -13,70 +13,83 @@ // limitations under the License. import React from 'react' -import { action } from '@storybook/addon-actions' -import TtsLogo from '@assets/static/logo.svg' +import TtsLogo from '@assets/static/tts-logo.svg' import Dropdown from '@ttn-lw/components/dropdown' -import NavigationBar from '@ttn-lw/components/navigation/bar' -import Logo from '@ttn-lw/components/logo' -import ExampleLogo from '@ttn-lw/components/logo/story-logo.svg' +import ExampleLogo from '@ttn-lw/components/logo/story-logo-new.svg' +import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' import Header from '.' const user = { - name: 'kschiffer', + name: 'johndoe', ids: { - user_id: 'ksc300', + user_id: 'jdoe300', }, } -const singleLogo = -const doubleLogo = ( - -) +export default { + title: 'Header V2', +} -const navigationEntries = ( - - - - - - +const plusDropdownItems = ( + <> + + + + + ) -const items = ( - - - - +const starDropdownItems = ( + <> + + + + + + + ) -export default { - title: 'Header', -} - -export const SingleLogo = () => ( -
    +const profileDropdownItems = ( + <> + + + + +
    + + + +
    + + ) -export const DoubleLogo = () => ( -
    -) +export const Default = () => { + const breadcrumbs = [ + , + , + , + ] + + return ( +
    +
    +
    + ) +} diff --git a/pkg/webui/components/navigation/side-v2/index.js b/pkg/webui/components/navigation/side-v2/index.js deleted file mode 100644 index 4b895e3948..0000000000 --- a/pkg/webui/components/navigation/side-v2/index.js +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2023 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. - -import React, { useRef } from 'react' -import classnames from 'classnames' - -import PropTypes from '@ttn-lw/lib/prop-types' - -import SideNavigationList from './list' -import SideNavigationItem from './item' - -import style from './side.styl' - -const SideNavigation = ({ className, children }) => { - const node = useRef() - - const navigationClassNames = classnames(className, style.navigation) - - return ( - - ) -} - -SideNavigation.propTypes = { - children: PropTypes.node.isRequired, - className: PropTypes.string, -} - -SideNavigation.defaultProps = { - className: undefined, -} - -SideNavigation.Item = SideNavigationItem - -export default SideNavigation diff --git a/pkg/webui/components/navigation/side-v2/item/index.js b/pkg/webui/components/navigation/side-v2/item/index.js deleted file mode 100644 index a87411fd0b..0000000000 --- a/pkg/webui/components/navigation/side-v2/item/index.js +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright © 2023 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. - -import React, { useCallback, useEffect, useState } from 'react' -import classnames from 'classnames' - -import Dropdown from '@ttn-lw/components/dropdown-v2' -import MenuLink from '@ttn-lw/components/sidebar/side-menu-link' -import Button from '@ttn-lw/components/button-v2' -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 style from './item.styl' - -const handleItemClick = event => { - if (event && event.target) { - event.target.blur() - } -} - -const SideNavigationItem = props => { - const { className, children, title, depth, icon, path, exact, isActive, isMinimized } = props - const [isExpanded, setIsExpanded] = useState(false) - - const handleExpandCollapsableItem = useCallback(() => { - setIsExpanded(isExpanded => !isExpanded) - document.activeElement.blur() - }, []) - - useEffect(() => { - // Make sure that the item corresponding to the currently open path is expanded - // on initial render, if applicable - if (Boolean(children)) { - const paths = React.Children.toArray(children).reduce( - (paths, child) => [...paths, ...(Boolean(child) ? child.props.path : [])], - [], - ) - for (const path of paths) { - if (location.pathname.includes(path)) { - setIsExpanded(true) - return - } - } - } - }, [children]) - - return ( -
  • - {Boolean(children) ? ( - - ) : ( - - )} -
  • - ) -} - -SideNavigationItem.propTypes = { - children: PropTypes.node, - className: PropTypes.string, - depth: PropTypes.number, - /** A flag specifying whether the path of the linkable item should be matched exactly or not. */ - exact: PropTypes.bool, - /** The name of the icon for the side navigation item. */ - icon: PropTypes.string, - /** A flag specifying whether the side navigation item is active or not. */ - isActive: PropTypes.bool, - isMinimized: PropTypes.bool, - /** The path of the linkable side navigation item. */ - path: PropTypes.string, - /** The title of the side navigation item. */ - title: PropTypes.message.isRequired, -} - -SideNavigationItem.defaultProps = { - className: undefined, - children: undefined, - exact: false, - icon: undefined, - isActive: false, - depth: 0, - path: undefined, - isMinimized: false, -} - -const CollapsableItem = ({ - children, - onClick, - isExpanded, - isMinimized, - title, - icon, - depth, - onDropdownItemsClick, - currentPathName, -}) => { - 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 => { - document.activeElement.blur() - onClick(event) - }, - [onClick], - ) - - return ( - <> - - - ) -} - -LinkItem.propTypes = { - exact: PropTypes.bool.isRequired, - icon: PropTypes.string, - onClick: PropTypes.func, - path: PropTypes.string, - title: PropTypes.message.isRequired, -} - -LinkItem.defaultProps = { - icon: undefined, - path: undefined, - onClick: () => null, -} - -export default SideNavigationItem diff --git a/pkg/webui/components/navigation/side-v2/item/item.styl b/pkg/webui/components/navigation/side-v2/item/item.styl deleted file mode 100644 index ae017616e8..0000000000 --- a/pkg/webui/components/navigation/side-v2/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/navigation/side-v2/list/index.js b/pkg/webui/components/navigation/side-v2/list/index.js deleted file mode 100644 index 4561c58e4c..0000000000 --- a/pkg/webui/components/navigation/side-v2/list/index.js +++ /dev/null @@ -1,50 +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. - -import React from 'react' -import classnames from 'classnames' - -import PropTypes from '@ttn-lw/lib/prop-types' - -import style from './list.styl' - -const SideNavigationList = ({ children, className, depth, isExpanded }) => { - const isRoot = depth === 0 - const listClassNames = classnames(className, style.list, { - [style.listNested]: !isRoot, - [style.listExpanded]: isExpanded, - }) - - return
      {children}
    -} - -SideNavigationList.propTypes = { - children: PropTypes.node.isRequired, - className: PropTypes.string, - /** The depth of the current list starting at 0 for the root list. */ - depth: PropTypes.number, - /** - * A flag specifying whether the side navigation list is expanded or not. - * Applicable to nested lists. - */ - isExpanded: PropTypes.bool, -} - -SideNavigationList.defaultProps = { - className: undefined, - depth: 0, - isExpanded: false, -} - -export default SideNavigationList diff --git a/pkg/webui/components/navigation/side-v2/list/list.styl b/pkg/webui/components/navigation/side-v2/list/list.styl deleted file mode 100644 index baddbf7c23..0000000000 --- a/pkg/webui/components/navigation/side-v2/list/list.styl +++ /dev/null @@ -1,46 +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. - -.list - list-style-type: none - padding: 0 - margin: 0 - overflow: visible - font-size: $fs.m - - & > 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 - - &-expanded, &-nested - & li a - padding-left: $cs.l + $cs.xs - -.icon - margin-right: $cs.xs diff --git a/pkg/webui/components/navigation/side-v2/side.styl b/pkg/webui/components/navigation/side-v2/side.styl deleted file mode 100644 index 05670b0362..0000000000 --- a/pkg/webui/components/navigation/side-v2/side.styl +++ /dev/null @@ -1,91 +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. - -.container - border-normal(right) - display: flex - flex-direction: column - z-index: $zi.nav - +media-query-min($bp.s) - left: 0 - top: 0 - position: absolute - height: 100% - +media-query($bp.s) - height: auto - position: sticky - top: $header-height-mobile - z-index: $zi.nav - +media-query-height(500px) - position: static - -.navigation - box-sizing: border-box - max-height: 100% - width: 100% - - &-minimized - overflow: visible - display: flex - flex-direction: column - justify-content: space-between - +media-query-min($bp.s) - width: $sidebar-width-minimized - display: inline-flex - - .header - padding: 0 - width: 100% - display: flex - height: $breadcrumbs-bar-height - justify-content: center - - .message - display: none - - .icon - margin-right: 0 - - .navigation-list - overflow: visible - - .drawer - overflow: visible - -.drawer - background-color: white - z-index: $zi.nav - overflow: auto - +media-query($bp.s) - sidebar-transition(left, box-shadow) - position: fixed - top: $header-height-mobile - left: -60vw - bottom: 0 - width: 60vw - max-width: 20rem - - &-open - box-shadow: 0 0 13rem 2rem rgba(0,0,0,.4) - left: 0 - -.message - display: block - white-space: nowrap - overflow: hidden - text-overflow: ellipsis - -.scroll-lock - +media-query($bp.s) - overflow: hidden diff --git a/pkg/webui/components/navigation/side-v2/story.js b/pkg/webui/components/navigation/side-v2/story.js deleted file mode 100644 index baf5acf7cd..0000000000 --- a/pkg/webui/components/navigation/side-v2/story.js +++ /dev/null @@ -1,57 +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. - -import React from 'react' - -import SidebarContext from '@console/containers/side-bar/context' - -import SideNavigationItem from './item' - -import SideNavigation from '.' - -export default { - title: 'Navigation v2', - component: SideNavigation, - decorators: [ - storyFn => ( - {storyFn()} - ), - ], -} - -export const _SideNavigation = () => ( -
    - - - - - - - - - - - - - - - - - -
    -) diff --git a/pkg/webui/components/navigation/side/context.js b/pkg/webui/components/navigation/side/context.js deleted file mode 100644 index 6537cf3e33..0000000000 --- a/pkg/webui/components/navigation/side/context.js +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2020 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. - -import React from 'react' - -const SideNavigationContext = React.createContext() - -export default SideNavigationContext diff --git a/pkg/webui/components/navigation/side/index.js b/pkg/webui/components/navigation/side/index.js index 77bafa2edc..4b895e3948 100644 --- a/pkg/webui/components/navigation/side/index.js +++ b/pkg/webui/components/navigation/side/index.js @@ -12,214 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -import ReactDom from 'react-dom' -import React, { useState, useEffect, useCallback, useRef } from 'react' +import React, { useRef } from 'react' import classnames from 'classnames' -import { defineMessages, useIntl } from 'react-intl' - -import LAYOUT from '@ttn-lw/constants/layout' - -import Button from '@ttn-lw/components/button' -import Icon from '@ttn-lw/components/icon' -import Link from '@ttn-lw/components/link' - -import Message from '@ttn-lw/lib/components/message' import PropTypes from '@ttn-lw/lib/prop-types' import SideNavigationList from './list' import SideNavigationItem from './item' -import SideNavigationContext from './context' import style from './side.styl' -const getViewportWidth = () => - Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0) - -const m = defineMessages({ - hideSidebar: 'Hide sidebar', -}) - -const SideNavigation = ({ - appContainerId, - modifyAppContainerClasses, - className, - header, - children, -}) => { - const [isMinimized, setIsMinimized] = useState(getViewportWidth() <= LAYOUT.BREAKPOINTS.M) - const [isDrawerOpen, setIsDrawerOpen] = useState(false) - const [preferMinimized, setPreferMinimized] = useState(false) +const SideNavigation = ({ className, children }) => { const node = useRef() - const intl = useIntl() - - const updateAppContainerClasses = useCallback( - (initial = false) => { - if (!modifyAppContainerClasses) { - return - } - const containerClasses = document.getElementById(appContainerId).classList - containerClasses.add('with-sidebar') - if (!initial) { - containerClasses.add('sidebar-transitioned') - } - if (isMinimized) { - containerClasses.add('sidebar-minimized') - } else { - containerClasses.remove('sidebar-minimized') - } - }, - [modifyAppContainerClasses, appContainerId, isMinimized], - ) - - const removeAppContainerClasses = useCallback(() => { - if (!modifyAppContainerClasses) { - return - } - document - .getElementById(appContainerId) - .classList.remove('with-sidebar', 'sidebar-minimized', 'sidebar-transitioned') - }, [modifyAppContainerClasses, appContainerId]) - - const closeDrawer = useCallback(() => { - setIsDrawerOpen(false) - document.body.classList.remove(style.scrollLock) - }, []) - - const openDrawer = useCallback(() => { - setIsDrawerOpen(true) - document.body.classList.add(style.scrollLock) - }, []) - useEffect(() => { - const onClickOutside = e => { - if (isDrawerOpen && node.current && !node.current.contains(e.target)) { - closeDrawer() - } - } - - if (isDrawerOpen) { - document.addEventListener('mousedown', onClickOutside) - return () => document.removeEventListener('mousedown', onClickOutside) - } - }, [isDrawerOpen, closeDrawer]) - - const setMinimizedState = useCallback(() => { - const viewportWidth = getViewportWidth() - if ( - (!isMinimized && viewportWidth <= LAYOUT.BREAKPOINTS.M) || - (isMinimized && viewportWidth > LAYOUT.BREAKPOINTS.M) - ) { - setIsMinimized(getViewportWidth() <= LAYOUT.BREAKPOINTS.M || preferMinimized) - updateAppContainerClasses() - } - }, [isMinimized, preferMinimized, updateAppContainerClasses]) - - useEffect(() => { - window.addEventListener('resize', setMinimizedState) - updateAppContainerClasses(true) - return () => { - window.removeEventListener('resize', setMinimizedState) - removeAppContainerClasses() - } - }, [removeAppContainerClasses, setMinimizedState, updateAppContainerClasses]) - - const onToggle = useCallback(async () => { - setIsMinimized(prev => !prev) - setPreferMinimized(prev => !prev) - updateAppContainerClasses() - }, [updateAppContainerClasses]) - - const onDrawerExpandClick = useCallback(() => { - if (!isDrawerOpen) { - openDrawer() - } else { - closeDrawer() - } - }, [isDrawerOpen, openDrawer, closeDrawer]) - - const onLeafItemClick = useCallback(() => { - if (isDrawerOpen) { - onDrawerExpandClick() - } - }, [isDrawerOpen, onDrawerExpandClick]) - - const navigationClassNames = classnames(className, style.navigation, { - [style.navigationMinimized]: isMinimized, - }) - const minimizeButtonClassNames = classnames(style.minimizeButton, { - [style.minimizeButtonMinimized]: isMinimized, - }) - - const drawerClassNames = classnames(style.drawer, { [style.drawerOpen]: isDrawerOpen }) + const navigationClassNames = classnames(className, style.navigation) return ( - <> - - - - - {subItems.map(item => ( - - ))} - - - {children} - + {!isMinimized && ( + <> + + + + )} + {isMinimized && ( +
    + + + {subItems.map(item => ( + + ))} + +
    + )} + + {!isMinimized && ( + + {children} + + )} ) } @@ -179,8 +193,8 @@ CollapsableItem.propTypes = { currentPathName: PropTypes.string.isRequired, depth: PropTypes.number.isRequired, icon: PropTypes.string, - isActive: PropTypes.bool.isRequired, isExpanded: PropTypes.bool.isRequired, + isMinimized: PropTypes.bool.isRequired, onClick: PropTypes.func.isRequired, onDropdownItemsClick: PropTypes.func, title: PropTypes.message.isRequired, @@ -192,8 +206,8 @@ CollapsableItem.defaultProps = { onDropdownItemsClick: () => null, } -const LinkItem = ({ onClick, title, icon, exact, path, onDropdownItemsClick }) => { - const handleLinkItemClick = React.useCallback( +const LinkItem = ({ onClick, title, icon, exact, path }) => { + const handleLinkItemClick = useCallback( event => { document.activeElement.blur() onClick(event) @@ -203,19 +217,7 @@ const LinkItem = ({ onClick, title, icon, exact, path, onDropdownItemsClick }) = return ( <> - - {icon && } - - - - - + ) } @@ -224,7 +226,6 @@ LinkItem.propTypes = { exact: PropTypes.bool.isRequired, icon: PropTypes.string, onClick: PropTypes.func, - onDropdownItemsClick: PropTypes.func, path: PropTypes.string, title: PropTypes.message.isRequired, } @@ -233,7 +234,6 @@ LinkItem.defaultProps = { icon: undefined, path: undefined, onClick: () => null, - onDropdownItemsClick: () => null, } export default SideNavigationItem diff --git a/pkg/webui/components/navigation/side/item/item.styl b/pkg/webui/components/navigation/side/item/item.styl index 4ecc7a7d2e..ae017616e8 100644 --- a/pkg/webui/components/navigation/side/item/item.styl +++ b/pkg/webui/components/navigation/side/item/item.styl @@ -13,106 +13,53 @@ // limitations under the License. .item - +media-query-min($bp.s) - &-minimized - position: relative - .message, - .expand-icon - .sub-items - display: none - - .icon - margin-right: 0 - - .link, - .button - display: flex - padding: $cs.m 0 - height: auto - justify-content: space-evenly - - &:focus-within, - &:hover - .fly-out-list - display: block + &:hover + .fly-out-list + display: block .button reset-button() - one-liner() width: 100% - display: block - text-align: left + transition: 0.2s background-color ease-in, 0.2s color ease-in + text-decoration: none + display: flex + align-items: center position: relative - padding: $cs.s $cs.xxl $cs.s $cs.xl - height: 3.1rem - transition: background-color $ad.m, color $ad.s ease-in + gap: $cs.xs !important - &:active - background-color: lighter($c-active-blue-hover, 10) + span + &:not(:first-child) + color: $c.grey-700 !important - &-active, - &:hover - color: $c-active-blue + &:hover&:not(&-active) + background: none !important + color: $tc-deep-gray !important + text-decoration: none -.link - one-liner() - color: $tc-deep-gray - padding: $cs.m $cs.xxl $cs.m $cs.xl - display: block - position: relative + span + &:not(:first-child) + color: $c.grey-900 !important - &:not(&-active)::before // @stylint ignore - content: '' - position: absolute - z-index: -1 - top: 0 - left: 0 - width: 0 - height: 100% - background-color: $c-backdrop-lighter - transition: width $ad.s ease-out 0s, background-color $ad.s linear - - &:hover:not(&-active)::before // @stylint ignore - width: 100% - transition: width $ad.s ease-out $ad.m, background-color $ad.s linear - - &::after - content: '' - position: absolute - top: 0 - left: 0 - width: 3px - height: 100% - background-color: $c-subtle-fill - transform: scaleY(0) - transform-origin: center - transition: transform $ad.m ease-in-out, background-color $ad.s linear - - &:hover:not(&-active)::after // @stylint ignore - transform: scaleY(1) + .icon + color: inherit &-active - background-color: $c-backdrop-lighter - color: $c-active-blue - - &::before - content: '' - position: absolute - bottom: 0 - left: 0 - height: 100% - display: block - width: 3px - background: $c-active-blue + background: $c.tts-primary-150 + color: $c.grey-900 !important - .icon - color: $c-active-blue + &:hover + background: $c.tts-primary-150 !important + color: $c.grey-900 !important -.icon - margin-right: $cs.s - vertical-align: middle - color: $c-icon-fill - font-size: 1.5rem + span + &:not(:first-child) + color: $c.grey-900 !important + + .icon + color: inherit + + .icon + color: inherit .message vertical-align: middle @@ -127,16 +74,14 @@ &-open transform: translateY(-50%) rotate(180deg) -.fly-out-list - display: none - left: 3rem +.fly-out-list-container top: 0 + left: 0 + position: absolute + width: 180% + height: 13rem - &::before - content: '' - position: absolute - top: -10px - bottom: -10px - left: -10px - right: -10px - z-index: -1 +.fly-out-list + display: none + left: 5rem !important + top: 0 !important diff --git a/pkg/webui/components/navigation/side/list/index.js b/pkg/webui/components/navigation/side/list/index.js index 4640991c93..4561c58e4c 100644 --- a/pkg/webui/components/navigation/side/list/index.js +++ b/pkg/webui/components/navigation/side/list/index.js @@ -25,6 +25,7 @@ const SideNavigationList = ({ children, className, depth, isExpanded }) => { [style.listNested]: !isRoot, [style.listExpanded]: isExpanded, }) + return
      {children}
    } diff --git a/pkg/webui/components/navigation/side/list/list.styl b/pkg/webui/components/navigation/side/list/list.styl index 8b850437bb..baddbf7c23 100644 --- a/pkg/webui/components/navigation/side/list/list.styl +++ b/pkg/webui/components/navigation/side/list/list.styl @@ -16,7 +16,8 @@ list-style-type: none padding: 0 margin: 0 - overflow: hidden + overflow: visible + font-size: $fs.m & > li:not(:last-child) margin-bottom: 0 @@ -28,18 +29,18 @@ transition: height $ad.m ease-in-out &-nested > li - height: 0 - transition: visibility $ad.m, height $ad.m ease-in-out + height: 0px + transition: height $ad.m ease-in-out &-nested:not(.list-expanded) > li visibility: hidden &-expanded > li - height: 3.1rem + height: 2.4rem &-expanded, &-nested & li a - padding-left: $cs.l + $cs.l + padding-left: $cs.l + $cs.xs .icon margin-right: $cs.xs diff --git a/pkg/webui/components/navigation/side/side.styl b/pkg/webui/components/navigation/side/side.styl index 5d1e60a491..05670b0362 100644 --- a/pkg/webui/components/navigation/side/side.styl +++ b/pkg/webui/components/navigation/side/side.styl @@ -12,14 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -$minimize-button-height = 5rem - -// This style is to be applied to the portalled container. .container border-normal(right) display: flex flex-direction: column - background-color: white z-index: $zi.nav +media-query-min($bp.s) left: 0 @@ -34,50 +30,10 @@ $minimize-button-height = 5rem +media-query-height(500px) position: static - .minimize-button - reset-button() - transition-color() - border-normal(top) - box-sizing: border-box - color: $tc-subtle-gray - background-color: white - position: sticky - top: "calc(100vh - %s)" % $minimize-button-height - width: 100% - height: $minimize-button-height - margin-bottom: 1px - flex-shrink: 0 - &:not(.minimize-button-minimized) - padding-left: $cs.l - text-align: left - &-minimized - padding-left: 0 - text-align: center - +media-query-min($bp.s) - display: block - +media-query($bp.s) - display: none - &:hover - color: $tc-deep-gray - span:first-child - nudge('up') - - .navigation - sidebar-transition(width) - position: sticky - top: 0 - background-color: white box-sizing: border-box max-height: 100% - +media-query-min($bp.s) - padding-bottom: $minimize-button-height - width: $sidebar-width - overflow-y: auto - overflow-x: hidden - +media-query($bp.s) - height: $sidebar-mobile-menu-height - width: 100% + width: 100% &-minimized overflow: visible @@ -107,36 +63,6 @@ $minimize-button-height = 5rem .drawer overflow: visible -.header - border-normal(bottom) - sidebar-transition(padding) - height: 6rem - color: $tc-deep-gray - box-sizing: border-box - display: flex - align-items: center - padding: $cs.xl $cs.l - font-weight: $fw.bold - -.mobile-header - align-items: center - +media-query-min($bp.s) - display: none - +media-query($bp.s) - border-normal(bottom) - display: flex - padding: 0 $cs.s - height: $sidebar-mobile-menu-height - - .expand-icon - margin-right: $cs.s - font-size: 2rem - color: $tc-subtle-gray - -.icon - width: 2rem - margin-right: $cs.m - .drawer background-color: white z-index: $zi.nav diff --git a/pkg/webui/components/navigation/side/story.js b/pkg/webui/components/navigation/side/story.js index c29c5b473f..baf5acf7cd 100644 --- a/pkg/webui/components/navigation/side/story.js +++ b/pkg/webui/components/navigation/side/story.js @@ -14,57 +14,44 @@ import React from 'react' -import applicationIcon from '@assets/misc/application.svg' +import SidebarContext from '@console/containers/side-bar/context' import SideNavigationItem from './item' -import { SideNavigation } from '.' +import SideNavigation from '.' export default { - title: 'Navigation', + title: 'Navigation v2', component: SideNavigation, + decorators: [ + storyFn => ( + {storyFn()} + ), + ], } -export const _SideNavigation = () => { - const header = { - title: 'test-application', - icon: applicationIcon, - iconAlt: { id: 'application', defaultMessage: 'Application' }, - to: '/', - } - - return ( -
    - - - - - - - - - - - - - - - - - - -
    - ) -} - -_SideNavigation.story = { - name: 'SideNavigation', -} +export const _SideNavigation = () => ( +
    + + + + + + + + + + + + + + + + + +
    +) diff --git a/pkg/webui/components/profile-dropdown-v2/index.js b/pkg/webui/components/profile-dropdown-v2/index.js deleted file mode 100644 index 0b557ea93f..0000000000 --- a/pkg/webui/components/profile-dropdown-v2/index.js +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright © 2023 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. - -import React, { useCallback, useRef, useState } from 'react' -import classnames from 'classnames' - -import Icon from '@ttn-lw/components/icon' -import Dropdown from '@ttn-lw/components/dropdown-v2' -import ProfilePicture from '@ttn-lw/components/profile-picture' -import Button from '@ttn-lw/components/button-v2' -import style from '@ttn-lw/components/button-v2/button.styl' - -import PropTypes from '@ttn-lw/lib/prop-types' - -import styles from './profile-dropdown-v2.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 ( - - ) -} - -ProfileDropdown.propTypes = { - brandLogo: PropTypes.shape({ - src: PropTypes.string.isRequired, - alt: PropTypes.string.isRequired, - }), - /** - * A list of items for the dropdown component. See ``'s `items` - * proptypes for details. - */ - children: PropTypes.node.isRequired, - className: PropTypes.string, - /** The profile picture of the current user. */ - profilePicture: PropTypes.profilePicture, -} - -ProfileDropdown.defaultProps = { - brandLogo: undefined, - className: undefined, - profilePicture: undefined, -} - -export default ProfileDropdown diff --git a/pkg/webui/components/profile-dropdown-v2/profile-dropdown-v2.styl b/pkg/webui/components/profile-dropdown-v2/profile-dropdown-v2.styl deleted file mode 100644 index e22504b7f5..0000000000 --- a/pkg/webui/components/profile-dropdown-v2/profile-dropdown-v2.styl +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2023 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. - -.container - padding: 0 0.25rem 0 0.5rem - position: relative - gap: 0 - -.brand-logo - height: 1.14rem - - +media-query($bp.s) - display: none - -.profile-picture - height: 1.5rem !important - width: 1.5rem !important diff --git a/pkg/webui/components/profile-dropdown-v2/story.js b/pkg/webui/components/profile-dropdown-v2/story.js deleted file mode 100644 index dfbef0d5e4..0000000000 --- a/pkg/webui/components/profile-dropdown-v2/story.js +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright © 2023 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. - -import React from 'react' - -import Dropdown from '@ttn-lw/components/dropdown-v2' -import ExampleLogo from '@ttn-lw/components/logo/story-logo-new.svg' - -import ProfileDropdown from '.' - -const handleLogout = () => { - // eslint-disable-next-line no-console - console.log('Click') -} - -export default { - title: 'Profile Dropdown V2', - component: ProfileDropdown, -} - -export const Default = () => ( -
    - - - - - -
    - - - -
    - -
    -
    -) diff --git a/pkg/webui/components/profile-dropdown/index.js b/pkg/webui/components/profile-dropdown/index.js index 1c69eee1ea..6b9046b837 100644 --- a/pkg/webui/components/profile-dropdown/index.js +++ b/pkg/webui/components/profile-dropdown/index.js @@ -18,6 +18,8 @@ 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' @@ -26,7 +28,7 @@ import styles from './profile-dropdown.styl' const ProfileDropdown = props => { const [expanded, setExpanded] = useState(false) const node = useRef(null) - const { userName, className, children, profilePicture, ...rest } = props + const { brandLogo, className, children, profilePicture, ...rest } = props const handleClickOutside = useCallback(e => { if (node.current && !node.current.contains(e.target)) { @@ -44,23 +46,33 @@ const ProfileDropdown = props => { }, [handleClickOutside]) return ( -
    - - {userName} - - {expanded && {children}} -
    +
    + {brandLogo && } + +
    + + {children} + ) } ProfileDropdown.propTypes = { + brandLogo: PropTypes.shape({ + src: PropTypes.string.isRequired, + alt: PropTypes.string.isRequired, + }), /** * A list of items for the dropdown component. See ``'s `items` * proptypes for details. @@ -69,11 +81,10 @@ ProfileDropdown.propTypes = { className: PropTypes.string, /** The profile picture of the current user. */ profilePicture: PropTypes.profilePicture, - /** The name/id of the current user. */ - userName: PropTypes.string.isRequired, } ProfileDropdown.defaultProps = { + brandLogo: undefined, className: undefined, profilePicture: undefined, } diff --git a/pkg/webui/components/profile-dropdown/profile-dropdown.styl b/pkg/webui/components/profile-dropdown/profile-dropdown.styl index af14a2d1e5..e22504b7f5 100644 --- a/pkg/webui/components/profile-dropdown/profile-dropdown.styl +++ b/pkg/webui/components/profile-dropdown/profile-dropdown.styl @@ -1,4 +1,4 @@ -// Copyright © 2019 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 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. @@ -13,37 +13,16 @@ // limitations under the License. .container - display: inline-flex - align-items: center + padding: 0 0.25rem 0 0.5rem position: relative - cursor: pointer - transition: background-color $ad.s - height: 100% - padding: 0 $cs.xl + gap: 0 - &:hover - area-hover() +.brand-logo + height: 1.14rem - +focus-visible() - background-color: $c-backdrop-lighter - - &:active, &:focus - area-active() - -.id - one-liner() - margin-right: $cs.xs - - +media-query($bp.l) + +media-query($bp.s) display: none -.dropdown - top: $header-height - - &-icon - margin-right: -.4rem - .profile-picture - height: 3rem - width: 3rem - margin-right: $cs.m + height: 1.5rem !important + width: 1.5rem !important diff --git a/pkg/webui/components/profile-dropdown/story.js b/pkg/webui/components/profile-dropdown/story.js index 1d3c30be8e..a08ba25b67 100644 --- a/pkg/webui/components/profile-dropdown/story.js +++ b/pkg/webui/components/profile-dropdown/story.js @@ -1,4 +1,4 @@ -// Copyright © 2019 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 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. @@ -15,6 +15,7 @@ import React from 'react' import Dropdown from '@ttn-lw/components/dropdown' +import ExampleLogo from '@ttn-lw/components/logo/story-logo-new.svg' import ProfileDropdown from '.' @@ -24,15 +25,27 @@ const handleLogout = () => { } export default { - title: 'Profile Dropdown', + title: 'Profile Dropdown V2', component: ProfileDropdown, } export const Default = () => ( -
    - - - +
    + + + + + +
    + + + +
    +
    ) diff --git a/pkg/webui/components/sidebar/dedicated-entity/index.js b/pkg/webui/components/sidebar/dedicated-entity/index.js index 8bf68e8eb1..9255137997 100644 --- a/pkg/webui/components/sidebar/dedicated-entity/index.js +++ b/pkg/webui/components/sidebar/dedicated-entity/index.js @@ -16,7 +16,7 @@ import React from 'react' import { NavLink } from 'react-router-dom' import classnames from 'classnames' -import Button from '@ttn-lw/components/button-v2' +import Button from '@ttn-lw/components/button' import Message from '@ttn-lw/lib/components/message' diff --git a/pkg/webui/components/sidebar/search-button/index.js b/pkg/webui/components/sidebar/search-button/index.js index 2d47a29ba3..601f622f51 100644 --- a/pkg/webui/components/sidebar/search-button/index.js +++ b/pkg/webui/components/sidebar/search-button/index.js @@ -15,7 +15,7 @@ import React, { useCallback, useContext } from 'react' import classnames from 'classnames' -import Button from '@ttn-lw/components/button-v2' +import Button from '@ttn-lw/components/button' import Icon from '@ttn-lw/components/icon' import Message from '@ttn-lw/lib/components/message' diff --git a/pkg/webui/components/sidebar/section-label/index.js b/pkg/webui/components/sidebar/section-label/index.js index 88ee21981a..a1a9fcfae8 100644 --- a/pkg/webui/components/sidebar/section-label/index.js +++ b/pkg/webui/components/sidebar/section-label/index.js @@ -15,7 +15,7 @@ import React from 'react' import classnames from 'classnames' -import Button from '@ttn-lw/components/button-v2' +import Button from '@ttn-lw/components/button' import Message from '@ttn-lw/lib/components/message' diff --git a/pkg/webui/components/sidebar/side-footer/index.js b/pkg/webui/components/sidebar/side-footer/index.js index aa85a59682..381686fdca 100644 --- a/pkg/webui/components/sidebar/side-footer/index.js +++ b/pkg/webui/components/sidebar/side-footer/index.js @@ -15,8 +15,8 @@ import React, { useCallback, useContext, useRef } from 'react' import classnames from 'classnames' -import Button from '@ttn-lw/components/button-v2' -import Dropdown from '@ttn-lw/components/dropdown-v2' +import Button from '@ttn-lw/components/button' +import Dropdown from '@ttn-lw/components/dropdown' import { LanguageContext } from '@ttn-lw/lib/components/with-locale' @@ -137,7 +137,7 @@ const SideFooter = () => { message="EU1" noDropdownIcon dropdownItems={clusterDropdownItems} - dropdownClassName={style.sideFooterDropdown} + dropdownClassName={classnames(style.sideFooterDropdown, style.sideFooterClusterDropdown)} ref={clusterButtonRef} /> )} diff --git a/pkg/webui/components/sidebar/side-footer/side-footer.styl b/pkg/webui/components/sidebar/side-footer/side-footer.styl index 76f2da9c53..82d2d74b46 100644 --- a/pkg/webui/components/sidebar/side-footer/side-footer.styl +++ b/pkg/webui/components/sidebar/side-footer/side-footer.styl @@ -26,4 +26,7 @@ right: -79px !important &-dropdown - width: 18rem + width: 18.2rem + + &-cluster-dropdown + left: -207px !important diff --git a/pkg/webui/components/sidebar/side-header/index.js b/pkg/webui/components/sidebar/side-header/index.js index d3885d1627..aa18230693 100644 --- a/pkg/webui/components/sidebar/side-header/index.js +++ b/pkg/webui/components/sidebar/side-header/index.js @@ -17,7 +17,7 @@ import classnames from 'classnames' import LAYOUT from '@ttn-lw/constants/layout' -import Button from '@ttn-lw/components/button-v2' +import Button from '@ttn-lw/components/button' import SidebarContext from '@console/containers/side-bar/context' diff --git a/pkg/webui/console/containers/header/index.js b/pkg/webui/console/containers/header/index.js index 8bbb7283fc..b26a07df9d 100644 --- a/pkg/webui/console/containers/header/index.js +++ b/pkg/webui/console/containers/header/index.js @@ -16,11 +16,10 @@ import React, { useCallback } from 'react' import { useDispatch, useSelector } from 'react-redux' import HeaderComponent from '@ttn-lw/components/header' -import NavigationBar from '@ttn-lw/components/navigation/bar' import Dropdown from '@ttn-lw/components/dropdown' -import PropTypes from '@ttn-lw/lib/prop-types' import sharedMessages from '@ttn-lw/lib/shared-messages' +import { selectAssetsRootPath, selectBrandingRootPath } from '@ttn-lw/lib/selectors/env' import selectAccountUrl from '@console/lib/selectors/app-config' import { @@ -28,7 +27,6 @@ import { mayViewApplications, mayViewGateways, mayViewOrganizationsOfUser, - mayViewOrEditApiKeys, } from '@console/lib/feature-checks' import { logout } from '@console/store/actions/logout' @@ -39,12 +37,11 @@ import Logo from '../logo' const accountUrl = selectAccountUrl() -const Header = ({ searchable, handleSearchRequest }) => { +const Header = () => { const dispatch = useDispatch() const handleLogout = useCallback(() => dispatch(logout()), [dispatch]) const user = useSelector(selectUser) - const isUserAdmin = useSelector(selectUserIsAdmin) const mayViewApps = useSelector(state => user ? checkFromState(mayViewApplications, state) : false, ) @@ -52,44 +49,28 @@ const Header = ({ searchable, handleSearchRequest }) => { const mayViewOrgs = useSelector(state => user ? checkFromState(mayViewOrganizationsOfUser, state) : false, ) - const mayHandleApiKeys = useSelector(state => - user ? checkFromState(mayViewOrEditApiKeys, state) : false, - ) + const isAdmin = useSelector(selectUserIsAdmin) - const navigation = [ - { - title: sharedMessages.overview, - icon: 'overview', - path: '', - exact: true, - hidden: !mayViewApps && !mayViewGateways, - }, - { - title: sharedMessages.applications, - icon: 'application', - path: '/applications', - hidden: !mayViewApps, - }, - { - title: sharedMessages.gateways, - icon: 'gateway', - path: '/gateways', - hidden: !mayViewGtws, - }, - { - title: sharedMessages.organizations, - icon: 'organization', - path: '/organizations', - hidden: !mayViewOrgs, - }, - ] - - const navigationEntries = ( - - {navigation.map( - ({ hidden, ...rest }) => !hidden && , + const plusDropdownItems = ( + <> + {mayViewApps && ( + )} - + {mayViewGtws && } + {mayViewOrgs && ( + + )} + + + ) const dropdownItems = ( @@ -100,54 +81,7 @@ const Header = ({ searchable, handleSearchRequest }) => { path={`${accountUrl}/profile-settings`} external /> - {mayHandleApiKeys && ( - - )} - -
    - - -
    - - - ) - - const mobileDropdownItems = ( - - {navigation.map( - ({ hidden, ...rest }) => !hidden && , - )} - -
    - -
    - {mayHandleApiKeys && ( - - )} - {isUserAdmin && ( + {isAdmin && ( { path="https://thethingsindustries.com/docs" external /> +
    +
    ) + const hasCustomBranding = selectBrandingRootPath() !== selectAssetsRootPath() + const brandLogo = hasCustomBranding + ? { + src: `${selectBrandingRootPath()}/logo.svg`, + alt: 'Logo', + } + : undefined + return ( } /> ) } -Header.propTypes = { - /** A handler for when the user used the search input. */ - handleSearchRequest: PropTypes.func, - /** A flag identifying whether the header should display the search input. */ - searchable: PropTypes.bool, -} - -Header.defaultProps = { - handleSearchRequest: () => null, - searchable: false, -} - export default Header diff --git a/pkg/webui/console/containers/side-bar/navigation/app-list-side-navigation.js b/pkg/webui/console/containers/side-bar/navigation/app-list-side-navigation.js index c2db6b7f41..00877d3b27 100644 --- a/pkg/webui/console/containers/side-bar/navigation/app-list-side-navigation.js +++ b/pkg/webui/console/containers/side-bar/navigation/app-list-side-navigation.js @@ -15,7 +15,7 @@ import React, { useContext } from 'react' import SectionLabel from '@ttn-lw/components/sidebar/section-label' -import SideNavigation from '@ttn-lw/components/navigation/side-v2' +import SideNavigation from '@ttn-lw/components/navigation/side' import sharedMessages from '@ttn-lw/lib/shared-messages' diff --git a/pkg/webui/console/containers/side-bar/navigation/app-side-navigation.js b/pkg/webui/console/containers/side-bar/navigation/app-side-navigation.js index 1a6cc78888..a5f4015354 100644 --- a/pkg/webui/console/containers/side-bar/navigation/app-side-navigation.js +++ b/pkg/webui/console/containers/side-bar/navigation/app-side-navigation.js @@ -16,7 +16,7 @@ import React, { useContext } from 'react' import { useSelector } from 'react-redux' import { defineMessages } from 'react-intl' -import SideNavigation from '@ttn-lw/components/navigation/side-v2' +import SideNavigation from '@ttn-lw/components/navigation/side' import DedicatedEntity from '@ttn-lw/components/sidebar/dedicated-entity' import sharedMessages from '@ttn-lw/lib/shared-messages' diff --git a/pkg/webui/console/containers/side-bar/navigation/general-side-navigation.js b/pkg/webui/console/containers/side-bar/navigation/general-side-navigation.js index aa10207fa7..4968245e95 100644 --- a/pkg/webui/console/containers/side-bar/navigation/general-side-navigation.js +++ b/pkg/webui/console/containers/side-bar/navigation/general-side-navigation.js @@ -15,7 +15,7 @@ import React, { useContext } from 'react' import { useSelector } from 'react-redux' -import SideNavigation from '@ttn-lw/components/navigation/side-v2' +import SideNavigation from '@ttn-lw/components/navigation/side' import SectionLabel from '@ttn-lw/components/sidebar/section-label' import sharedMessages from '@ttn-lw/lib/shared-messages' diff --git a/pkg/webui/console/containers/side-bar/navigation/gtw-list-side-navigation.js b/pkg/webui/console/containers/side-bar/navigation/gtw-list-side-navigation.js index 39c300ce78..7442db0ceb 100644 --- a/pkg/webui/console/containers/side-bar/navigation/gtw-list-side-navigation.js +++ b/pkg/webui/console/containers/side-bar/navigation/gtw-list-side-navigation.js @@ -15,7 +15,7 @@ import React, { useContext } from 'react' import SectionLabel from '@ttn-lw/components/sidebar/section-label' -import SideNavigation from '@ttn-lw/components/navigation/side-v2' +import SideNavigation from '@ttn-lw/components/navigation/side' import sharedMessages from '@ttn-lw/lib/shared-messages' diff --git a/pkg/webui/console/containers/side-bar/navigation/gtw-side-navigation.js b/pkg/webui/console/containers/side-bar/navigation/gtw-side-navigation.js index 875ad38f2b..039f2b084c 100644 --- a/pkg/webui/console/containers/side-bar/navigation/gtw-side-navigation.js +++ b/pkg/webui/console/containers/side-bar/navigation/gtw-side-navigation.js @@ -16,7 +16,7 @@ import React, { useContext } from 'react' import { useSelector } from 'react-redux' import { defineMessages } from 'react-intl' -import SideNavigation from '@ttn-lw/components/navigation/side-v2' +import SideNavigation from '@ttn-lw/components/navigation/side' import DedicatedEntity from '@ttn-lw/components/sidebar/dedicated-entity' import sharedMessages from '@ttn-lw/lib/shared-messages' diff --git a/pkg/webui/console/containers/side-bar/navigation/index.js b/pkg/webui/console/containers/side-bar/navigation/index.js index 0adc2211c9..416b78dc01 100644 --- a/pkg/webui/console/containers/side-bar/navigation/index.js +++ b/pkg/webui/console/containers/side-bar/navigation/index.js @@ -27,14 +27,20 @@ const SidebarNavigation = () => { const showGeneralSideNavigation = !pathname.includes('/applications') && !pathname.includes('/gateways') - const showAppSideNavigation = pathname.includes('/applications/') + const showSingleAppSideNavigation = pathname.match( + new RegExp('/applications/[a-z0-9](?:[-]?[a-z0-9]){1,}'), + ) + + const showSingleGatewaySideNavigation = pathname.match( + new RegExp('/gateways/[a-z0-9](?:[-]?[a-z0-9]){1,}'), + ) return (
    {showGeneralSideNavigation && } - {showAppSideNavigation && } + {showSingleAppSideNavigation && } {pathname.includes('/applications') && } - {pathname.includes('/gateways/') && } + {showSingleGatewaySideNavigation && } {pathname.includes('/gateways') && }
    ) diff --git a/pkg/webui/console/containers/side-bar/side-bar.styl b/pkg/webui/console/containers/side-bar/side-bar.styl index 966d0ae22d..5fccfb397f 100644 --- a/pkg/webui/console/containers/side-bar/side-bar.styl +++ b/pkg/webui/console/containers/side-bar/side-bar.styl @@ -18,6 +18,7 @@ font-size: $fs.s height: calc(100vh - 1.5rem) min-width: 20rem + width: 20rem sidebar-transition(min-width) +media-query($bp.m) sidebar-transition(left, box-shadow) diff --git a/pkg/webui/console/containers/side-bar/story.js b/pkg/webui/console/containers/side-bar/story.js index 908e55400d..7d64118f2f 100644 --- a/pkg/webui/console/containers/side-bar/story.js +++ b/pkg/webui/console/containers/side-bar/story.js @@ -18,7 +18,7 @@ import classnames from 'classnames' import TtsLogo from '@assets/static/tts-logo.svg' import Switcher from '@ttn-lw/components/sidebar/switcher' -import SideNavigation from '@ttn-lw/components/navigation/side-v2' +import SideNavigation from '@ttn-lw/components/navigation/side' import SideHeader from '@ttn-lw/components/sidebar/side-header' import SearchButton from '@ttn-lw/components/sidebar/search-button' import SideFooter from '@ttn-lw/components/sidebar/side-footer' diff --git a/pkg/webui/console/views/app/app.styl b/pkg/webui/console/views/app/app.styl index 89f16356e1..48e4510b14 100644 --- a/pkg/webui/console/views/app/app.styl +++ b/pkg/webui/console/views/app/app.styl @@ -12,27 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -.main - position: relative - display: flex - flex-direction: column - +media-query-min($bp.s) - min-height: "calc(100vh - %s - %s)" % ($header-height $footer-height) - +media-query($bp.s) - min-height: "calc(100vh - %s - %s)" % ($header-height-mobile $footer-height) - +media-query($bp.xs) - min-height: "calc(100vh - %s - %s)" % ($header-height-mobile $footer-height * 2) - -global-sidebar() - .content position: relative - flex: 1 display: flex flex-direction: column .stage - flex: 1 &:not(.stage-flex) padding-bottom: $ls.xxs &-flex diff --git a/pkg/webui/console/views/app/index.js b/pkg/webui/console/views/app/index.js index f2d8c6819c..b140007f20 100644 --- a/pkg/webui/console/views/app/index.js +++ b/pkg/webui/console/views/app/index.js @@ -25,9 +25,6 @@ import { import classnames from 'classnames' import { ToastContainer } from '@ttn-lw/components/toast' -import sidebarStyle from '@ttn-lw/components/navigation/side/side.styl' - -import Footer from '@ttn-lw/containers/footer' import GenericNotFound from '@ttn-lw/lib/components/full-view-error/not-found' import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' @@ -37,6 +34,7 @@ import FullViewError, { FullViewErrorInner } from '@ttn-lw/lib/components/full-v import Header from '@console/containers/header' import LogBackInModal from '@console/containers/log-back-in-modal' +import Sidebar from '@console/containers/side-bar' import Overview from '@console/views/overview' import Applications from '@console/views/applications' @@ -125,33 +123,36 @@ const Layout = () => { <> -
    +
    diff --git a/pkg/webui/console/views/application/index.js b/pkg/webui/console/views/application/index.js index 7e876d2a1e..e873db883d 100644 --- a/pkg/webui/console/views/application/index.js +++ b/pkg/webui/console/views/application/index.js @@ -16,12 +16,8 @@ import React, { useEffect } from 'react' import { Routes, Route, useParams } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' -import applicationIcon from '@assets/misc/application.svg' - import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' -import SideNavigation from '@ttn-lw/components/navigation/side' import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' -import Breadcrumbs from '@ttn-lw/components/breadcrumbs' import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' import RequireRequest from '@ttn-lw/lib/components/require-request' @@ -41,22 +37,9 @@ import ApplicationIntegrationsMqtt from '@console/views/application-integrations import ApplicationIntegrationsLoRaCloud from '@console/views/application-integrations-lora-cloud' import Devices from '@console/views/devices' -import sharedMessages from '@ttn-lw/lib/shared-messages' import { selectApplicationSiteName } from '@ttn-lw/lib/selectors/env' -import { - mayViewApplicationInfo, - mayViewApplicationEvents, - maySetApplicationPayloadFormatters, - mayViewApplicationDevices, - mayCreateOrEditApplicationIntegrations, - mayEditBasicApplicationInfo, - mayViewOrEditApplicationApiKeys, - mayViewOrEditApplicationCollaborators, - mayViewOrEditApplicationPackages, - mayAddPubSubIntegrations, - mayViewApplications, -} from '@console/lib/feature-checks' +import { mayAddPubSubIntegrations, mayViewApplications } from '@console/lib/feature-checks' import { getApplication, @@ -65,10 +48,7 @@ import { } from '@console/store/actions/applications' import { getAsConfiguration } from '@console/store/actions/application-server' -import { - selectApplicationRights, - selectSelectedApplication, -} from '@console/store/selectors/applications' +import { selectSelectedApplication } from '@console/store/selectors/applications' import { selectMqttProviderDisabled, selectNatsProviderDisabled, @@ -102,7 +82,6 @@ const ApplicationInner = () => { const { appId } = useParams() const application = useSelector(selectSelectedApplication) const name = application.name || appId - const rights = useSelector(selectApplicationRights) const siteName = selectApplicationSiteName() const natsDisabled = useSelector(selectNatsProviderDisabled) const mqttDisabled = useSelector(selectMqttProviderDisabled) @@ -115,85 +94,7 @@ const ApplicationInner = () => { return ( <> - - - {mayViewApplicationInfo.check(rights) && ( - - )} - {mayViewApplicationDevices.check(rights) && ( - - )} - {mayViewApplicationEvents.check(rights) && ( - - )} - {maySetApplicationPayloadFormatters.check(rights) && ( - - - - - )} - {mayCreateOrEditApplicationIntegrations.check(rights) && ( - - - - {mayAddPubSubIntegrations.check(natsDisabled, mqttDisabled) && ( - - )} - {mayViewOrEditApplicationPackages.check(rights) && ( - - )} - - )} - {mayViewOrEditApplicationCollaborators.check(rights) && ( - - )} - {mayViewOrEditApplicationApiKeys.check(rights) && ( - - )} - {mayEditBasicApplicationInfo.check(rights) && ( - - )} - diff --git a/pkg/webui/console/views/applications-list/index.js b/pkg/webui/console/views/applications-list/index.js index d0a6204937..c1b22146b6 100644 --- a/pkg/webui/console/views/applications-list/index.js +++ b/pkg/webui/console/views/applications-list/index.js @@ -15,21 +15,30 @@ import React from 'react' import { Row, Col, Container } from 'react-grid-system' +import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' +import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' + import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' import ApplicationsTable from '@console/containers/applications-table' import sharedMessages from '@ttn-lw/lib/shared-messages' -const ApplicationsList = () => ( - - - - - - - - -) +const ApplicationsList = () => { + useBreadcrumbs('apps.list', ) + + return ( + <> + + + + + + + + + + ) +} export default ApplicationsList diff --git a/pkg/webui/console/views/applications/index.js b/pkg/webui/console/views/applications/index.js index a22737072a..d05c6bb22c 100644 --- a/pkg/webui/console/views/applications/index.js +++ b/pkg/webui/console/views/applications/index.js @@ -16,6 +16,7 @@ import React from 'react' import { Routes, Route } from 'react-router-dom' import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' +import Breadcrumbs from '@ttn-lw/components/breadcrumbs' import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' import GenericNotFound from '@ttn-lw/lib/components/full-view-error/not-found' @@ -37,6 +38,7 @@ const Applications = () => { return ( + diff --git a/pkg/webui/console/views/gateway/index.js b/pkg/webui/console/views/gateway/index.js index 535e25d252..9e9eb5139a 100644 --- a/pkg/webui/console/views/gateway/index.js +++ b/pkg/webui/console/views/gateway/index.js @@ -16,12 +16,8 @@ import React, { useCallback, useEffect } from 'react' import { Routes, Route, useParams } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' -import gatewayIcon from '@assets/misc/gateway.svg' - import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' -import Breadcrumbs from '@ttn-lw/components/breadcrumbs' -import SideNavigation from '@ttn-lw/components/navigation/side' import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' import GenericNotFound from '@ttn-lw/lib/components/full-view-error/not-found' @@ -35,25 +31,15 @@ import GatewayApiKeys from '@console/views/gateway-api-keys' import GatewayOverview from '@console/views/gateway-overview' import attachPromise from '@ttn-lw/lib/store/actions/attach-promise' -import sharedMessages from '@ttn-lw/lib/shared-messages' import { selectApplicationSiteName } from '@ttn-lw/lib/selectors/env' -import { - mayViewGatewayInfo, - mayViewGatewayEvents, - mayViewOrEditGatewayLocation, - mayViewOrEditGatewayCollaborators, - mayViewOrEditGatewayApiKeys, - mayEditBasicGatewayInformation, -} from '@console/lib/feature-checks' - import { getGateway, stopGatewayEventsStream, getGatewaysRightsList, } from '@console/store/actions/gateways' -import { selectSelectedGateway, selectGatewayRights } from '@console/store/selectors/gateways' +import { selectSelectedGateway } from '@console/store/selectors/gateways' const Gateway = () => { const { gtwId } = useParams() @@ -104,7 +90,6 @@ const Gateway = () => { const GatewayInner = () => { const { gtwId } = useParams() const gateway = useSelector(selectSelectedGateway) - const rights = useSelector(selectGatewayRights) const gatewayName = gateway?.name || gtwId @@ -112,43 +97,7 @@ const GatewayInner = () => { return ( <> - - - {mayViewGatewayInfo.check(rights) && ( - - )} - {mayViewGatewayEvents.check(rights) && ( - - )} - {mayViewOrEditGatewayLocation.check(rights) && ( - - )} - {mayViewOrEditGatewayCollaborators.check(rights) && ( - - )} - {mayViewOrEditGatewayApiKeys.check(rights) && ( - - )} - {mayEditBasicGatewayInformation.check(rights) && ( - - )} - diff --git a/pkg/webui/console/views/gateways-list/index.js b/pkg/webui/console/views/gateways-list/index.js index 1f8c7e10ef..3663d16265 100644 --- a/pkg/webui/console/views/gateways-list/index.js +++ b/pkg/webui/console/views/gateways-list/index.js @@ -15,21 +15,28 @@ import React from 'react' import { Container, Row, Col } from 'react-grid-system' +import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' +import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' + import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' import GatewaysTable from '@console/containers/gateways-table' import sharedMessages from '@ttn-lw/lib/shared-messages' -const GatewaysList = () => ( - - - - - - - - -) +const GatewaysList = () => { + useBreadcrumbs('gtws.list', ) + + return ( + + + + + + + + + ) +} export default GatewaysList diff --git a/pkg/webui/console/views/gateways/index.js b/pkg/webui/console/views/gateways/index.js index 64878d4a8e..0e79168e57 100644 --- a/pkg/webui/console/views/gateways/index.js +++ b/pkg/webui/console/views/gateways/index.js @@ -17,6 +17,7 @@ import { Routes, Route } from 'react-router-dom' import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' +import Breadcrumbs from '@ttn-lw/components/breadcrumbs' import ValidateRouteParam from '@ttn-lw/lib/components/validate-route-param' @@ -35,6 +36,7 @@ const Gateways = () => { useBreadcrumbs('gtws', ) return ( + diff --git a/pkg/webui/console/views/organization/index.js b/pkg/webui/console/views/organization/index.js index 3ae3b0a883..5b055bcea4 100644 --- a/pkg/webui/console/views/organization/index.js +++ b/pkg/webui/console/views/organization/index.js @@ -16,12 +16,8 @@ import React, { useEffect } from 'react' import { Routes, Route, useParams } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' -import organizationIcon from '@assets/misc/organization.svg' - import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' -import Breadcrumbs from '@ttn-lw/components/breadcrumbs' -import SideNavigation from '@ttn-lw/components/navigation/side' import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' import GenericNotFound from '@ttn-lw/lib/components/full-view-error/not-found' @@ -35,17 +31,9 @@ import OrganizationGeneralSettings from '@console/views/organization-general-set import OrganizationApiKeys from '@console/views/organization-api-keys' import OrganizationCollaborators from '@console/views/organization-collaborators' -import sharedMessages from '@ttn-lw/lib/shared-messages' import { selectApplicationSiteName } from '@ttn-lw/lib/selectors/env' -import { - checkFromState, - mayViewOrganizationInformation, - mayViewOrEditOrganizationApiKeys, - mayViewOrEditOrganizationCollaborators, - mayEditBasicOrganizationInformation, - mayViewOrganizationsOfUser, -} from '@console/lib/feature-checks' +import { mayViewOrganizationsOfUser } from '@console/lib/feature-checks' import { getOrganization, @@ -82,15 +70,6 @@ const OrganizationInner = () => { const name = organization.name || orgId const dispatch = useDispatch() const siteName = selectApplicationSiteName() - const mayViewOrEditApiKeys = useSelector(state => - checkFromState(mayViewOrEditOrganizationApiKeys, state), - ) - const mayViewOrEditCollaborators = useSelector(state => - checkFromState(mayViewOrEditOrganizationCollaborators, state), - ) - const mayEditInformation = useSelector(state => - checkFromState(mayEditBasicOrganizationInformation, state), - ) useBreadcrumbs('orgs.single', ) @@ -103,38 +82,7 @@ const OrganizationInner = () => { return ( - - - {mayViewOrganizationInformation && ( - - )} - - {mayViewOrEditCollaborators && ( - - )} - {mayViewOrEditApiKeys && ( - - )} - {mayEditInformation && ( - - )} - diff --git a/pkg/webui/console/views/organizations-list/index.js b/pkg/webui/console/views/organizations-list/index.js index e0a9581f0c..a19ccdfc80 100644 --- a/pkg/webui/console/views/organizations-list/index.js +++ b/pkg/webui/console/views/organizations-list/index.js @@ -17,21 +17,28 @@ import { Row, Col, Container } from 'react-grid-system' import PAGE_SIZES from '@ttn-lw/constants/page-sizes' +import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' +import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' + import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' import OrganizationsTable from '@console/containers/organizations-table' import sharedMessages from '@ttn-lw/lib/shared-messages' -const List = () => ( - - - - - - - - -) +const List = () => { + useBreadcrumbs('orgs.list', ) + + return ( + + + + + + + + + ) +} export default List diff --git a/pkg/webui/console/views/organizations/index.js b/pkg/webui/console/views/organizations/index.js index 8dc902cc56..50982adfa3 100644 --- a/pkg/webui/console/views/organizations/index.js +++ b/pkg/webui/console/views/organizations/index.js @@ -17,6 +17,7 @@ import { Routes, Route } from 'react-router-dom' import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb' import { useBreadcrumbs } from '@ttn-lw/components/breadcrumbs/context' +import Breadcrumbs from '@ttn-lw/components/breadcrumbs' import GenericNotFound from '@ttn-lw/lib/components/full-view-error/not-found' import ValidateRouteParam from '@ttn-lw/lib/components/validate-route-param' @@ -40,6 +41,7 @@ const Organizations = () => { return ( + diff --git a/pkg/webui/console/views/overview/index.js b/pkg/webui/console/views/overview/index.js index 91e4a59ab7..46fb48e733 100644 --- a/pkg/webui/console/views/overview/index.js +++ b/pkg/webui/console/views/overview/index.js @@ -180,7 +180,6 @@ const Overview = () => { - {chooser}
    diff --git a/pkg/webui/lib/shared-messages.js b/pkg/webui/lib/shared-messages.js index 738e3cf637..58b2fb48da 100644 --- a/pkg/webui/lib/shared-messages.js +++ b/pkg/webui/lib/shared-messages.js @@ -307,6 +307,7 @@ export default defineMessages({ 'The Authentication Key for Lora Basics Station LNS connections. This field is ignored for other gateways.', link: 'Link', linked: 'Linked', + list: 'List', liveData: 'Live data', location: 'Location', locationDescription: diff --git a/pkg/webui/locales/en.json b/pkg/webui/locales/en.json index 855eaf765b..405156007f 100644 --- a/pkg/webui/locales/en.json +++ b/pkg/webui/locales/en.json @@ -1246,6 +1246,7 @@ "lib.shared-messages.lbsLNSSecretDescription": "The Authentication Key for Lora Basics Station LNS connections. This field is ignored for other gateways.", "lib.shared-messages.link": "Link", "lib.shared-messages.linked": "Linked", + "lib.shared-messages.list": "List", "lib.shared-messages.liveData": "Live data", "lib.shared-messages.location": "Location", "lib.shared-messages.locationDescription": "When set to public, the gateway location may be visible to other users of the network", diff --git a/pkg/webui/locales/ja.json b/pkg/webui/locales/ja.json index 402aa056e5..4f21652658 100644 --- a/pkg/webui/locales/ja.json +++ b/pkg/webui/locales/ja.json @@ -1246,6 +1246,7 @@ "lib.shared-messages.lbsLNSSecretDescription": "LoRa Basics Station LNS 接続のための認証キーです。このフィールドは、他のゲートウェイでは無視されます", "lib.shared-messages.link": "リンク", "lib.shared-messages.linked": "リンク済", + "lib.shared-messages.list": "", "lib.shared-messages.liveData": "ライブデータ", "lib.shared-messages.location": "場所", "lib.shared-messages.locationDescription": "公開に設定すると、ゲートウェイの位置がネットワークの他のユーザーから見えることがあります",