Merge pull request #18420 from overleaf/td-bs5-nav

Bootstrap 5: Navigation

GitOrigin-RevId: d22683d78c8b3bc3f41bbd276c36f47d480e24ff
This commit is contained in:
Tim Down 2024-05-28 12:40:05 +01:00 committed by Copybot
parent 8704e430a4
commit edaba14ed1
14 changed files with 555 additions and 23 deletions

View file

@ -0,0 +1,23 @@
mixin nav-item
li(role="none")&attributes(attributes)
block
mixin nav-link
a(role="menuitem")&attributes(attributes)
block
mixin dropdown-menu
ul(role="menu").dropdown-menu&attributes(attributes)
block
mixin dropdown-menu-item
li(role="none")
block
mixin dropdown-menu-link-item
+dropdown-menu-item
a(role="menuitem", tabindex="-1").dropdown-item&attributes(attributes)
block
mixin dropdown-menu-divider
li(role="separator").dropdown-divider.d-none.d-lg-block

View file

@ -7,12 +7,15 @@ block entrypointVar
- entrypoint = 'marketing' - entrypoint = 'marketing'
block body block body
if (typeof(suppressNavbar) == "undefined") if (typeof suppressNavbar === "undefined")
if bootstrapVersion === 5
include layout/navbar-marketing-bootstrap-5
else
include layout/navbar-marketing include layout/navbar-marketing
block content block content
if (typeof(suppressFooter) == "undefined") if (typeof suppressFooter === "undefined")
if showThinFooter if showThinFooter
include layout/footer-marketing include layout/footer-marketing
else else

View file

@ -0,0 +1,179 @@
include ../_mixins/navbar
nav.navbar.navbar-default.navbar-main.navbar-expand-lg
.container-fluid.navbar-container
.navbar-header
if settings.nav.custom_logo
a(href='/', aria-label=settings.appName, style='background-image:url("'+settings.nav.custom_logo+'")').navbar-brand
else if (nav.title)
a(href='/', aria-label=settings.appName).navbar-title #{nav.title}
else
a(href='/', aria-label=settings.appName).navbar-brand
- var enableUpgradeButton = projectDashboardReact && usersBestSubscription && usersBestSubscription.type === 'free'
if (enableUpgradeButton)
a.btn.btn-primary.me-2.d-md-none(
href="/user/subscription/plans"
event-tracking="upgrade-button-click"
event-tracking-mb="true"
event-tracking-label="upgrade"
event-tracking-trigger="click"
event-segmentation='{"source": "dashboard-top", "project-dashboard-react": "enabled", "is-dashboard-sidebar-hidden": "true", "is-screen-width-less-than-768px": "true"}'
) #{translate("upgrade")}
- var canDisplayAdminMenu = hasAdminAccess()
- var canDisplayAdminRedirect = canRedirectToAdminDomain()
- var canDisplaySplitTestMenu = hasFeature('saas') && (canDisplayAdminMenu || (getSessionUser() && getSessionUser().staffAccess && (getSessionUser().staffAccess.splitTestMetrics || getSessionUser().staffAccess.splitTestManagement)))
- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
if (typeof suppressNavbarRight === "undefined")
button.navbar-toggler.collapsed(
type="button",
data-bs-toggle="collapse",
data-bs-target="#navbar-main-collapse"
aria-controls="navbar-main-collapse"
aria-expanded="false"
aria-label="Toggle " + translate('navigation')
)
i.fa.fa-bars(aria-hidden="true")
.navbar-collapse.collapse#navbar-main-collapse
ul.nav.navbar-nav.navbar-right.ms-auto(role="menubar")
if (canDisplayAdminMenu || canDisplayAdminRedirect || canDisplaySplitTestMenu)
+nav-item.dropdown.subdued
button.dropdown-toggle(
aria-haspopup="true",
aria-expanded="false",
data-bs-toggle="dropdown"
)
| Admin
span.caret
+dropdown-menu.dropdown-menu-end
if canDisplayAdminMenu
+dropdown-menu-link-item()(href="/admin") Manage Site
+dropdown-menu-link-item()(href="/admin/user") Manage Users
+dropdown-menu-link-item()(href="/admin/project") Project URL Lookup
if canDisplayAdminRedirect
+dropdown-menu-link-item()(settings.adminUrl) Switch to Admin
if canDisplaySplitTestMenu
+dropdown-menu-link-item()(href="/admin/split-test") Manage Feature Flags
if canDisplaySurveyMenu
+dropdown-menu-link-item()(href="/admin/survey") Manage Surveys
// loop over header_extras
each item in nav.header_extras
-
if ((item.only_when_logged_in && getSessionUser())
|| (item.only_when_logged_out && (!getSessionUser()))
|| (!item.only_when_logged_out && !item.only_when_logged_in && !item.only_content_pages)
|| (item.only_content_pages && (typeof suppressNavContentLinks === "undefined" || !suppressNavContentLinks))
){
var showNavItem = true
} else {
var showNavItem = false
}
if showNavItem
if item.dropdown
+nav-item.dropdown(class=item.class)
button.dropdown-toggle(
aria-haspopup="true",
aria-expanded="false",
data-bs-toggle="dropdown"
)
| !{translate(item.text)}
span.caret
+dropdown-menu.dropdown-menu-end
each child in item.dropdown
if child.divider
+dropdown-menu-divider
else if child.isContactUs
+dropdown-menu-link-item()(data-ol-open-contact-form-modal="contact-us" href)
span(event-tracking="menu-clicked-contact" event-tracking-mb="true" event-tracking-trigger="click")
| #{translate("contact_us")}
else
if child.url
+dropdown-menu-link-item()(
class=child.class,
event-tracking=child.event
event-tracking-mb="true"
event-tracking-trigger="click"
event-segmentation=child.eventSegmentation
) !{translate(child.text)}
else
+dropdown-menu-item !{translate(child.text)}
else
+nav-item(class=item.class)
if item.url
+nav-link(
href=item.url,
class=item.class,
event-tracking=item.event
event-tracking-mb="true"
event-tracking-trigger="click"
) !{translate(item.text)}
else
| !{translate(item.text)}
// logged out
if !getSessionUser()
// register link
if hasFeature('registration-page')
+nav-item.primary
+nav-link(
href="/register"
event-tracking="menu-clicked-register"
event-tracking-action="clicked"
event-tracking-trigger="click"
event-tracking-mb="true"
event-segmentation={ page: currentUrl }
) #{translate('sign_up')}
// login link
+nav-item
+nav-link(
href="/login"
event-tracking="menu-clicked-login"
event-tracking-action="clicked"
event-tracking-trigger="click"
event-tracking-mb="true"
event-segmentation={ page: currentUrl }
) #{translate('log_in')}
// projects link and account menu
if getSessionUser()
+nav-item
+nav-link(href="/project") #{translate('Projects')}
+nav-item.dropdown
button.dropdown-toggle(
aria-haspopup="true",
aria-expanded="false",
data-bs-toggle="dropdown"
)
| #{translate('Account')}
span.caret
+dropdown-menu.dropdown-menu-end
+dropdown-menu-item
div.disabled.dropdown-item #{getSessionUser().email}
+dropdown-menu-divider
+dropdown-menu-link-item()(href="/user/settings") #{translate('Account Settings')}
if nav.showSubscriptionLink
+dropdown-menu-link-item()(href="/user/subscription") #{translate('subscription')}
+dropdown-menu-divider
+dropdown-menu-item
//-
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.
button.btn-link.text-left.dropdown-menu-button.dropdown-item(
role="menuitem",
tabindex="-1"
form="logOutForm"
)
| #{translate('log_out')}
form(
method="POST",
action="/logout",
id="logOutForm"
)
input(name='_csrf', type='hidden', value=csrfToken)

View file

@ -11,6 +11,7 @@ import type {
DropdownItemProps, DropdownItemProps,
DropdownToggleProps, DropdownToggleProps,
DropdownMenuProps, DropdownMenuProps,
DropdownDividerProps,
} from '@/features/ui/components/types/dropdown-menu-props' } from '@/features/ui/components/types/dropdown-menu-props'
import MaterialIcon from '@/shared/components/material-icon' import MaterialIcon from '@/shared/components/material-icon'
@ -62,9 +63,9 @@ export function DropdownToggle({ ...props }: DropdownToggleProps) {
} }
export function DropdownMenu({ as = 'ul', ...props }: DropdownMenuProps) { export function DropdownMenu({ as = 'ul', ...props }: DropdownMenuProps) {
return <BS5DropdownMenu as={as} role="menubar" {...props} /> return <BS5DropdownMenu as={as} role="menu" {...props} />
} }
export function DropdownDivider() { export function DropdownDivider({ as = 'li' }: DropdownDividerProps) {
return <BS5DropdownDivider aria-hidden="true" /> return <BS5DropdownDivider as={as} />
} }

View file

@ -45,3 +45,7 @@ export type DropdownMenuProps = PropsWithChildren<{
disabled?: boolean disabled?: boolean
show?: boolean show?: boolean
}> }>
export type DropdownDividerProps = PropsWithChildren<{
as?: ElementType
}>

View file

@ -1,3 +1,3 @@
@import 'mixins'; @import 'mixins';
@import 'variable-overrides';
@import 'variables'; @import 'variables';
@import 'variable-overrides';

View file

@ -130,7 +130,6 @@ $form-validation-states: (
), ),
); );
// Close buttons // Close buttons
// Close button
$btn-close-color: $content-primary; $btn-close-color: $content-primary;
$btn-close-opacity: 1; $btn-close-opacity: 1;
$btn-close-width: 10px; $btn-close-width: 10px;
@ -145,6 +144,14 @@ $danger: $bg-danger-01;
$light: $bg-light-tertiary; $light: $bg-light-tertiary;
$dark: $neutral-90; $dark: $neutral-90;
// Body colors
$body-color: $content-primary;
$body-bg: $bg-light-primary;
$body-secondary-color: $content-secondary;
$body-secondary-bg: $bg-light-secondary;
$body-tertiary-color: $content-disabled;
$body-tertiary-bg: $bg-light-tertiary;
// Badges // Badges
$badge-font-size: var(--font-size-01); $badge-font-size: var(--font-size-01);
$badge-font-weight: var(--bs-body-font-weight); $badge-font-weight: var(--bs-body-font-weight);
@ -183,3 +190,30 @@ $box-shadow-lg: shadow-lg();
$modal-header-padding: $spacing-05 $spacing-06; $modal-header-padding: $spacing-05 $spacing-06;
$modal-header-padding-x: $spacing-08; $modal-header-padding-x: $spacing-08;
$modal-header-padding-y: $spacing-08; $modal-header-padding-y: $spacing-08;
// Nav toggler
$navbar-toggler-font-size: 30px;
$navbar-toggler-padding-x: var(--spacing-05);
$navbar-toggler-padding-y: var(--spacing-02);
$navbar-toggler-border-radius: var(--border-radius-base);
// Spacing scale used by Bootstrap utilities
$spacer: $spacing-06;
$spacers: (
0: 0,
1: $spacing-02,
2: $spacing-04,
3: $spacing-06,
4: $spacing-08,
5: $spacing-11,
);
// Dropdowns
$dropdown-link-disabled-color: var(--content-disabled);
$dropdown-spacer: var(--spacing-01);
$dropdown-border-width: 0;
$dropdown-border-radius: var(--border-radius-base);
$dropdown-padding-x: var(--spacing-02);
$dropdown-padding-y: var(--spacing-02);
$dropdown-item-padding-x: var(--spacing-04);
$dropdown-item-padding-y: var(--spacing-05);

View file

@ -22,6 +22,7 @@
// Layout & components // Layout & components
@import 'bootstrap-5/scss/root'; @import 'bootstrap-5/scss/root';
@import 'bootstrap-5/scss/reboot'; @import 'bootstrap-5/scss/reboot';
@import 'bootstrap-5/scss/transitions';
@import 'bootstrap-5/scss/type'; @import 'bootstrap-5/scss/type';
@import 'bootstrap-5/scss/images'; @import 'bootstrap-5/scss/images';
@import 'bootstrap-5/scss/containers'; @import 'bootstrap-5/scss/containers';
@ -35,6 +36,8 @@
@import 'bootstrap-5/scss/spinners'; @import 'bootstrap-5/scss/spinners';
@import 'bootstrap-5/scss/card'; @import 'bootstrap-5/scss/card';
@import 'bootstrap-5/scss/close'; @import 'bootstrap-5/scss/close';
@import 'bootstrap-5/scss/nav';
@import 'bootstrap-5/scss/navbar';
// Helpers // Helpers
@import 'bootstrap-5/scss/helpers'; @import 'bootstrap-5/scss/helpers';

View file

@ -9,3 +9,5 @@
@import 'input-suggestions'; @import 'input-suggestions';
@import 'modal'; @import 'modal';
@import 'footer'; @import 'footer';
@import 'nav';
@import 'navbar';

View file

@ -96,7 +96,6 @@
color: var(--link-ui); color: var(--link-ui);
font-weight: normal; font-weight: normal;
cursor: pointer; cursor: pointer;
border-radius: 0;
text-decoration: underline; text-decoration: underline;
font-size: inherit; font-size: inherit;
vertical-align: inherit; vertical-align: inherit;

View file

@ -3,30 +3,30 @@
} }
.dropdown-menu { .dropdown-menu {
@include shadow-sm; @include shadow-md;
border: none;
border-radius: var(--border-radius-base);
padding: var(--spacing-02);
transform: none;
width: 240px; width: 240px;
} }
.dropdown-item { .dropdown-item {
@include body-sm; @include body-sm;
border-radius: var(--border-radius-base); --bs-dropdown-item-border-radius: var(--border-radius-base);
color: var(--neutral-90);
display: grid; display: grid;
grid-auto-flow: column; grid-auto-flow: column;
justify-content: start; justify-content: start;
align-content: center;
min-height: 44px; // a minimum height of 44px to be accessible for touch screens min-height: 44px; // a minimum height of 44px to be accessible for touch screens
padding: var(--spacing-05) var(--spacing-04);
position: relative; position: relative;
&:active { &:active {
background-color: var(--bg-accent-03); background-color: var(--bg-accent-03);
color: inherit; }
&,
&:active,
&:visited {
color: var(--neutral-90);
} }
&:hover:not(.active), &:hover:not(.active),
@ -49,12 +49,20 @@
background-color: var(--bg-accent-03); background-color: var(--bg-accent-03);
color: var(--green-70); color: var(--green-70);
} }
&.btn-link {
text-decoration: none;
&:hover,
&:focus {
color: inherit;
}
}
} }
.dropdown-divider { .dropdown-divider {
border-radius: 1px; border-top-color: var(--border-divider);
background-color: var(--border-divider); margin: var(--spacing-01) var(--spacing-03);
margin: 2px 6px;
} }
.dropdown-item-description { .dropdown-item-description {

View file

@ -0,0 +1,56 @@
@import 'bootstrap-5/scss/functions';
:root {
// Basics
--navbar-item-spacing-horizontal: var(--spacing-06);
--navbar-bg: var(--bg-dark-primary);
--navbar-border: transparent;
--navbar-padding-h: var(--spacing-05);
--navbar-padding: 0 var(--navbar-padding-h);
--navbar-brand-width: 130px;
--navbar-brand-image-url: url(../../../../public/img/ol-brand/overleaf-white.svg);
// Title, when used instead of a logo
--navbar-title-font-size: var(--font-size-05);
--navbar-title-color: var(--neutral-20);
--navbar-title-color-hover: var(--neutral-40);
// Button-like top-level items
--navbar-btn-font-size: #{$font-size-base};
--navbar-btn-border-radius: #{$border-radius-full};
--navbar-btn-border-width: #{$spacing-01};
--navbar-btn-font-weight: #{$font-weight-base};
--navbar-btn-padding-v: #{$spacing-02};
--navbar-btn-padding-h: #{$spacing-06};
--navbar-btn-line-height: #{$line-height-base};
// Properties of "subdued" items
--navbar-subdued-padding: calc(
var(--navbar-btn-padding-v) + var(--navbar-btn-border-width)
)
calc(var(--navbar-btn-padding-h) + 1px);
--navbar-subdued-color: var(--white);
--navbar-subdued-hover-bg: var(--white);
--navbar-subdued-hover-color: var(--green-50);
// Links
--navbar-link-color: var(--white);
--navbar-link-border-color: var(--navbar-link-color);
--navbar-link-hover-color: var(--white);
--navbar-link-hover-bg: var(--green-50);
--navbar-link-hover-border-color: var(--navbar-link-hover-bg);
// Toggler
--navbar-toggler-expanded-color: var(--navbar-link-color);
--navbar-toggler-expanded-bg: var(--blue-60);
// Mobile view
--navbar-hamburger-submenu-bg: var(--bg-dark-secondary);
--navbar-hamburger-submenu-item-color: var(--navbar-link-color);
--navbar-hamburger-submenu-item-hover-color: var(--navbar-link-color);
--navbar-hamburger-submenu-item-hover-bg: var(--navbar-link-hover-bg);
}

View file

@ -0,0 +1,220 @@
// Default navbar
.navbar-default {
background-color: var(--navbar-bg);
border-color: var(--navbar-border);
padding: var(--navbar-padding);
position: absolute;
top: 0;
width: 100%;
height: $header-height;
.navbar-container {
padding-left: 0;
padding-right: 0;
}
.navbar-header {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
}
.navbar-brand {
width: var(--navbar-brand-width);
height: $header-height;
padding: 0;
background: var(--navbar-brand-image-url) no-repeat left center;
background-size: contain;
}
.navbar-title {
display: inline-block;
font-size: var(--navbar-title-font-size);
color: var(--navbar-title-color);
text-decoration: none;
&:hover,
&:active,
&:focus {
color: var(--navbar-title-color-hover);
}
}
.navbar-nav {
> li {
display: inline-flex;
> a,
> .dropdown-toggle {
display: block;
color: var(--navbar-link-color);
background-color: transparent;
padding: var(--navbar-btn-padding-v) var(--navbar-btn-padding-h);
margin-left: var(--navbar-item-spacing-horizontal);
border: var(--navbar-btn-border-width) solid
var(--navbar-link-border-color);
border-radius: var(--navbar-btn-border-radius);
font-size: var(--navbar-btn-font-size);
font-weight: var(--navbar-btn-font-weight);
line-height: var(--navbar-btn-line-height);
text-decoration: none;
&.show,
&:hover,
&:focus {
color: var(--navbar-link-hover-color);
background-color: var(--navbar-link-hover-bg);
border-color: var(--navbar-link-hover-border-color);
}
}
&.subdued > a,
&.subdued > .dropdown-toggle {
border: 0;
color: var(--navbar-subdued-color);
padding: var(--navbar-subdued-padding);
margin-left: 0;
&.show,
&:hover,
&:focus {
color: var(--navbar-subdued-hover-color);
background-color: var(--navbar-subdued-hover-bg);
}
}
}
}
.navbar-toggler {
color: var(--navbar-link-color);
border-radius: var(--border-radius-base);
border-width: 0;
transition: 0.2s ease-out;
&:not(.collapsed) {
color: var(--navbar-toggler-expanded-color);
background-color: var(--navbar-toggler-expanded-bg);
transition: 0.2s ease-in;
}
}
.navbar-collapse,
.navbar-form {
border-color: var(--navbar-border);
}
}
@include media-breakpoint-only(lg) {
:root {
--navbar-btn-padding-h: #{$spacing-04};
}
}
// Different (stacked) layout for smaller screens
@include media-breakpoint-down(lg) {
.navbar-default .navbar-collapse {
&.show {
min-height: calc(100vh - $header-height);
}
background-color: var(--navbar-bg);
margin: 0 calc(-1 * var(--navbar-padding-h));
z-index: 1;
.navbar-nav {
> li {
display: block;
> a,
> .dropdown-toggle {
margin: 0;
padding: var(--spacing-05) var(--navbar-padding-h);
width: 100%;
text-align: left;
border-radius: 0;
border-width: 0;
}
// Dropdowns get custom display when collapsed
.dropdown-menu.show {
--bs-dropdown-spacer: 0;
box-shadow: none;
background-color: var(--navbar-hamburger-submenu-bg);
width: auto;
.dropdown-item {
padding: var(--spacing-02) var(--spacing-06) var(--spacing-02)
var(--spacing-08);
&:not(.disabled) {
color: var(--navbar-hamburger-submenu-item-color);
&:hover,
&:focus {
color: var(--navbar-hamburger-submenu-item-hover-color);
background-color: var(--navbar-hamburger-submenu-item-hover-bg);
}
}
}
}
}
}
}
}
// Accessibility
.skip-to-content {
color: var(--navbar-link-color);
background-color: var(--navbar-link-bg);
border: var(--spacing-01) solid transparent;
border-radius: var(--navbar-btn-border-radius);
font-size: var(--navbar-btn-font-size);
font-weight: var(--navbar-btn-font-weight);
left: calc(var(--navbar-brand-width) + var(--spacing-09));
line-height: var(--navbar-btn-line-height);
padding: var(--navbar-btn-padding-v) var(--navbar-btn-padding-h);
position: absolute;
top: -200px;
z-index: 1;
&:focus {
background-color: var(--navbar-link-hover-bg);
border: var(--spacing-01) solid var(--navbar-link-hover-color);
color: var(--white);
text-decoration: none;
top: calc(($header-height - 36px) / 2); // 36px is the height of the link
}
}
.website-redesign-navbar {
--navbar-title-color: var(--content-primary);
--navbar-title-color-hover: var(--content-secondary);
--navbar-brand-image-url: url(../../../../public/img/ol-brand/overleaf-black.svg);
--navbar-subdued-color: var(--content-primary);
--navbar-subdued-hover-bg: var(--bg-dark-primary);
--navbar-subdued-hover-color: var(--content-primary-dark);
--navbar-bg: var(--bg-light-primary);
// Navbar links
--navbar-link-color: var(--content-primary);
--navbar-link-border-color: var(--border-primary);
--navbar-link-hover-color: var(--navbar-link-color);
--navbar-link-hover-bg: var(--bg-light-tertiary);
--navbar-link-hover-border-color: var(--navbar-link-border-color);
// Toggler
--navbar-toggler-expanded-color: var(--white);
--navbar-toggler-expanded-bg: var(--bg-dark-primary);
// Mobile view
--navbar-hamburger-submenu-bg: var(--bg-light-secondary);
--navbar-hamburger-submenu-item-color: var(--navbar-link-color);
--navbar-hamburger-submenu-item-hover-color: var(--white);
--navbar-hamburger-submenu-item-hover-bg: var(--bg-dark-primary);
}

View file

@ -13,6 +13,6 @@
// Modals, drawers // Modals, drawers
@mixin shadow-lg { @mixin shadow-lg {
box-shadow: box-shadow:
0px 8px 16px 0px rgb(30 37 48 / 12%), 0 8px 16px 0 rgb(30 37 48 / 12%),
0px 4px 6px 0px rgb(30 37 48 / 12%); 0 4px 6px 0 rgb(30 37 48 / 12%);
} }