[web] Add icon menus in the Sidebar (#22318)
* Add sidebar icons and menus (Help, Account) * Place the account dropdown menu above the tooltip * Hide tooltip when dropdown menu is shown * Fixup tooltip position * Add Digital Science link * Add aria-labels on the dropdown toggles * Update sidebar items spacings - Add space between notification and DS icons - Balance margin/padding in the scrollable part * Revert changing the side of .dev-tool-bar-open-button * Remove `!important` in link color * Add Help tooltip * Revert dropdown-menu z-index change * Move `AccountMenuItems` to its own file * Revert "Remove `!important` in link color" This reverts commit b2de83f815d05cd1e224604d4ca355986ba76b99. GitOrigin-RevId: 286266ea5348384f2592f748d3ae32c452558988
This commit is contained in:
+89
-5
@@ -1,15 +1,36 @@
|
||||
import { useState } from 'react'
|
||||
import NewProjectButton from '../new-project-button'
|
||||
import SidebarFilters from './sidebar-filters'
|
||||
import AddAffiliation, { useAddAffiliation } from '../add-affiliation'
|
||||
import SurveyWidget from '../survey-widget'
|
||||
import { usePersistedResize } from '../../../../shared/hooks/use-resize'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { Dropdown } from 'react-bootstrap-5'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { NavDropdownMenuItems } from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-from-data'
|
||||
import { NavbarDropdownItemData } from '@/features/ui/components/types/navbar'
|
||||
import { useContactUsModal } from '@/shared/hooks/use-contact-us-modal'
|
||||
import { UserProvider } from '@/shared/context/user-context'
|
||||
import { AccountMenuItems } from '@/features/ui/components/bootstrap-5/navbar/account-menu-items'
|
||||
|
||||
function SidebarDsNav() {
|
||||
const { t } = useTranslation()
|
||||
const [showAccountDropdown, setShowAccountDropdown] = useState(false)
|
||||
const [showHelpDropdown, setShowHelpDropdown] = useState(false)
|
||||
const { showModal: showContactUsModal, modal: contactUsModal } =
|
||||
useContactUsModal({
|
||||
autofillProjectUrl: false,
|
||||
})
|
||||
const { show: showAddAffiliationWidget } = useAddAffiliation()
|
||||
const { mousePos, getHandleProps, getTargetProps } = usePersistedResize({
|
||||
name: 'project-sidebar',
|
||||
})
|
||||
|
||||
const { sessionUser, showSubscriptionLink, items } = getMeta('ol-navbar')
|
||||
const helpItem = items.find(
|
||||
item => item.text === 'help'
|
||||
) as NavbarDropdownItemData
|
||||
return (
|
||||
<div
|
||||
className="project-list-sidebar-wrapper-react d-none d-md-flex"
|
||||
@@ -28,10 +49,73 @@ function SidebarDsNav() {
|
||||
<div className="project-list-sidebar-survey-wrapper">
|
||||
<SurveyWidget variant="light" />
|
||||
</div>
|
||||
<div className="bg-warning">
|
||||
Help / Profile
|
||||
<br />
|
||||
DS Nav
|
||||
<div className="d-flex gap-3 mb-2">
|
||||
{helpItem && (
|
||||
<Dropdown
|
||||
className="ds-nav-icon-dropdown"
|
||||
onToggle={show => setShowHelpDropdown(show)}
|
||||
>
|
||||
<Dropdown.Toggle role="menuitem" aria-label={t('help')}>
|
||||
<OLTooltip
|
||||
description={t('help')}
|
||||
id="help-icon"
|
||||
overlayProps={{
|
||||
placement: 'top',
|
||||
show: showHelpDropdown ? false : undefined,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<MaterialIcon type="help" size="2x" />
|
||||
</div>
|
||||
</OLTooltip>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as="ul" role="menu" align="end">
|
||||
<NavDropdownMenuItems
|
||||
dropdown={helpItem.dropdown}
|
||||
showContactUsModal={showContactUsModal}
|
||||
/>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
)}
|
||||
{sessionUser && (
|
||||
<>
|
||||
<Dropdown
|
||||
className="ds-nav-icon-dropdown"
|
||||
onToggle={show => setShowAccountDropdown(show)}
|
||||
>
|
||||
<Dropdown.Toggle role="menuitem" aria-label={t('Account')}>
|
||||
<OLTooltip
|
||||
description={t('Account')}
|
||||
id="open-account"
|
||||
overlayProps={{
|
||||
placement: 'top',
|
||||
show: showAccountDropdown ? false : undefined,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<MaterialIcon type="person" size="2x" />
|
||||
</div>
|
||||
</OLTooltip>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as="ul" role="menu" align="end">
|
||||
<AccountMenuItems
|
||||
sessionUser={sessionUser}
|
||||
showSubscriptionLink={showSubscriptionLink}
|
||||
/>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<UserProvider>{contactUsModal}</UserProvider>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="ds-nav-ds-link">
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://www.digital-science.com/"
|
||||
rel="noopener"
|
||||
>
|
||||
Digital Science
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
{...getHandleProps({
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { Dropdown } from 'react-bootstrap-5'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import getMeta from '@/utils/meta'
|
||||
import type { NavbarSessionUser } from '@/features/ui/components/types/navbar'
|
||||
import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item'
|
||||
import NavDropdownDivider from './nav-dropdown-divider'
|
||||
import NavDropdownLinkItem from './nav-dropdown-link-item'
|
||||
|
||||
export function AccountMenuItems({
|
||||
sessionUser,
|
||||
showSubscriptionLink,
|
||||
}: {
|
||||
sessionUser: NavbarSessionUser
|
||||
showSubscriptionLink: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const logOutFormId = 'logOutForm'
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown.Item as="li" disabled role="menuitem">
|
||||
{sessionUser.email}
|
||||
</Dropdown.Item>
|
||||
<NavDropdownDivider />
|
||||
<NavDropdownLinkItem href="/user/settings">
|
||||
{t('Account Settings')}
|
||||
</NavDropdownLinkItem>
|
||||
{showSubscriptionLink ? (
|
||||
<NavDropdownLinkItem href="/user/subscription">
|
||||
{t('subscription')}
|
||||
</NavDropdownLinkItem>
|
||||
) : null}
|
||||
<NavDropdownDivider />
|
||||
<DropdownListItem>
|
||||
{
|
||||
// The button is outside the form but still belongs to it via the
|
||||
// form attribute. The reason to do this is that if the button is
|
||||
// inside the form, screen readers will not count it in the total
|
||||
// number of menu items
|
||||
}
|
||||
<Dropdown.Item
|
||||
as="button"
|
||||
type="submit"
|
||||
form={logOutFormId}
|
||||
role="menuitem"
|
||||
>
|
||||
{t('log_out')}
|
||||
</Dropdown.Item>
|
||||
<form id={logOutFormId} method="POST" action="/logout">
|
||||
<input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
|
||||
</form>
|
||||
</DropdownListItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
+5
-39
@@ -1,12 +1,8 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dropdown } from 'react-bootstrap-5'
|
||||
import NavDropdownDivider from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-divider'
|
||||
import getMeta from '@/utils/meta'
|
||||
import NavDropdownMenu from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-menu'
|
||||
import NavDropdownLinkItem from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-link-item'
|
||||
import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item'
|
||||
import type { NavbarSessionUser } from '@/features/ui/components/types/navbar'
|
||||
import NavLinkItem from '@/features/ui/components/bootstrap-5/navbar/nav-link-item'
|
||||
import { AccountMenuItems } from './account-menu-items'
|
||||
|
||||
export default function LoggedInItems({
|
||||
sessionUser,
|
||||
@@ -16,44 +12,14 @@ export default function LoggedInItems({
|
||||
showSubscriptionLink: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const logOutFormId = 'logOutForm'
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavLinkItem href="/project">{t('projects')}</NavLinkItem>
|
||||
<NavDropdownMenu title={t('Account')}>
|
||||
<Dropdown.Item as="li" disabled role="menuitem">
|
||||
{sessionUser.email}
|
||||
</Dropdown.Item>
|
||||
<NavDropdownDivider />
|
||||
<NavDropdownLinkItem href="/user/settings">
|
||||
{t('Account Settings')}
|
||||
</NavDropdownLinkItem>
|
||||
{showSubscriptionLink ? (
|
||||
<NavDropdownLinkItem href="/user/subscription">
|
||||
{t('subscription')}
|
||||
</NavDropdownLinkItem>
|
||||
) : null}
|
||||
<NavDropdownDivider />
|
||||
<DropdownListItem>
|
||||
{
|
||||
// The button is outside the form but still belongs to it via the
|
||||
// form attribute. The reason to do this is that if the button is
|
||||
// inside the form, screen readers will not count it in the total
|
||||
// number of menu items
|
||||
}
|
||||
<Dropdown.Item
|
||||
as="button"
|
||||
type="submit"
|
||||
form={logOutFormId}
|
||||
role="menuitem"
|
||||
>
|
||||
{t('log_out')}
|
||||
</Dropdown.Item>
|
||||
<form id={logOutFormId} method="POST" action="/logout">
|
||||
<input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
|
||||
</form>
|
||||
</DropdownListItem>
|
||||
<AccountMenuItems
|
||||
sessionUser={sessionUser}
|
||||
showSubscriptionLink={showSubscriptionLink}
|
||||
/>
|
||||
</NavDropdownMenu>
|
||||
</>
|
||||
)
|
||||
|
||||
+23
-3
@@ -1,4 +1,7 @@
|
||||
import type { NavbarDropdownItemData } from '@/features/ui/components/types/navbar'
|
||||
import type {
|
||||
NavbarDropdownItemData,
|
||||
NavbarItemDropdownData,
|
||||
} from '@/features/ui/components/types/navbar'
|
||||
import NavDropdownDivider from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-divider'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import { isDropdownLinkItem } from '@/features/ui/components/bootstrap-5/navbar/util'
|
||||
@@ -16,7 +19,24 @@ export default function NavDropdownFromData({
|
||||
}) {
|
||||
return (
|
||||
<NavDropdownMenu title={item.translatedText} className={item.class}>
|
||||
{item.dropdown.map((child, index) => {
|
||||
<NavDropdownMenuItems
|
||||
dropdown={item.dropdown}
|
||||
showContactUsModal={showContactUsModal}
|
||||
/>
|
||||
</NavDropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
export function NavDropdownMenuItems({
|
||||
dropdown,
|
||||
showContactUsModal,
|
||||
}: {
|
||||
dropdown: NavbarItemDropdownData
|
||||
showContactUsModal: (event?: Event) => void
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{dropdown.map((child, index) => {
|
||||
if ('divider' in child) {
|
||||
return <NavDropdownDivider key={index} />
|
||||
} else if ('isContactUs' in child) {
|
||||
@@ -41,6 +61,6 @@ export default function NavDropdownFromData({
|
||||
)
|
||||
}
|
||||
})}
|
||||
</NavDropdownMenu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
display: flex;
|
||||
overflow-y: hidden;
|
||||
|
||||
.new-project-button {
|
||||
margin-bottom: var(--spacing-08);
|
||||
}
|
||||
|
||||
.project-list-sidebar-wrapper-react {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -41,8 +37,8 @@
|
||||
.project-list-sidebar-scroll {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
margin: 0 calc(var(--spacing-07) * -1);
|
||||
padding: 0 var(--spacing-07);
|
||||
margin: var(--spacing-05) calc(var(--spacing-07) * -1);
|
||||
padding: var(--spacing-05) var(--spacing-07);
|
||||
}
|
||||
|
||||
.project-list-sidebar-survey-link {
|
||||
@@ -59,9 +55,15 @@
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.project-list-sidebar-survey-wrapper .user-notifications {
|
||||
margin-bottom: var(--spacing-05);
|
||||
}
|
||||
}
|
||||
|
||||
ul.project-list-filters {
|
||||
margin-bottom: 0;
|
||||
|
||||
hr {
|
||||
margin: var(--spacing-05) 0;
|
||||
}
|
||||
@@ -143,8 +145,51 @@
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-list-sidebar-survey-wrapper {
|
||||
margin-top: var(--spacing-05);
|
||||
.ds-nav-icon-dropdown {
|
||||
.dropdown-toggle {
|
||||
color: var(--content-secondary);
|
||||
background: none;
|
||||
height: 44px;
|
||||
width: 44px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-light-secondary);
|
||||
}
|
||||
|
||||
&.show {
|
||||
background-color: var(--bg-accent-03);
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> div {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.material-symbols {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ds-nav-ds-link {
|
||||
margin-bottom: var(--spacing-05);
|
||||
|
||||
a {
|
||||
color: var(--content-secondary) !important;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
|
||||
@include body-xs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user