diff --git a/frontend-next-migration/src/app/[lng]/(helper)/layout.tsx b/frontend-next-migration/src/app/[lng]/(helper)/layout.tsx index eb9567c2f..dfbc8836f 100644 --- a/frontend-next-migration/src/app/[lng]/(helper)/layout.tsx +++ b/frontend-next-migration/src/app/[lng]/(helper)/layout.tsx @@ -1,6 +1,6 @@ import { ReactNode } from 'react'; import { Footer } from '@/widgets/Footer'; -import { Navbar } from '@/widgets/Navbar'; +import { Navbar } from '@/widgets/NavbarV3'; import { ScrollTop } from '@/features/ScrollTop'; import LayoutDefault from '../../../preparedPages/Layouts/ui/LayoutDefault/LayoutDefault'; diff --git a/frontend-next-migration/src/app/[lng]/(home)/layout.tsx b/frontend-next-migration/src/app/[lng]/(home)/layout.tsx index ce54256ce..237f82161 100644 --- a/frontend-next-migration/src/app/[lng]/(home)/layout.tsx +++ b/frontend-next-migration/src/app/[lng]/(home)/layout.tsx @@ -1,6 +1,6 @@ 'use client'; import { ReactNode } from 'react'; -import { Navbar } from '@/widgets/Navbar'; +import { Navbar } from '@/widgets/NavbarV3'; import { Footer } from '@/widgets/Footer'; import { ScrollTop } from '@/features/ScrollTop'; import cls from './homeLayout.module.scss'; diff --git a/frontend-next-migration/src/app/[lng]/(intro)/layout.tsx b/frontend-next-migration/src/app/[lng]/(intro)/layout.tsx index c1525286c..124b7ae93 100644 --- a/frontend-next-migration/src/app/[lng]/(intro)/layout.tsx +++ b/frontend-next-migration/src/app/[lng]/(intro)/layout.tsx @@ -1,6 +1,6 @@ import { ReactNode } from 'react'; import { Footer } from '@/widgets/Footer'; -import { Navbar } from '@/widgets/Navbar'; +import { Navbar } from '@/widgets/NavbarV3'; type Props = { children: ReactNode; diff --git a/frontend-next-migration/src/shared/assets/icons/profileIcon.svg b/frontend-next-migration/src/shared/assets/icons/profileIcon.svg index a15d67c1e..64b7bc285 100644 --- a/frontend-next-migration/src/shared/assets/icons/profileIcon.svg +++ b/frontend-next-migration/src/shared/assets/icons/profileIcon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/frontend-next-migration/src/shared/assets/icons/profileiconV3.png b/frontend-next-migration/src/shared/assets/icons/profileiconV3.png new file mode 100644 index 000000000..3a636adf0 Binary files /dev/null and b/frontend-next-migration/src/shared/assets/icons/profileiconV3.png differ diff --git a/frontend-next-migration/src/shared/assets/images/altLogo.png b/frontend-next-migration/src/shared/assets/images/altLogo.png index 388432320..3a636adf0 100644 Binary files a/frontend-next-migration/src/shared/assets/images/altLogo.png and b/frontend-next-migration/src/shared/assets/images/altLogo.png differ diff --git a/frontend-next-migration/src/shared/assets/images/altLogoOld.png b/frontend-next-migration/src/shared/assets/images/altLogoOld.png new file mode 100644 index 000000000..388432320 Binary files /dev/null and b/frontend-next-migration/src/shared/assets/images/altLogoOld.png differ diff --git a/frontend-next-migration/src/shared/i18n/locales/en/navbar.json b/frontend-next-migration/src/shared/i18n/locales/en/navbar.json index 1f5674411..0893727d4 100644 --- a/frontend-next-migration/src/shared/i18n/locales/en/navbar.json +++ b/frontend-next-migration/src/shared/i18n/locales/en/navbar.json @@ -2,14 +2,14 @@ "main": "Main page", "forum": "Forum", "heroes": "Heroes", - "team": "Team", + "team": "Contact us", "art": "Art", "artGame": "Game art", "teachingPackage": "Teaching package", "login": "Log in", "profile": "Profile", "logout": "Log out", - "community": "Community", + "community": "PRG", "feedback": "Feedback", "game": "Game", "clans": "Clans", @@ -21,7 +21,7 @@ "SignUpFirst!": "Sign up first!", "join": "Join us", "clanpage": "Clan Page", - "gameart": "Game Art", + "gameart": "Education", "gallery": "Gallery", "news": "News", "dlPackage": "Education package", @@ -32,5 +32,8 @@ "teachers": "Teachers", "furnituresets": "Furniture", "collections": "Collections", - "about": "About" + "about": "About", + "education": "Education", + "prg": "PRG", + "contactUs": "Contact us" } diff --git a/frontend-next-migration/src/shared/i18n/locales/fi/navbar.json b/frontend-next-migration/src/shared/i18n/locales/fi/navbar.json index 8de28b72a..8f22ccba6 100644 --- a/frontend-next-migration/src/shared/i18n/locales/fi/navbar.json +++ b/frontend-next-migration/src/shared/i18n/locales/fi/navbar.json @@ -2,14 +2,14 @@ "main": "Pääsivu", "forum": "Foorumi", "heroes": "Hahmot", - "team": "Tiimi", + "team": "Ota yhteyttä", "art": "Taide", "artGame": "Pelitaide", "teachingPackage": "Opetuspaketti", "login": "Kirjaudu", "profile": "Profiili", "logout": "Kirjaudu ulos", - "community": "Yhteisö", + "community": "PRG", "feedback": "Palaute", "game": "Peli", "clans": "Klaanit", @@ -19,7 +19,7 @@ "SignUpFirst!": "Kirjadu ensin!", "join": "Tule mukaan", "clanpage": "Klaanisivu", - "gameart": "Pelitaide", + "gameart": "Opetus", "gallery": "Galleria", "news": "Uutiset", "dlPackage": "Opetuspaketti", @@ -30,5 +30,8 @@ "teachers": "Opettajat", "furnituresets": "Huonekalumallistot", "collections": "Kokoelmat", - "about": "Meistä" + "about": "Meistä", + "education": "Opetus", + "prg": "PRG", + "contactUs": "Ota yhteyttä" } diff --git a/frontend-next-migration/src/shared/ui/DropdownWrapper/ui/DropdownWrapper.module.scss b/frontend-next-migration/src/shared/ui/DropdownWrapper/ui/DropdownWrapper.module.scss index d65f2ace2..2cccd14f3 100644 --- a/frontend-next-migration/src/shared/ui/DropdownWrapper/ui/DropdownWrapper.module.scss +++ b/frontend-next-migration/src/shared/ui/DropdownWrapper/ui/DropdownWrapper.module.scss @@ -57,7 +57,7 @@ } .childrenWrapper { display: flex; - inline-size: 110px; + inline-size: fit-content; overflow-wrap: break-word; } diff --git a/frontend-next-migration/src/widgets/NavbarV3/index.ts b/frontend-next-migration/src/widgets/NavbarV3/index.ts new file mode 100644 index 000000000..fb26b4a9b --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/index.ts @@ -0,0 +1 @@ +export { NavbarMain as Navbar } from './ui/NavbarMain/NavbarMain'; diff --git a/frontend-next-migration/src/widgets/NavbarV3/model/data/NavbarBuilder.ts b/frontend-next-migration/src/widgets/NavbarV3/model/data/NavbarBuilder.ts new file mode 100644 index 000000000..8678ea88e --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/model/data/NavbarBuilder.ts @@ -0,0 +1,47 @@ +import { ItemType, NamedMenu, NavbarBuild, NavbarMenuItem, NavLogoObject } from '../types'; +import { DropDownElement } from '@/shared/ui/DropdownWrapper'; + +/** Helps build the navbar menu config step by step. */ +export class NavbarBuilder { + private menu: NavbarMenuItem[] = []; + private namedMenu: NamedMenu = {}; + + /** Add a plain link. */ + addLink(name: string, path: string): void { + this.menu.push({ + name, + path, + type: ItemType.navLink, + }); + } + + /** + * Add a dropdown. + * @param path - If set, the label becomes a clickable link too. + */ + addDropDown(name: string, elements: Array, path?: string): void { + this.menu.push({ + name, + path, + elements, + type: ItemType.navDropDown, + }); + } + + /** Add the logo. */ + addLogo(name: string, src: string, path: string): void { + const logoObject = { + name, + src, + path, + type: ItemType.navLogo, + } as NavLogoObject; + this.namedMenu[ItemType.navLogo] = logoObject; + this.menu.push(logoObject); + } + + /** Wrap up and return the built config. */ + build(): NavbarBuild { + return { menu: this.menu, namedMenu: this.namedMenu }; + } +} diff --git a/frontend-next-migration/src/widgets/NavbarV3/model/data/navbarMenuDesktop.ts b/frontend-next-migration/src/widgets/NavbarV3/model/data/navbarMenuDesktop.ts new file mode 100644 index 000000000..8c4160f32 --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/model/data/navbarMenuDesktop.ts @@ -0,0 +1,20 @@ +import img from '@/shared/assets/images/altLogo.png'; +import { dropdowns } from '@/widgets/Navbar/model/data/dropdowns'; +import { + getRouteTeamPage, + getRouteMainPage, + getRouteAllNewsPage, + getRouteGameArtPage, +} from '@/shared/appLinks/RoutePaths'; +import { NavbarBuilder } from './NavbarBuilder'; + +const navbarBuilder = new NavbarBuilder(); +navbarBuilder.addLogo('Nav logo', img as unknown as string, getRouteMainPage()); +navbarBuilder.addLink('news', getRouteAllNewsPage()); +navbarBuilder.addDropDown('game', dropdowns.game); +navbarBuilder.addDropDown('gallery', dropdowns.gallery); +navbarBuilder.addDropDown('education', dropdowns.gameart, getRouteGameArtPage()); +navbarBuilder.addDropDown('community', dropdowns.community); +navbarBuilder.addLink('contactUs', getRouteTeamPage()); + +export const navbarMenuDesktop = navbarBuilder.build(); diff --git a/frontend-next-migration/src/widgets/NavbarV3/model/data/navbarMenuMobile.ts b/frontend-next-migration/src/widgets/NavbarV3/model/data/navbarMenuMobile.ts new file mode 100644 index 000000000..f06d03fb6 --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/model/data/navbarMenuMobile.ts @@ -0,0 +1,20 @@ +import { NavbarBuilder } from './NavbarBuilder'; +import { + getRouteMainPage, + getRouteAllNewsPage, + getRouteDefenseGalleryPage, + getRouteGalleryPage, + getRouteGameArtPage, + getRouteTeamPage, +} from '@/shared/appLinks/RoutePaths'; +import img from '@/shared/assets/images/altLogo.png'; + +const navbarBuilder = new NavbarBuilder(); +navbarBuilder.addLink('news', getRouteAllNewsPage()); +navbarBuilder.addLink('game', getRouteDefenseGalleryPage()); +navbarBuilder.addLink('gallery', getRouteGalleryPage()); +navbarBuilder.addLink('education', getRouteGameArtPage()); +navbarBuilder.addLink('contactUs', getRouteTeamPage()); +navbarBuilder.addLogo('main', img as unknown as string, getRouteMainPage()); + +export const navbarMenuMobile = navbarBuilder.build(); diff --git a/frontend-next-migration/src/widgets/NavbarV3/model/getNavbarBuildBySize.ts b/frontend-next-migration/src/widgets/NavbarV3/model/getNavbarBuildBySize.ts new file mode 100644 index 000000000..7e909ef8c --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/model/getNavbarBuildBySize.ts @@ -0,0 +1,9 @@ +import { navbarMenuDesktop } from './data/navbarMenuDesktop'; +import { navbarMenuMobile } from './data/navbarMenuMobile'; + +export const getNavbarBuildBySize = (size: 'mobile' | 'tablet' | 'desktop') => { + if (size === 'desktop') { + return navbarMenuDesktop; + } + return navbarMenuMobile; +}; diff --git a/frontend-next-migration/src/widgets/NavbarV3/model/types/index.ts b/frontend-next-migration/src/widgets/NavbarV3/model/types/index.ts new file mode 100644 index 000000000..c6b5856c5 --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/model/types/index.ts @@ -0,0 +1,46 @@ +import { DropDownElement } from '@/shared/ui/DropdownWrapper'; + +/** Tells apart the different kinds of navbar items. */ +export enum ItemType { + navLink = 'navLink', + navLogo = 'navLogo', + navDropDown = 'navDropDown', +} + +/** A basic link that goes somewhere. */ +export type NavbarLinkObject = { + name: string; + path: string; + type: ItemType.navLink; +}; + +/** A dropdown that shows a popup menu; can also act as a link if `path` is set. */ +export type NavbarDropDownObject = { + name: string; + /** Where clicking the label takes you (optional). */ + path?: string; + elements: Array; + type: ItemType.navDropDown; +}; + +/** The site logo in the navbar. */ +export type NavLogoObject = { + name: string; + src: string; + path: string; + type: ItemType.navLogo; +}; + +/** Any item that can appear in the navbar menu. */ +export type NavbarMenuItem = NavbarLinkObject | NavLogoObject | NavbarDropDownObject; + +export type NavbarMenu = NavbarMenuItem[]; + +export type NamedMenu = { + [ItemType.navLogo]?: NavLogoObject; +}; + +export type NavbarBuild = { + menu: NavbarMenu; + namedMenu: NamedMenu; +}; diff --git a/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavItem.tsx b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavItem.tsx new file mode 100644 index 000000000..28da3b1aa --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavItem.tsx @@ -0,0 +1,116 @@ +'use client'; +import Image from 'next/image'; +import { memo } from 'react'; +import { useClientTranslation } from '@/shared/i18n'; +import { AppLink, AppLinkTheme } from '@/shared/ui/AppLink/AppLink'; +import { DropdownWrapper } from '@/shared/ui/DropdownWrapper'; +import { NavbarMenuItem, ItemType } from '../../model/types'; +import cls from './NavbarDesktop.module.scss'; +import { faChevronDown } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +type NavItemProps = { + /** The menu item to render. */ + item: NavbarMenuItem; + /** Is the navbar being hovered? (opens dropdowns). */ + mouseOver?: boolean; + /** This dropdown's index among all dropdowns (for horizontal positioning). */ + dropdownIndex?: number; + /** Total number of dropdowns (for horizontal centering). */ + totalDropdowns?: number; +}; + +const CHEVRON_ITEMS = new Set(['game', 'gallery', 'gameart', 'community']); + +/** Renders one navbar item — a link, a dropdown (optionally linked), or the logo. */ +const NavItem = memo((props: NavItemProps) => { + const { item, mouseOver = false } = props; + const { type: itemType } = item; + const { t } = useClientTranslation('navbar'); + + if (itemType === ItemType.navDropDown) { + const { dropdownIndex = 0, totalDropdowns = 1 } = props; + const gap = 220; + const offset = (dropdownIndex - (totalDropdowns - 1) / 2) * gap; + const elements = item.elements.map((el) => { + if (el && typeof el === 'object' && 'elementText' in el) { + return { ...el, elementText: t(`${el.elementText}`) }; + } + return el; + }); + + const trigger = ( + <> + {t(`${item.name}`)} + {CHEVRON_ITEMS.has(item.name) && ( + + )} + + ); + + return ( +
  • + + {item.path ? ( + + {trigger} + + ) : ( + trigger + )} + +
  • + ); + } + + if (itemType === 'navLink') { + return ( +
  • + + {t(`${item.name}`)} + +
  • + ); + } + + if (itemType === 'navLogo') { + return ( + + {item.name + + ); + } + + return null; +}); + +NavItem.displayName = 'NavItemV3'; + +export default NavItem; diff --git a/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavbarDesktop.module.scss b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavbarDesktop.module.scss new file mode 100644 index 000000000..f82d67975 --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavbarDesktop.module.scss @@ -0,0 +1,373 @@ +.navbar { + position: absolute; + top: 0; + left: -1px; + width: 100%; + height: 110px; + background: #1e3544; + border-bottom: 2px solid #121212; + z-index: var(--navbar-z-index); + user-select: none; + overflow-y: visible; + + &.collapsed { + overflow: hidden; + } +} + +.inner { + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + padding: 0 0 0 32px; + gap: 20px; + width: 100%; + height: 110px; + transition: width 0.3s ease, height 0.3s ease; + position: relative; + + &.collapsed { + width: 50px; + overflow: hidden; + } + +} + +.logoSlot { + position: relative; + flex: none; + width: 90px; + height: 92px; + transition: opacity 0.3s ease, transform 0.3s ease; + + &.collapsed { + opacity: 0; + transform: scaleX(0); + pointer-events: none; + } + + a { + display: block; + position: absolute; + width: 109px; + height: 92px; + left: calc(50% - 109px / 2 - 1px); + top: calc(50% - 92px / 2); + } + + img { + width: 109px; + height: 92px; + object-fit: contain; + } +} + +.navLinks { + display: flex; + flex-direction: row; + align-items: center; + padding: 0; + gap: 0; + flex: 1; + height: 60px; + list-style: none; + margin: 0; + justify-content: flex-start; + + a, + .col { + font-family: 'DM Sans', sans-serif; + font-style: normal; + font-weight: 700; + font-size: 24px; + line-height: 31px; + color: #faf9f6; + text-decoration: none; + white-space: nowrap; + } + + li { + display: flex; + flex-direction: row; + align-items: center; + padding: 0 24px; + gap: 10px; + height: 60px; + border-radius: 12px; + flex: none; + position: relative; + cursor: pointer; + transition: background 0.15s ease; + width: fit-content; + + &:hover { + background: rgba(255, 255, 255, 0.06); + } + } +} + +.col { + font-family: 'DM Sans', sans-serif; + font-style: normal; + font-weight: 700; + font-size: 24px; + line-height: 31px; + color: #faf9f6; + text-decoration: none; + white-space: nowrap; +} + +.chevron { + margin-left: 6px; + font-size: 18px; + color: #faf9f6; +} + +.actions { + display: flex; + flex-direction: row; + align-items: center; + gap: 30px; + width: 250px; + height: 38px; + flex: none; + margin-left: auto; + transition: opacity 0.3s ease, transform 0.3s ease; + + &.collapsed { + opacity: 0; + transform: translateX(20px); + pointer-events: none; + } +} + +.iconBtn { + display: flex; + justify-content: center; + align-items: center; + padding: 4px; + width: 48px; + height: 38px; + background: none; + border: none; + cursor: pointer; + border-radius: 8px; + transition: background 0.15s ease; + + &:hover { + background: rgba(255, 255, 255, 0.08); + } + + img { + width: 30px; + height: 30px; + filter: brightness(0) invert(1); + } +} + +.authWrapper { + position: relative; + display: flex; + align-items: center; +} + +.authContainer { + position: relative; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.authTrigger { + cursor: pointer; + z-index: 1; +} + +.authDropdown { + opacity: 0; + pointer-events: none; + position: absolute; + top: 5px; + right: 0; + width: max-content; + max-width: 300px; + transform: translateY(-4px) scale(0.98); + font-size: 0.7em; + max-height: 100px; + border-radius: var(--border-radius-lg); + color: var(--primary-color); + padding: 15px; + transition: opacity 0.1s cubic-bezier(0.4, 0, 0.2, 1), transform 0.1s cubic-bezier(0.4, 0, 0.2, 1); + transition-delay: 0s, 0s; + background: #1e3544; + + form { + background: #1e3544; + } + + &.authDropdownVisible { + opacity: 1; + pointer-events: auto; + transform: translateY(0) scale(1); + transition-delay: 0.2s, 0.2s; + } +} + +.authDropdownContent { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-items: flex-start; + min-width: 160px; + padding: 14px 0; +} + +.logoutButton { + margin-top: 8px; + cursor: pointer; + background-color: var(--primary-color); + border: none; + border-radius: var(--border-radius-xl); + box-shadow: 2px 2px var(--black); + width: 260px; + padding: 10px 0; + color: var(--black); + font-weight: 600; + font-size: 1em; + text-align: center; +} + +.dropdownItem { + padding: 12px 36px; + min-width: 180px; + display: block; + white-space: nowrap; +} + +.langSwitcher { + display: flex; + align-items: center; + cursor: pointer; + position: relative; + + > div { + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + } + + ul { + position: absolute; + top: 100%; + right: 0; + z-index: 10; + } + + img { + width: 30px; + height: 30px; + filter: brightness(0) invert(1); + } +} + +.navDropdownCenter { + position: fixed !important; + left: var(--az-dropdown-left, 50%) !important; + top: 110px !important; + transform: translateX(-50%) !important; + width: max-content; + max-width: 90vw; +} + +@media (max-width: breakpoint(xl)) { + .inner { + padding: 0 0 0 16px; + gap: 12px; + } + .navLinks { + a, + .col { + font-size: 20px; + line-height: 26px; + } + li { + padding: 0 14px; + } + } + .col { + font-size: 20px; + line-height: 26px; + } + .actions { + gap: 16px; + width: 200px; + } + .logoSlot { + width: 70px; + height: 72px; + a { + width: 85px; + height: 72px; + left: calc(50% - 85px / 2 - 1px); + top: calc(50% - 72px / 2); + } + img { + width: 85px; + height: 72px; + } + } +} + +@media (max-width: breakpoint(lg)) { + .inner { + padding: 0 0 0 12px; + gap: 8px; + } + .navLinks { + a, + .col { + font-size: 16px; + line-height: 20px; + } + li { + padding: 0 10px; + } + } + .col { + font-size: 16px; + line-height: 20px; + } + .actions { + gap: 10px; + width: 150px; + } + .iconBtn { + width: 36px; + height: 30px; + img { + width: 22px; + height: 22px; + } + } + .logoSlot { + width: 50px; + height: 52px; + a { + width: 65px; + height: 52px; + left: calc(50% - 65px / 2 - 1px); + top: calc(50% - 52px / 2); + } + img { + width: 65px; + height: 52px; + } + } + .langSwitcher img { + width: 24px; + height: 24px; + } +} diff --git a/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavbarDesktop.tsx b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavbarDesktop.tsx new file mode 100644 index 000000000..1d4e6100d --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarDesktopV3/NavbarDesktop.tsx @@ -0,0 +1,187 @@ +'use client'; +import { CSSProperties, memo, useState } from 'react'; +import Image from 'next/image'; +import { classNames } from '@/shared/lib/classNames/classNames'; +import { useClientTranslation } from '@/shared/i18n'; +import { useDropdownManager } from '@/shared/lib/hooks/useDropdownManager'; +import { useLogoutMutation, useUserPermissionsV2 } from '@/entities/Auth'; +import { LoginForm } from '@/features/AuthByUsername'; +import { LangSwitcher } from '@/features/LangSwitcher'; +import { NavbarBuild } from '../../model/types'; +import cls from './NavbarDesktop.module.scss'; +import NavItem from './NavItem'; +import profileIcon from '@/shared/assets/icons/profileIcon.svg'; +import searchIcon from '@/shared/assets/icons/search.png'; + +export interface NavbarProps { + /** Pushes the bar down by this many pixels. */ + marginTop?: number; + className?: string; + /** The menu structure (links, dropdowns, logo). */ + navbarBuild: NavbarBuild; + /** Collapse into a thin strip when true. */ + isCollapsed?: boolean; +} + +/** Desktop navbar — hover dropdowns, auth/lang toggles, collapse support. */ +const NavbarDesktop = memo((props: NavbarProps) => { + const { navbarBuild, marginTop, className = '', isCollapsed = false } = props; + + const [isMouseOver, setIsMouseOver] = useState(false); + const { checkPermissionFor } = useUserPermissionsV2(); + const permissionToLogin = checkPermissionFor('login'); + const permissionToLogout = checkPermissionFor('logout'); + const [logout] = useLogoutMutation(); + const { t } = useClientTranslation('auth'); + + const authDropdown = useDropdownManager(); + const langDropdown = useDropdownManager(); + + const style = marginTop ? ({ marginTop: `${marginTop}px` } as CSSProperties) : {}; + + const nonLogoItems = navbarBuild.menu.filter((item) => item.type !== 'navLogo'); + let dropIdx = 0; + const dropdownIndices = new Map(); + nonLogoItems.forEach((item) => { + if (item.type === 'navDropDown') { + dropdownIndices.set(item.name, dropIdx++); + } + }); + const totalDropdowns = dropIdx; + + const handleDropdownClick = (dropdown: 'auth' | 'lang') => { + if (dropdown === 'auth') { + authDropdown.actions.toggle(); + if (!authDropdown.state.isToggled) { + langDropdown.actions.reset(); + } + } else { + langDropdown.actions.toggle(); + if (!langDropdown.state.isToggled) { + authDropdown.actions.reset(); + } + } + }; + + return ( + + ); +}); + +NavbarDesktop.displayName = 'NavbarDesktopV3'; + +export default NavbarDesktop; diff --git a/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMain/NavbarMain.tsx b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMain/NavbarMain.tsx new file mode 100644 index 000000000..86144dd4f --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMain/NavbarMain.tsx @@ -0,0 +1,38 @@ +'use client'; +import { memo, useMemo } from 'react'; +import useSizes from '@/shared/lib/hooks/useSizes'; +import { getNavbarBuildBySize } from '../../model/getNavbarBuildBySize'; +import NavbarDesktop from '../NavbarDesktopV3/NavbarDesktop'; +import NavbarMobile from '../NavbarMobileV3/NavbarMobile'; + +interface NavbarMainProps { + marginTop?: number; + className?: string; +} + +export const NavbarMain = memo((props: NavbarMainProps) => { + const { marginTop, className } = props; + + const { isMobileSize, isTabletSize } = useSizes(); + const isTouchSize = isMobileSize || isTabletSize; + + const size = useMemo(() => (isTouchSize ? 'mobile' : 'desktop'), [isTouchSize]); + + const navbarBuild = useMemo(() => getNavbarBuildBySize(size), [size]); + + return isTouchSize ? ( + + ) : ( + + ); +}); + +NavbarMain.displayName = 'NavbarMain'; diff --git a/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMobileV3/NavbarMobile.module.scss b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMobileV3/NavbarMobile.module.scss new file mode 100644 index 000000000..06e9b32c1 --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMobileV3/NavbarMobile.module.scss @@ -0,0 +1,109 @@ +.Navbar { + position: absolute; + font: var(--font-dm-bold-l); + background-color: var(--base-card-background); + border-radius: 0px; + width: 100%; + height: 66px; + left: 0px; + top: 0px; + z-index: var(--navbar-z-index); +} + +.NavbarContent { + display: flex; + height: 66px; + align-items: center; + justify-content: space-between; +} + +.NavbarDropdown { + display: flex; + justify-content: center; + transform: scaleY(0); + transform-origin: top; + max-height: 0; + transition: transform 0.3s ease, max-height 0.3s ease; + z-index: var(--navbar-z-index); + background: var(--base-card-background); +} + +.openDropdown { + padding: 10px; + transform: scaleY(1); + max-height: 450px; +} + +.HamurgerBtn { + position: absolute; + margin-right: 15px; + right: 0; +} + +.NavbarMobile__center { + margin-left: auto !important; + margin-right: auto !important; +} + +.navLogo { + height: 100%; + + img { + height: 100%; + width: auto; + } +} + +.buttonContainer { + display: flex; + margin-right: 15px; + text-align: center; + position: absolute; + justify-content: flex-end; + align-items: center; + flex-direction: row; + left: 15px; + width: fit-content; + gap: 5px; +} + +.navItem { + opacity: 1; + transform: translateX(0) scaleX(1); + transform-origin: right; + transition: transform 0.3s ease-out, opacity 0.3s ease-out; + visibility: visible; +} + +.authDropdownContent { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.authFormContainer { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 10px; +} + +.profileLabel { + font-size: 1.2em; +} + +.logoutButton { + background-color: var(--primary-color); + border: none; + border-radius: var(--border-radius-xl); + box-shadow: 2px 2px var(--black); + width: 100%; + padding: 10px; + cursor: pointer; +} + +.logoutButton:hover { + background-color: rgba(255, 0, 0, 0.2); +} diff --git a/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMobileV3/NavbarMobile.tsx b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMobileV3/NavbarMobile.tsx new file mode 100644 index 000000000..52c880f12 --- /dev/null +++ b/frontend-next-migration/src/widgets/NavbarV3/ui/NavbarMobileV3/NavbarMobile.tsx @@ -0,0 +1,139 @@ +'use client'; +import Image from 'next/image'; +import { CSSProperties, memo, useMemo, useState } from 'react'; +import { useLogoutMutation, useUserPermissionsV2 } from '@/entities/Auth'; +import { classNames } from '@/shared/lib/classNames/classNames'; +import { useClientTranslation } from '@/shared/i18n'; +import { LoginForm } from '@/features/AuthByUsername'; +import { AppLink, AppLinkTheme } from '@/shared/ui/AppLink/AppLink'; +import { NavMenu, INavMenuItem, NavMenuItemType } from '@/shared/ui/NavMenu'; +import { ItemType, NavbarBuild } from '../../model/types'; +import cls from './NavbarMobile.module.scss'; +import profileIcon from '@/shared/assets/icons/profileIcon.svg'; +import hamburgerIcon from '@/shared/assets/icons/hamburgerIcon.svg'; +import closeIcon from '@/shared/assets/icons/closeIcon.svg'; + +type DropdownType = 'hamburger' | 'auth' | null; + +export interface NavbarTouchProps { + marginTop?: number; + navbarBuild?: NavbarBuild; + className?: string; +} + +const NavbarTouchComponent = (props: NavbarTouchProps) => { + const { marginTop, navbarBuild, className = '' } = props; + const { t } = useClientTranslation('navbar'); + const { t: tAuth } = useClientTranslation('auth'); + + const { checkPermissionFor } = useUserPermissionsV2(); + const permissionToLogin = checkPermissionFor('login'); + const permissionToLogout = checkPermissionFor('logout'); + const [logout] = useLogoutMutation(); + + const [dropdownType, setDropdownType] = useState(null); + + const style: CSSProperties = marginTop ? { marginTop: `${marginTop}px` } : {}; + + const closeDropdown = () => setDropdownType(null); + + const navManuItemsList: INavMenuItem[] = useMemo(() => { + return (navbarBuild?.menu || []) + .filter((item) => item.type === ItemType.navLink) + .map((item) => ({ + path: item.path, + name: t(`${item.name}`), + type: NavMenuItemType.Link, + active: false, + })); + }, [t, navbarBuild?.menu]); + + return ( + + ); +}; + +NavbarTouchComponent.displayName = 'NavbarTouch'; + +export default memo(NavbarTouchComponent);