[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:
Antoine Clausse
2024-12-12 09:06:05 +00:00
committed by Copybot
parent 650d285105
commit 62cfb68c1b
5 changed files with 226 additions and 56 deletions
@@ -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({
@@ -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>
</>
)
}
@@ -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>
</>
)
@@ -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;
}
}
}