diff --git a/services/web/app/views/_mixins/navbar.pug b/services/web/app/views/_mixins/navbar.pug new file mode 100644 index 0000000000..bd72cf2939 --- /dev/null +++ b/services/web/app/views/_mixins/navbar.pug @@ -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 diff --git a/services/web/app/views/layout-marketing.pug b/services/web/app/views/layout-marketing.pug index a5c7ae5b76..528fabaa82 100644 --- a/services/web/app/views/layout-marketing.pug +++ b/services/web/app/views/layout-marketing.pug @@ -7,12 +7,15 @@ block entrypointVar - entrypoint = 'marketing' block body - if (typeof(suppressNavbar) == "undefined") - include layout/navbar-marketing + if (typeof suppressNavbar === "undefined") + if bootstrapVersion === 5 + include layout/navbar-marketing-bootstrap-5 + else + include layout/navbar-marketing block content - if (typeof(suppressFooter) == "undefined") + if (typeof suppressFooter === "undefined") if showThinFooter include layout/footer-marketing else diff --git a/services/web/app/views/layout/navbar-marketing-bootstrap-5.pug b/services/web/app/views/layout/navbar-marketing-bootstrap-5.pug new file mode 100644 index 0000000000..3bacbd65eb --- /dev/null +++ b/services/web/app/views/layout/navbar-marketing-bootstrap-5.pug @@ -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) diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx index 9202eb3492..6973ee37c9 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx @@ -11,6 +11,7 @@ import type { DropdownItemProps, DropdownToggleProps, DropdownMenuProps, + DropdownDividerProps, } from '@/features/ui/components/types/dropdown-menu-props' import MaterialIcon from '@/shared/components/material-icon' @@ -62,9 +63,9 @@ export function DropdownToggle({ ...props }: DropdownToggleProps) { } export function DropdownMenu({ as = 'ul', ...props }: DropdownMenuProps) { - return + return } -export function DropdownDivider() { - return