diff --git a/src/App/AppHeader/index.js b/src/App/AppHeader/index.js index 75eab289d5..585d689a83 100644 --- a/src/App/AppHeader/index.js +++ b/src/App/AppHeader/index.js @@ -71,7 +71,7 @@ const AppHeader = () => ( topbarContent={menu} wide="xl" logout - id="demo-topbar" + id="app-topbar" sticky={false} />
diff --git a/src/App/ComponentsDocumentation/components/Topbar/constants.js b/src/App/ComponentsDocumentation/components/Topbar/constants.js index 7b27b75bb3..5d53696f8d 100644 --- a/src/App/ComponentsDocumentation/components/Topbar/constants.js +++ b/src/App/ComponentsDocumentation/components/Topbar/constants.js @@ -1,5 +1,6 @@ -import React from "react"; +import React, { useEffect } from "react"; import TopbarComponent from "@components/Topbar"; +import { topbar } from "@src/scripts/main"; import CodeTags from "@components/CodeTags"; @@ -64,18 +65,46 @@ export const menu = { ], }; -const Topbar = ({ sticky, wide, logout }) => ( -
- -
-
-); +const menuLegacy = { + btn: { + name: "Menu", + icon: "menu", + }, + items: [ + { + name: "Home", + icon: "home", + }, + { + name: "Purchases", + icon: "shopping_cart", + }, + { + name: "Settings", + icon: "settings", + }, + ], +}; + +const Topbar = ({ sticky, wide, logout, legacy }) => { + useEffect(() => { + topbar.init(); + }, [legacy]); + + return ( +
+ +
+
+ ); +}; export const topbarShowcase = { id: "overviewTopbar", @@ -118,5 +147,43 @@ export const topbarShowcase = { ), }, + { + tab: "⚠️ Legacy Desktop", + component: , + title: "Desktop", + description: ( +

+ {/* TODO: mention it's legacy topbar and it all involve to use this in terms of breaking changes */} + The minimum requirement in a Topbar is to include the Swedbank Pay + Logotype, common additional functionality is a list with navigation + links. On desktop use{" "} + to show the links + listed horizontally in the topbar.{" "} +

+ ), + }, + { + tab: "⚠️ Legacy Mobile/tablet", + component: , + title: "Mobile/tablet", + description: ( + <> +

+ {/* TODO: mention it's legacy topbar and it all involve to use this in terms of breaking changes */} + The minimum requirement in a Topbar is to include the Swedbank Pay + Logotype, common additional functionality is a list with navigation + links. On smaller screens use a menu button to toggle a vertical + navigation drawer with links when the menu button is clicked. +

+

+ Be aware; The {" "} + attribute for element {" "} + when it is active is set to 2500px. This is to ensure animation for + the transition to happen. You can easily alter this by creating + custom css. +

+ + ), + }, ], }; diff --git a/src/App/components/Topbar/index.js b/src/App/components/Topbar/index.js index b9bc37efa8..82306c00f7 100644 --- a/src/App/components/Topbar/index.js +++ b/src/App/components/Topbar/index.js @@ -1,6 +1,7 @@ import React, { Fragment } from "react"; import PropTypes from "prop-types"; -import swedbankpayLogo from "@src/img/swedbankpay/logo/swedbankpay-logo-h.svg"; +import swedbankpayLogoHorizontal from "@src/img/swedbankpay/logo/swedbankpay-logo-h.svg"; +import swedbankpayLogoVertical from "@src/img/swedbankpay/logo/swedbankpay-logo-v.svg"; import payexLogo from "@src/img/payex/logo/payex-logo.svg"; import SidebarComponent from "@components/Sidebar"; @@ -9,28 +10,44 @@ import ButtonComponent from "@components/Button"; const brand = process.env.brand; -const devLogo = brand === "swedbankpay" ? swedbankpayLogo : payexLogo; - const isDev = process.env.version === "LOCAL_DEV"; +const getDevLogo = (legacy) => { + if (brand === "swedbankpay" && !legacy) { + return swedbankpayLogoHorizontal; + } else if (brand === "swedbankpay" && legacy) { + return swedbankpayLogoVertical; + } else { + return payexLogo; + } +}; -const TopbarBtn = () => ( - <> - - -); +const TopbarBtn = ({ legacy = false }) => { + return ( + <> + + {legacy && ( + + )} + + ); +}; -const TopbarMenu = ({ menu, logout, sidebar }) => { +const TopbarMenu = ({ menu, logout, sidebar, legacy }) => { const { items } = menu; return ( @@ -58,7 +75,7 @@ const TopbarMenu = ({ menu, logout, sidebar }) => { ))} {"\n"} - {logout ? : null} + {logout ? legacy ? : : null} ); @@ -74,7 +91,24 @@ const TopbarLogout = () => ( /> ); -const TopbarLogo = ({ png }) => ( +const TopbarLogoutLegacy = () => ( + <> + e.preventDefault()} + > + {"\n"} + exit_to_app + {"\n"} + Log out + {"\n"} + + {"\n"} + +); + +const TopbarLogo = ({ png, legacy }) => ( <> ( {brand === "swedbankpay" && png ? ( {`${brand} ) : ( {`${brand} )} {"\n"} @@ -110,22 +152,46 @@ const TopbarLogo = ({ png }) => ( ); -const Topbar = ({ topbarContent, wide, logout, id, png, sticky, sidebar }) => ( +const Topbar = ({ + topbarContent, + wide, + logout, + id, + png, + sticky, + sidebar, + legacy = false, +}) => (
{"\n"} {topbarContent ? (
+ {legacy && ( + <> + {"\n"} + + + )} {"\n"} - - {"\n"} - + {"\n"} - + + {!legacy && ( + <> + {"\n"} + + + )}
) : ( <> @@ -143,6 +209,7 @@ Topbar.propTypes = { png: PropTypes.bool, sticky: PropTypes.bool, sidebar: PropTypes.bool, + legacy: PropTypes.bool, }; export default Topbar; diff --git a/src/less/components/topbar-legacy.less b/src/less/components/topbar-legacy.less index ab4003c121..c3b849b366 100644 --- a/src/less/components/topbar-legacy.less +++ b/src/less/components/topbar-legacy.less @@ -356,11 +356,11 @@ } &.topbar-nav-open { - animation: fade-in 0.5s forwards; + animation: fade-in-legacy 0.5s forwards; border-top: solid 1px @brand-bg-gray; .topbar-link-container { - animation: slide-in 0.5s ease forwards; + animation: slide-in-legacy 0.5s ease forwards; .sidebar { display: block; @@ -399,10 +399,10 @@ } &.topbar-nav-closing { - animation: fade-out 0.3s forwards; + animation: fade-out-legacy 0.3s forwards; .topbar-link-container { - animation: slide-out 0.3s ease alternate; + animation: slide-out-legacy 0.3s ease alternate; > a:first-of-type { margin-top: 1rem; @@ -592,21 +592,21 @@ } each(@grid-breakpoints, .(@min-width, @infix) { - .topbar-@{infix}-wide { - @media screen and (min-width: @min-width) { - .topbar-wide(); - } - } + &.topbar-@{infix}-wide { + @media screen and (min-width: @min-width) { + .topbar-wide(); + } + } }); // Keyframe animations - @keyframes slide-in { + @keyframes slide-in-legacy { 100% { transform: translateX(0%); } } - @keyframes slide-out { + @keyframes slide-out-legacy { 0% { transform: translateX(0%); } @@ -616,7 +616,7 @@ } } - @keyframes fade-in { + @keyframes fade-in-legacy { 0% { background: transparent; } @@ -626,7 +626,7 @@ } } - @keyframes fade-out { + @keyframes fade-out-legacy { 0% { background: fade(@black, 50%); } @@ -654,18 +654,18 @@ } &.topbar-nav-open { - animation: fade-in 0s forwards; + animation: fade-in-legacy 0s forwards; .topbar-link-container { - animation: slide-in 0s ease forwards; + animation: slide-in-legacy 0s ease forwards; } } &.topbar-nav-closing { - animation: fade-out 0s forwards; + animation: fade-out-legacy 0s forwards; .topbar-link-container { - animation: slide-out 0s ease alternate; + animation: slide-out-legacy 0s ease alternate; } } } diff --git a/src/less/components/topbar.less b/src/less/components/topbar.less index 725299ae31..3cc9e611e1 100644 --- a/src/less/components/topbar.less +++ b/src/less/components/topbar.less @@ -14,7 +14,7 @@ height: var(--topbar-height); width: calc(100% - 2 * var(--topbar-spacing-horizontal, 32px)); max-width: var(--topbar-max-width, 960px); - z-index: var(--topbar-z-index, 400); + z-index: calc(var(--topbar-z-index, 400) - 1); display: flex; justify-content: center; align-items: center; @@ -275,224 +275,224 @@ } } } -} - -/* -LESS mixin function scoped for the topbar on wider viewport, generated by the topbar mixin -1. Create a mixin called .topbar-@{infix}-wide -2. For each breakpoint in @grid-breakpoints, create a selector called .topbar-@{infix}-wide -3. For each selector, create a media query for the breakpoint -4. Inside each media query, call the mixin .topbar-wide() -*/ -// TODO: try to refactor it to make it more readable and not use LESS mixin, but native CSS - -each(@grid-breakpoints, .(@min-width, @infix) { - .topbar-@{infix}-wide:not(.legacy) { - @media screen and (min-width: @min-width) { - .topbar-wide(); - } - } -}); -.topbar-wide { - --topbar-height: 88px; - --border-radius-topbar-nav: 48px; - --modal-nav-width: 480px; + /* + LESS mixin function scoped for the topbar on wider viewport, generated by the topbar mixin + 1. Create a mixin called .topbar-@{infix}-wide + 2. For each breakpoint in @grid-breakpoints, create a selector called .topbar-@{infix}-wide + 3. For each selector, create a media query for the breakpoint + 4. Inside each media query, call the mixin .topbar-wide() + */ + // TODO: try to refactor it to make it more readable and not use LESS mixin, but native CSS - padding: 0 var(--topbar-spacing-horizontal); + each(@grid-breakpoints, .(@min-width, @infix) { + &.topbar-@{infix}-wide:not(.legacy) { + @media screen and (min-width: @min-width) { + .topbar-wide(); + } + } + }); - .topbar-logo img { - height: var(--topbar-spacing-horizontal); - width: auto; - } + .topbar-wide { + --topbar-height: 88px; + --border-radius-topbar-nav: 48px; + --modal-nav-width: 480px; - .topbar-nav { - display: flex; - position: static; - flex-grow: 1; - height: 100%; + padding: 0 var(--topbar-spacing-horizontal); - &.topbar-nav-open { - width: 100vw; - height: 100vh; - position: fixed; - justify-content: center; - align-items: center; + .topbar-logo img { + height: var(--topbar-spacing-horizontal); + width: auto; + } - .topbar-link-container { - width: var(--modal-nav-width, 480px); - height: max-content; - max-height: calc( - 100% - 2 * var(--topbar-links-container-spacing, 60px) - ); - margin: var(--topbar-links-container-spacing, 60px); - border-radius: var(--border-radius-topbar-nav, 48px); - border: solid var(--white) var(--border-radius-topbar-nav, 48px); - padding: 0; - overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: var(--brand-primary) transparent; + .topbar-nav { + display: flex; + position: static; + flex-grow: 1; + height: 100%; + + &.topbar-nav-open { + width: 100vw; + height: 100vh; + position: fixed; + justify-content: center; + align-items: center; - &::-webkit-scrollbar { - background-color: transparent; - width: 10px; - } + .topbar-link-container { + width: var(--modal-nav-width, 480px); + height: max-content; + max-height: calc( + 100% - 2 * var(--topbar-links-container-spacing, 60px) + ); + margin: var(--topbar-links-container-spacing, 60px); + border-radius: var(--border-radius-topbar-nav, 48px); + border: solid var(--white) var(--border-radius-topbar-nav, 48px); + padding: 0; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--brand-primary) transparent; + + &::-webkit-scrollbar { + background-color: transparent; + width: 10px; + } - &::-webkit-scrollbar-thumb { - background: var(--brand-primary); - border-radius: 5rem; - } + &::-webkit-scrollbar-thumb { + background: var(--brand-primary); + border-radius: 5rem; + } - > a, - > a:not(.pinned) { - display: flex; - font-size: 1.5rem; - padding: 1.5rem 0; + > a, + > a:not(.pinned) { + display: flex; + font-size: 1.5rem; + padding: 1.5rem 0; + } } } - } - // links container when in topbar only, not opened in dialog - &:not(.topbar-nav-open) .topbar-link-container { - position: static; - background-color: transparent; - width: 100%; - display: flex; - justify-content: flex-start; - gap: 2rem; - padding: 0 2rem 0 3rem; - overflow: visible; + // links container when in topbar only, not opened in dialog + &:not(.topbar-nav-open) .topbar-link-container { + position: static; + background-color: transparent; + width: 100%; + display: flex; + justify-content: flex-start; + gap: 2rem; + padding: 0 2rem 0 3rem; + overflow: visible; - > :is(a, button):not(.pinned) { - display: none; - } + > :is(a, button):not(.pinned) { + display: none; + } - // :hover underline expand width effects in non-opened topbar links - > a { - &:hover { - > span { - &:before { - display: block; - width: 100%; - transition: width 0.4s; + // :hover underline expand width effects in non-opened topbar links + > a { + &:hover { + > span { + &:before { + display: block; + width: 100%; + transition: width 0.4s; + } + + text-decoration: none; } - - text-decoration: none; } - } - &.active { - > span { - &:before { - width: 100%; - background-color: var(--brand-primary); - } + &.active { + > span { + &:before { + width: 100%; + background-color: var(--brand-primary); + } - text-decoration: none; - color: @black; + text-decoration: none; + color: @black; + } } - } - > span { - position: relative; + > span { + position: relative; - &:before { - content: ""; - position: absolute; - background-color: currentcolor; - height: 2px; - bottom: -4px; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - width: 0; + &:before { + content: ""; + position: absolute; + background-color: currentcolor; + height: 2px; + bottom: -4px; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + width: 0; + } } } } } } -} -@keyframes fade-in { - 0% { - backdrop-filter: blur(0); - background-color: transparent; - } + @keyframes fade-in { + 0% { + backdrop-filter: blur(0); + background-color: transparent; + } - 33% { - background-color: color-mix(in srgb, @brand-primary, transparent 40%); - } + 33% { + background-color: color-mix(in srgb, @brand-primary, transparent 40%); + } - 100% { - backdrop-filter: blur(20px); - background-color: color-mix(in srgb, @brand-primary, transparent 40%); + 100% { + backdrop-filter: blur(20px); + background-color: color-mix(in srgb, @brand-primary, transparent 40%); + } } -} -@keyframes fade-out { - 0% { - backdrop-filter: blur(20px); - background-color: color-mix(in srgb, @brand-primary, transparent 40%); - position: fixed; - inset: 0; - width: 100vw; - height: 100vh; - } + @keyframes fade-out { + 0% { + backdrop-filter: blur(20px); + background-color: color-mix(in srgb, @brand-primary, transparent 40%); + position: fixed; + inset: 0; + width: 100vw; + height: 100vh; + } - 1% { - backdrop-filter: blur(20px); - } + 1% { + backdrop-filter: blur(20px); + } - 33% { - backdrop-filter: blur(20px); - background-color: color-mix(in srgb, @brand-primary, transparent 40%); - } + 33% { + backdrop-filter: blur(20px); + background-color: color-mix(in srgb, @brand-primary, transparent 40%); + } - 99% { - position: fixed; - inset: 0; - width: 100vw; - height: 100vh; - background: transparent; - } + 99% { + position: fixed; + inset: 0; + width: 100vw; + height: 100vh; + background: transparent; + } - 100% { - backdrop-filter: blur(0); - background-color: transparent; + 100% { + backdrop-filter: blur(0); + background-color: transparent; + } } -} -@keyframes slide-in-from-right { - from { - transform: translateX(100%); - } + @keyframes slide-in-from-right { + from { + transform: translateX(100%); + } - to { - transform: translateX(0); + to { + transform: translateX(0); + } } -} - -@media (prefers-reduced-motion) { - // TODO: once finish animations / transition -> go through each & disable any animation/transition here - .topbar:not(.legacy) - nav.topbar-nav:is(.topbar-nav-open, .topbar-nav-closing) { - animation-duration: 0ms; - & > .topbar-link-container { + @media (prefers-reduced-motion) { + // TODO: once finish animations / transition -> go through each & disable any animation/transition here + .topbar:not(.legacy) + nav.topbar-nav:is(.topbar-nav-open, .topbar-nav-closing) { animation-duration: 0ms; + + & > .topbar-link-container { + animation-duration: 0ms; + } } } -} -@media (forced-colors: active) { - a { - border-bottom: 1px solid; + @media (forced-colors: active) { + a { + border-bottom: 1px solid; - &:focus, - &:hover, - &.active { - border-bottom: 4px solid; + &:focus, + &:hover, + &.active { + border-bottom: 4px solid; + } } } } diff --git a/src/scripts/main/topbar/NavMenu.js b/src/scripts/main/topbar/NavMenu.js index 4e9e950db3..4a639a1ce1 100644 --- a/src/scripts/main/topbar/NavMenu.js +++ b/src/scripts/main/topbar/NavMenu.js @@ -10,9 +10,6 @@ const SELECTORS = { const FOCUSELEMENTS = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]'; -const isLegacyMenu = () => - document.querySelector(".topbar").classList.contains("legacy"); - export default class NavMenu { constructor(topbarComponent, navMenu) { this._openHandler = this._openHandler.bind(this); @@ -32,7 +29,7 @@ export default class NavMenu { this.isOpen = navMenu.classList.contains(SELECTORS.OPEN); this.navMenuElement = navMenu; this.linkContainer = this.navMenuElement.querySelector( - ".topbar-link-container" + ".topbar-link-container", ); this.closeNavIcon = topbarComponent.querySelector(".topbar-close"); this.btnElement = topbarComponent.querySelector(SELECTORS.BTN); @@ -58,7 +55,7 @@ export default class NavMenu { if (this.navMenuElement) { this.navMenuElement.addEventListener( "mousedown", - this._closeHandlerNavMenuElement + this._closeHandlerNavMenuElement, ); try { @@ -93,7 +90,7 @@ export default class NavMenu { this.navMenuElement .querySelectorAll("a") .forEach((anchor) => - anchor.addEventListener("click", this._closeHandler) + anchor.addEventListener("click", this._closeHandler), ); } @@ -110,10 +107,10 @@ export default class NavMenu { window.removeEventListener("resize", this.resizeEvent, { passive: true }); this.navMenuElement.classList.remove("topbar-nav-open"); - if (isLegacyMenu()) { + if (this._isLegacyMenu(this.navMenuElement)) { this.navMenuElement.style.display = "none"; - this.btnElement.style.display = "flex"; this.closeNavIcon.style.display = "none"; + this.btnElement.style.display = "flex"; } } @@ -131,7 +128,8 @@ export default class NavMenu { window.addEventListener("resize", this.resizeEvent, { passive: true }); this.navMenuElement.classList.add("topbar-nav-open"); - if (isLegacyMenu()) { + if (this._isLegacyMenu(this.navMenuElement)) { + this.navMenuElement.style.display = "block"; this.btnElement.style.display = "none"; this.closeNavIcon.style.display = "flex"; } @@ -152,8 +150,9 @@ export default class NavMenu { this.navMenuElement.addEventListener( "animationend", - this._removesNavClosing + this._removesNavClosing, ); + this._safetyClosingCleanIfDidNotReachEnd(); this.btnElement.setAttribute("aria-expanded", "false"); @@ -170,14 +169,15 @@ export default class NavMenu { this.navMenuElement.classList.remove("topbar-nav-closing"); - if (isLegacyMenu()) { + if (this._isLegacyMenu(this.navMenuElement)) { + this.navMenuElement.style.display = "none"; this.btnElement.style.display = "flex"; this.closeNavIcon.style.display = "none"; } this.navMenuElement.removeEventListener( "animationend", - this._removesNavClosing + this._removesNavClosing, ); } @@ -188,4 +188,8 @@ export default class NavMenu { : null; }, 600); } + + _isLegacyMenu(navMenu) { + return Boolean(navMenu?.closest(".topbar")?.classList.contains("legacy")); + } }