Merge pull request #18996 from overleaf/td-bs5-nav-react

Main navigation React component

GitOrigin-RevId: c99a4b4a2f6fd02618689f829681118b2b64aa8d
This commit is contained in:
Tim Down 2024-08-21 11:33:17 +01:00 committed by Copybot
parent 2296287e61
commit d5643d53b3
33 changed files with 669 additions and 15 deletions

View file

@ -227,6 +227,26 @@ module.exports = function (webRouter, privateApiRouter, publicApiRouter) {
webRouter.use(function (req, res, next) {
res.locals.translate = req.i18n.translate
const addTranslatedTextDeep = obj => {
if (_.isObject(obj)) {
if (_.has(obj, 'text')) {
obj.translatedText = req.i18n.translate(obj.text)
}
_.forOwn(obj, value => {
addTranslatedTextDeep(value)
})
}
}
// This function is used to add translations from the server for main
// navigation items because it's tricky to get them in the front end
// otherwise.
res.locals.cloneAndTranslateText = obj => {
const clone = _.cloneDeep(obj)
addTranslatedTextDeep(clone)
return clone
}
// Don't include the query string parameters, otherwise Google
// treats ?nocdn=true as the canonical version
try {

View file

@ -3,7 +3,7 @@ mixin nav-item
block
mixin nav-link
a(role="menuitem")&attributes(attributes)
a(role="menuitem").nav-link&attributes(attributes)
block
mixin dropdown-menu

View file

@ -0,0 +1,61 @@
//- This is used for pages that are migrated to Bootstrap 5 but don't use Bootstrap's own JS, instead using
//- react-bootstrap for all Bootstrap components
extends ./layout-base
include ./_mixins/formMessages
include ./_mixins/bootstrap_js
block entrypointVar
- entrypoint = 'marketing'
block append meta
if bootstrapVersion === 5
- const canDisplayAdminMenu = hasAdminAccess()
- const canDisplayAdminRedirect = canRedirectToAdminDomain()
- const sessionUser = getSessionUser()
- const staffAccess = sessionUser?.staffAccess
- const canDisplaySplitTestMenu = hasFeature('saas') && (canDisplayAdminMenu || staffAccess?.splitTestMetrics || staffAccess?.splitTestManagement)
- const canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
- const enableUpgradeButton = projectDashboardReact && usersBestSubscription && usersBestSubscription.type === 'free'
meta(name="ol-navbar" data-type="json" content={
customLogo: settings.nav.custom_logo,
title: nav.title,
canDisplayAdminMenu,
canDisplayAdminRedirect,
canDisplaySplitTestMenu,
canDisplaySurveyMenu,
enableUpgradeButton,
suppressNavbarRight: !!suppressNavbarRight,
suppressNavContentLinks: !!suppressNavContentLinks,
showSubscriptionLink: nav.showSubscriptionLink,
sessionUser: sessionUser ? { email: sessionUser.email} : undefined,
adminUrl: settings.adminUrl,
items: cloneAndTranslateText(nav.header_extras)
})
block body
if (typeof suppressNavbar === "undefined")
if bootstrapVersion === 5
include layout/navbar-marketing-react-bootstrap-5
else
include layout/navbar-marketing
block content
if (typeof suppressFooter === "undefined")
if showThinFooter
include layout/footer-marketing
else
include layout/fat-footer
if (typeof suppressCookieBanner === "undefined")
include _cookie_banner
if bootstrapVersion === 3
!= moduleIncludes("contactModal-marketing", locals)
block prepend foot-scripts
//- Only include Bootstrap JS if using Bootstrap 3
if bootstrapVersion === 3
+bootstrap-js(3)

View file

@ -0,0 +1 @@
#navbar-container

View file

@ -1,4 +1,4 @@
extends ../layout-marketing
extends ../layout-react
block entrypointVar
- entrypoint = 'pages/project-list'

View file

@ -1,4 +1,4 @@
extends ../layout-marketing
extends ../layout-react
block entrypointVar
- entrypoint = 'pages/user/settings'

View file

@ -2,6 +2,8 @@
"1_2_width": "",
"1_4_width": "",
"3_4_width": "",
"Account": "",
"Account Settings": "",
"a_custom_size_has_been_used_in_the_latex_code": "",
"a_fatal_compile_error_that_completely_blocks_compilation": "",
"a_file_with_that_name_already_exists_and_will_be_overriden": "",
@ -770,6 +772,7 @@
"log_entry_maximum_entries_title": "",
"log_hint_extra_info": "",
"log_in_with_primary_email_address": "",
"log_out": "",
"log_out_lowercase_dot": "",
"log_viewer_error": "",
"logging_in_or_managing_your_account": "",
@ -782,6 +785,7 @@
"lost_connection": "",
"main_document": "",
"main_file_not_found": "",
"main_navigation": "",
"make_a_copy": "",
"make_email_primary_description": "",
"make_owner": "",
@ -1358,6 +1362,7 @@
"submit_title": "",
"subscribe": "",
"subscribe_to_find_the_symbols_you_need_faster": "",
"subscription": "",
"subscription_admins_cannot_be_deleted": "",
"subscription_canceled": "",
"subscription_canceled_and_terminate_on_x": "",

View file

@ -0,0 +1,9 @@
import ReactDOM from 'react-dom'
import DefaultNavbar from '@/features/ui/components/bootstrap-5/navbar/default-navbar'
import getMeta from '@/utils/meta'
const element = document.getElementById('navbar-container')
if (element) {
const navbarProps = getMeta('ol-navbar')
ReactDOM.render(<DefaultNavbar {...navbarProps} />, element)
}

View file

@ -99,8 +99,8 @@ export const DropdownMenu = forwardRef<
})
DropdownMenu.displayName = 'DropdownMenu'
export function DropdownDivider({ as = 'li' }: DropdownDividerProps) {
return <BS5DropdownDivider as={as} />
export function DropdownDivider({ as = 'li', ...props }: DropdownDividerProps) {
return <BS5DropdownDivider as={as} {...props} />
}
export function DropdownHeader({ as = 'li', ...props }: DropdownHeaderProps) {

View file

@ -0,0 +1,49 @@
import type { DefaultNavbarMetadata } from '@/features/ui/components/types/default-navbar-metadata'
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'
export default function AdminMenu({
canDisplayAdminMenu,
canDisplayAdminRedirect,
canDisplaySplitTestMenu,
canDisplaySurveyMenu,
adminUrl,
}: Pick<
DefaultNavbarMetadata,
| 'canDisplayAdminMenu'
| 'canDisplayAdminRedirect'
| 'canDisplaySplitTestMenu'
| 'canDisplaySurveyMenu'
| 'adminUrl'
>) {
return (
<NavDropdownMenu title="Admin" className="subdued">
{canDisplayAdminMenu ? (
<>
<NavDropdownLinkItem href="/admin">Manage Site</NavDropdownLinkItem>
<NavDropdownLinkItem href="/admin/user">
Manage Users
</NavDropdownLinkItem>
<NavDropdownLinkItem href="/admin/project">
Project URL lookup
</NavDropdownLinkItem>
</>
) : null}
{canDisplayAdminRedirect && adminUrl ? (
<NavDropdownLinkItem href={adminUrl}>
Switch to Admin
</NavDropdownLinkItem>
) : null}
{canDisplaySplitTestMenu ? (
<NavDropdownLinkItem href="/admin/split-test">
Manage Feature Flags
</NavDropdownLinkItem>
) : null}
{canDisplaySurveyMenu ? (
<NavDropdownLinkItem href="/admin/survey">
Manage Surveys
</NavDropdownLinkItem>
) : null}
</NavDropdownMenu>
)
}

View file

@ -0,0 +1,25 @@
import NavDropdownLinkItem from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-link-item'
import { sendMB } from '@/infrastructure/event-tracking'
import { useTranslation } from 'react-i18next'
import { useContactUsModal } from '@/shared/hooks/use-contact-us-modal'
import { UserProvider } from '@/shared/context/user-context'
export default function ContactUsItem() {
const { t } = useTranslation()
const { modal, showModal } = useContactUsModal({ autofillProjectUrl: false })
return (
<>
<NavDropdownLinkItem
href="#"
onClick={() => {
sendMB('menu-clicked-contact')
showModal()
}}
>
{t('contact_us')}
</NavDropdownLinkItem>
<UserProvider>{modal}</UserProvider>
</>
)
}

View file

@ -0,0 +1,119 @@
import { sendMB } from '@/infrastructure/event-tracking'
import { useTranslation } from 'react-i18next'
import { Button, Container, Nav, Navbar } from 'react-bootstrap-5'
import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
import AdminMenu from '@/features/ui/components/bootstrap-5/navbar/admin-menu'
import type { DefaultNavbarMetadata } from '@/features/ui/components/types/default-navbar-metadata'
import NavItemFromData from '@/features/ui/components/bootstrap-5/navbar/nav-item-from-data'
import LoggedInItems from '@/features/ui/components/bootstrap-5/navbar/logged-in-items'
import HeaderLogoOrTitle from '@/features/ui/components/bootstrap-5/navbar/header-logo-or-title'
import MaterialIcon from '@/shared/components/material-icon'
function LoggedOutItems() {
return <span>Logged out</span>
}
function DefaultNavbar(props: DefaultNavbarMetadata) {
const {
customLogo,
title,
canDisplayAdminMenu,
canDisplayAdminRedirect,
canDisplaySplitTestMenu,
canDisplaySurveyMenu,
enableUpgradeButton,
suppressNavbarRight,
suppressNavContentLinks,
showSubscriptionLink,
sessionUser,
adminUrl,
items,
} = props
const { t } = useTranslation()
const { isReady } = useWaitForI18n()
if (!isReady) {
return null
}
return (
<>
<Navbar className="navbar-default navbar-main" expand="lg">
<Container className="navbar-container" fluid>
<div className="navbar-header">
<HeaderLogoOrTitle title={title} customLogo={customLogo} />
{enableUpgradeButton ? (
<Button
as="a"
href="/user/subscription/plans"
className="me-2 d-md-none"
onClick={() => {
sendMB('upgrade-button-click', {
source: 'dashboard-top',
'project-dashboard-react': 'enabled',
'is-dashboard-sidebar-hidden': 'true',
'is-screen-width-less-than-768px': 'true',
})
}}
>
{t('upgrade')}
</Button>
) : null}
</div>
{suppressNavbarRight ? null : (
<>
<Navbar.Toggle
aria-controls="navbar-main-collapse"
aria-expanded="false"
aria-label={t('main_navigation')}
>
<MaterialIcon type="menu" />
</Navbar.Toggle>
<Navbar.Collapse
id="navbar-main-collapse"
className="justify-content-end"
>
<Nav as="ul" className="ms-auto" role="menubar">
{canDisplayAdminMenu ||
canDisplayAdminRedirect ||
canDisplaySplitTestMenu ? (
<AdminMenu
canDisplayAdminMenu={canDisplayAdminMenu}
canDisplayAdminRedirect={canDisplayAdminRedirect}
canDisplaySplitTestMenu={canDisplaySplitTestMenu}
canDisplaySurveyMenu={canDisplaySurveyMenu}
adminUrl={adminUrl}
/>
) : null}
{items.map((item, index) => {
const showNavItem =
(item.only_when_logged_in && sessionUser) ||
(item.only_when_logged_out && sessionUser) ||
(!item.only_when_logged_out &&
!item.only_when_logged_in &&
!item.only_content_pages) ||
(item.only_content_pages && !suppressNavContentLinks)
return showNavItem ? (
<NavItemFromData item={item} key={index} />
) : null
})}
{sessionUser ? (
<LoggedInItems
sessionUser={sessionUser}
showSubscriptionLink={showSubscriptionLink}
/>
) : (
<LoggedOutItems />
)}
</Nav>
</Navbar.Collapse>
</>
)}
</Container>
</Navbar>
</>
)
}
export default DefaultNavbar

View file

@ -0,0 +1,32 @@
import type { DefaultNavbarMetadata } from '@/features/ui/components/types/default-navbar-metadata'
import getMeta from '@/utils/meta'
export default function HeaderLogoOrTitle({
customLogo,
title,
}: Pick<DefaultNavbarMetadata, 'customLogo' | 'title'>) {
const { appName } = getMeta('ol-ExposedSettings')
if (customLogo) {
return (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a
href="/"
aria-label={appName}
className="navbar-brand"
style={{ backgroundImage: `url("${customLogo}")` }}
/>
)
} else if (title) {
return (
<a href="/" aria-label={appName} className="navbar-title">
{title}
</a>
)
} else {
return (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a href="/" aria-label={appName} className="navbar-brand" />
)
}
}

View file

@ -0,0 +1,60 @@
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 NavDropdownItem from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-item'
import type { NavbarSessionUser } from '@/features/ui/components/types/navbar'
import NavLinkItem from '@/features/ui/components/bootstrap-5/navbar/nav-link-item'
export default function LoggedInItems({
sessionUser,
showSubscriptionLink,
}: {
sessionUser: NavbarSessionUser
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 />
<NavDropdownItem>
{
// 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>
</NavDropdownItem>
</NavDropdownMenu>
</>
)
}

View file

@ -0,0 +1,5 @@
import { DropdownDivider } from '@/features/ui/components/bootstrap-5/dropdown-menu'
export default function NavDropdownDivider() {
return <DropdownDivider className="d-none d-lg-block" />
}

View file

@ -0,0 +1,44 @@
import type { NavbarDropdownItemData } 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'
import NavDropdownLinkItem from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-link-item'
import NavDropdownItem from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-item'
import NavDropdownMenu from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-menu'
import ContactUsItem from '@/features/ui/components/bootstrap-5/navbar/contact-us-item'
export default function NavDropdownFromData({
item,
}: {
item: NavbarDropdownItemData
}) {
return (
<NavDropdownMenu title={item.translatedText} className={item.class}>
{item.dropdown.map((child, index) => {
if ('divider' in child) {
return <NavDropdownDivider key={index} />
} else if ('isContactUs' in child) {
return <ContactUsItem key={index} />
} else if (isDropdownLinkItem(child)) {
return (
<NavDropdownLinkItem
key={index}
href={child.url}
onClick={() => {
sendMB(child.event)
}}
>
{child.translatedText}
</NavDropdownLinkItem>
)
} else {
return (
<NavDropdownItem key={index}>
{child.translatedText}
</NavDropdownItem>
)
}
})}
</NavDropdownMenu>
)
}

View file

@ -0,0 +1,5 @@
import { ReactNode } from 'react'
export default function NavDropdownItem({ children }: { children: ReactNode }) {
return <li role="none">{children}</li>
}

View file

@ -0,0 +1,22 @@
import { ReactNode } from 'react'
import NavDropdownItem from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-item'
import { DropdownItem } from 'react-bootstrap-5'
import { DropdownItemProps } from 'react-bootstrap-5/DropdownItem'
export default function NavDropdownLinkItem({
href,
onClick,
children,
}: {
href: string
onClick?: DropdownItemProps['onClick']
children: ReactNode
}) {
return (
<NavDropdownItem>
<DropdownItem href={href} role="menuitem" onClick={onClick}>
{children}
</DropdownItem>
</NavDropdownItem>
)
}

View file

@ -0,0 +1,23 @@
import { ReactNode } from 'react'
import { Dropdown } from 'react-bootstrap-5'
export default function NavDropdownMenu({
title,
className,
children,
}: {
title: string
className?: string
children: ReactNode
}) {
// Can't use a NavDropdown here because it's impossible to render the menu as
// a <ul> element using NavDropdown
return (
<Dropdown as="li" role="none" className={className}>
<Dropdown.Toggle role="menuitem">{title}</Dropdown.Toggle>
<Dropdown.Menu as="ul" role="menu" align="end">
{children}
</Dropdown.Menu>
</Dropdown>
)
}

View file

@ -0,0 +1,29 @@
import type { NavbarItemData } from '@/features/ui/components/types/navbar'
import {
isDropdownItem,
isLinkItem,
} from '@/features/ui/components/bootstrap-5/navbar/util'
import NavDropdownFromData from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-from-data'
import NavItem from '@/features/ui/components/bootstrap-5/navbar/nav-item'
import { sendMB } from '@/infrastructure/event-tracking'
import NavLinkItem from '@/features/ui/components/bootstrap-5/navbar/nav-link-item'
export default function NavItemFromData({ item }: { item: NavbarItemData }) {
if (isDropdownItem(item)) {
return <NavDropdownFromData item={item} />
} else if (isLinkItem(item)) {
return (
<NavLinkItem
className={item.class}
href={item.url}
onClick={() => {
sendMB(item.event)
}}
>
{item.translatedText}
</NavLinkItem>
)
} else {
return <NavItem className={item.class}>{item.translatedText}</NavItem>
}
}

View file

@ -0,0 +1,10 @@
import { Nav, NavItemProps } from 'react-bootstrap-5'
export default function NavItem(props: Omit<NavItemProps, 'as'>) {
const { children, ...rest } = props
return (
<Nav.Item as="li" role="none" {...rest}>
{children}
</Nav.Item>
)
}

View file

@ -0,0 +1,23 @@
import { ReactNode } from 'react'
import { Nav } from 'react-bootstrap-5'
import NavItem from '@/features/ui/components/bootstrap-5/navbar/nav-item'
export default function NavLinkItem({
href,
className,
onClick,
children,
}: {
href: string
className?: string
onClick?: React.ComponentProps<typeof Nav.Link>['onClick']
children: ReactNode
}) {
return (
<NavItem className={className}>
<Nav.Link role="menuitem" href={href} onClick={onClick}>
{children}
</Nav.Link>
</NavItem>
)
}

View file

@ -0,0 +1,23 @@
import type {
NavbarDropdownItem,
NavbarDropdownItemData,
NavbarDropdownLinkItem,
NavbarItemData,
NavbarLinkItemData,
} from '@/features/ui/components/types/navbar'
export function isDropdownLinkItem(
item: NavbarDropdownItem
): item is NavbarDropdownLinkItem {
return 'url' in item
}
export function isDropdownItem(
item: NavbarItemData
): item is NavbarDropdownItemData {
return 'dropdown' in item
}
export function isLinkItem(item: NavbarItemData): item is NavbarLinkItemData {
return 'url' in item
}

View file

@ -0,0 +1,20 @@
import type {
NavbarItemData,
NavbarSessionUser,
} from '@/features/ui/components/types/navbar'
export type DefaultNavbarMetadata = {
customLogo?: string
title?: string
canDisplayAdminMenu: boolean
canDisplayAdminRedirect: boolean
canDisplaySplitTestMenu: boolean
canDisplaySurveyMenu: boolean
enableUpgradeButton: boolean
suppressNavbarRight: boolean
suppressNavContentLinks: boolean
showSubscriptionLink: boolean
sessionUser?: NavbarSessionUser
adminUrl?: string
items: NavbarItemData[]
}

View file

@ -55,6 +55,7 @@ export type DropdownMenuProps = PropsWithChildren<{
export type DropdownDividerProps = PropsWithChildren<{
as?: ElementType
className?: string
}>
export type DropdownHeaderProps = PropsWithChildren<{

View file

@ -0,0 +1,52 @@
export interface NavbarDropdownDivider {
divider: true
}
export interface NavbarDropdownContactUsItem {
isContactUs: true
}
export interface NavbarDropdownTextItem {
text: string
translatedText: string
class?: string
}
export interface NavbarDropdownLinkItem extends NavbarDropdownTextItem {
url: string
event: string
eventSegmentation?: Record<string, any>
}
export type NavbarDropdownItem =
| NavbarDropdownDivider
| NavbarDropdownContactUsItem
| NavbarDropdownTextItem
| NavbarDropdownLinkItem
export type NavbarItemDropdownData = NavbarDropdownItem[]
export interface NavbarTextItemData {
text: string
translatedText: string
only_when_logged_in?: boolean
only_when_logged_out?: boolean
only_content_pages?: boolean
class?: string
}
export interface NavbarDropdownItemData extends NavbarTextItemData {
dropdown: NavbarItemDropdownData
}
export interface NavbarLinkItemData extends NavbarTextItemData {
url: string
event: string
}
export type NavbarItemData =
| NavbarDropdownItemData
| NavbarLinkItemData
| NavbarTextItemData
export type NavbarSessionUser = { email: string }

View file

@ -1,7 +1,7 @@
import getMeta from '@/utils/meta'
// The reason this is a function to ensure that meta tag is read before any
// isBootstrap5 check is performed
// The reason this is a function is to ensure that the meta tag is read before
// any isBootstrap5 check is performed
export const isBootstrap5 = () => getMeta('ol-bootstrapVersion') === 5
export const bsVersion = ({ bs5, bs3 }: { bs5?: string; bs3?: string }) => {

View file

@ -9,3 +9,4 @@ import './features/multi-submit'
import './features/cookie-banner'
import './features/autoplay-video'
import './features/mathjax'
import './features/header-footer-react'

View file

@ -2,10 +2,10 @@ import './../utils/meta'
import './../utils/webpack-public-path'
import './../infrastructure/error-reporter'
import './../i18n'
import '../features/contact-form'
import '../features/event-tracking'
import '../features/cookie-banner'
import '../features/link-helpers/slow-link'
import '../features/header-footer-react'
import ReactDOM from 'react-dom'
import ProjectListRoot from '../features/project-list/components/project-list-root'

View file

@ -46,6 +46,7 @@ import { PasswordStrengthOptions } from '../../../types/password-strength-option
import { Subscription as ProjectDashboardSubscription } from '../../../types/project/dashboard/subscription'
import { ThirdPartyIds } from '../../../types/third-party-ids'
import { Publisher } from '../../../types/subscription/dashboard/publisher'
import { DefaultNavbarMetadata } from '@/features/ui/components/types/default-navbar-metadata'
export interface Meta {
'ol-ExposedSettings': ExposedSettings
@ -135,6 +136,7 @@ export interface Meta {
'ol-memberGroupSubscriptions': MemberGroupSubscription[]
'ol-memberOfSSOEnabledGroups': GroupSSOLinkingStatus[]
'ol-members': MinimalUser[]
'ol-navbar': DefaultNavbarMetadata
'ol-no-single-dollar': boolean
'ol-notifications': NotificationType[]
'ol-notificationsInstitution': InstitutionType[]

View file

@ -204,6 +204,9 @@ $navbar-toggler-padding-x: var(--spacing-05);
$navbar-toggler-padding-y: var(--spacing-02);
$navbar-toggler-border-radius: var(--border-radius-base);
// Nav links
$navbar-nav-link-padding-x: var(--navbar-btn-padding-h);
// Spacing scale used by Bootstrap utilities
$spacer: $spacing-06;
$spacers: (

View file

@ -45,7 +45,7 @@
> li {
display: inline-flex;
> a,
> .nav-link,
> .dropdown-toggle {
display: block;
color: var(--navbar-link-color);
@ -69,7 +69,7 @@
}
}
&.subdued > a,
&.subdued > .nav-link,
&.subdued > .dropdown-toggle {
border: 0;
color: var(--navbar-subdued-color);
@ -87,6 +87,8 @@
}
.navbar-toggler {
--bs-navbar-toggler-padding-x: var(--spacing-04);
color: var(--navbar-link-color);
border-radius: var(--border-radius-base);
border-width: 0;
@ -97,6 +99,12 @@
background-color: var(--navbar-toggler-expanded-bg);
transition: 0.2s ease-in;
}
& .material-symbols {
font-size: inherit;
font-weight: bold;
vertical-align: middle;
}
}
.navbar-collapse,
@ -129,7 +137,8 @@
> a,
> .dropdown-toggle {
margin: 0;
padding: var(--spacing-05) var(--navbar-padding-h);
padding-top: var(--spacing-05);
padding-bottom: var(--spacing-05);
width: 100%;
text-align: left;
border-radius: 0;

View file

@ -674,11 +674,11 @@
"footer_about_us": "About us",
"footer_contact_us": "Contact us",
"footer_navigation": "Footer navigation",
"footer_plans_and_pricing": "Plans &amp; pricing",
"footer_plans_and_pricing": "Plans & pricing",
"for_business": "For business",
"for_enterprise": "For enterprise",
"for_groups_or_site_wide": "For groups or site-wide",
"for_individuals_and_groups": "For individuals &amp; groups",
"for_individuals_and_groups": "For individuals & groups",
"for_large_institutions_and_organizations_need_sitewide_on_premise": "For large institutions and organizations that need site-wide access or an on-premises solution.",
"for_more_information_see_managed_accounts_section": "For more information, see the \"Managed Accounts\" section in <0>our terms of use</0>, which you agree to by clicking Accept invitation.",
"for_publishers": "For publishers",
@ -1151,6 +1151,7 @@
"lost_connection": "Lost Connection",
"main_document": "Main document",
"main_file_not_found": "Unknown main document",
"main_navigation": "Main navigation",
"maintenance": "Maintenance",
"make_a_copy": "Make a copy",
"make_email_primary_description": "Make this the primary email, used to log in",
@ -1443,7 +1444,7 @@
"plan": "Plan",
"plan_tooltip": "Youre on the __plan__ plan. Click to find out how to make the most of your Overleaf premium features.",
"planned_maintenance": "Planned Maintenance",
"plans_amper_pricing": "Plans &amp; Pricing",
"plans_amper_pricing": "Plans & Pricing",
"plans_and_pricing": "Plans and Pricing",
"plans_and_pricing_lowercase": "plans and pricing",
"please_ask_the_project_owner_to_upgrade_to_track_changes": "Please ask the project owner to upgrade to use track changes",
@ -1486,7 +1487,7 @@
"premium_plan_label": "Youre using <b>Overleaf Premium</b>",
"presentation": "Presentation",
"presentation_mode": "Presentation mode",
"press_and_awards": "Press &amp; awards",
"press_and_awards": "Press & awards",
"previous_page": "Previous page",
"price": "Price",
"primarily_work_study_question": "Where do you primarily work or study?",