From b0617d89e8569e40d739bfd4e87dbc94cff7e865 Mon Sep 17 00:00:00 2001 From: SrJuggernaut Date: Mon, 23 Mar 2026 10:28:35 -0600 Subject: [PATCH] feat: add Header and MainMenu components --- src/components/layout/fragments/Header.tsx | 96 ++++++++++ src/components/layout/fragments/MainMenu.tsx | 186 +++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 src/components/layout/fragments/Header.tsx create mode 100644 src/components/layout/fragments/MainMenu.tsx diff --git a/src/components/layout/fragments/Header.tsx b/src/components/layout/fragments/Header.tsx new file mode 100644 index 0000000..fd8b20e --- /dev/null +++ b/src/components/layout/fragments/Header.tsx @@ -0,0 +1,96 @@ +import { css, cx } from '@styled-system/css' +import { token } from '@styled-system/tokens' +import { type FC, useCallback, useEffect, useState } from 'react' +import SrJuggernautLogo from '@/components/assets/SrJuggernautLogo' +import MainMenu from '@/components/layout/fragments/MainMenu' +import srOnlyClass from '@/styles/srOnly' + +const headerClass = css({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + position: 'sticky', + top: 0, + transition: `background-color ${token('durations.slow')} ${token('easings.easeOutQuint')}`, + zIndex: 1, + flexShrink: 0 +}) + +const headerUnscrolledClass = css({ + backgroundColor: 'transparent' +}) + +const headerScrolledClass = css({ + backgroundColor: 'neutral.2' +}) + +const headerContainerClass = css({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + padding: 'md', + maxWidth: { + sm: 'breakpoint-sm', + md: 'breakpoint-md', + lg: 'breakpoint-lg', + xl: 'breakpoint-xl', + '2xl': 'breakpoint-2xl' + } +}) + +const headerLogoLinkClass = css({ + color: 'neutral.12', + cursor: 'pointer' +}) + +const headerLogoClass = css({ + fill: 'neutral.12', + height: '30px', + width: 'auto' +}) + +const Header: FC = () => { + const [scrolled, setScrolled] = useState(false) + + const handleScroll = useCallback(() => { + if (window.scrollY > 0) { + setScrolled(true) + } else { + setScrolled(false) + } + }, []) + + useEffect(() => { + if (typeof window === 'undefined') { + return + } + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, [handleScroll]) + + return ( +
+
+ + + Ir a la página principal + + +
+
+ ) +} + +export default Header diff --git a/src/components/layout/fragments/MainMenu.tsx b/src/components/layout/fragments/MainMenu.tsx new file mode 100644 index 0000000..c06d2b1 --- /dev/null +++ b/src/components/layout/fragments/MainMenu.tsx @@ -0,0 +1,186 @@ +import { faBars, faTimes } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { css } from '@styled-system/css' +import { token } from '@styled-system/tokens' +import { type FC, type ReactNode, useRef } from 'react' +import SrJuggernautLogo from '@/components/assets/SrJuggernautLogo' +import Button from '@/components/ui/Button' +import Menu, { + MenuGroup, + MenuItem, + MenuLabel, + MenuSeparator +} from '@/components/ui/Menu' +import srOnlyClass from '@/styles/srOnly' + +const menuDialogClass = css({ + position: 'fixed', + height: '100dvh', + width: { + base: '100%', + sm: '250px' + }, + top: 0, + right: 0, + left: 'auto', + backgroundColor: 'neutral.2', + color: 'neutral.12', + transition: `transform ${token('durations.normal')} ${token('easings.easeOutQuint')}`, + transitionBehavior: 'allow-discrete', + transform: 'translateX(0)', + '@starting-style': { + transform: 'translateX(100%)', + _backdrop: { + opacity: 0, + backgroundColor: 'transparent', + transition: `background-color ${token('durations.normal')} ${token('easings.easeOutQuint')}, opacity ${token('durations.fast')} ${token('easings.easeOutQuint')}`, + transitionBehavior: 'allow-discrete' + } + }, + _backdrop: { + opacity: 1, + backdropFilter: 'blur(5px)', + backgroundColor: 'neutral.1/80', + transition: `background-color ${token('durations.normal')} ${token('easings.easeOutQuint')}, opacity ${token('durations.fast')} ${token('easings.easeOutQuint')}`, + transitionBehavior: 'allow-discrete' + } +}) + +const menuHeaderContainerClass = css({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + padding: 'md' +}) + +const menuHeaderLogoLinkClass = css({ + color: 'neutral.12', + cursor: 'pointer' +}) + +const menuHeaderLogoClass = css({ + fill: 'neutral.12', + height: '30px', + width: 'auto' +}) + +interface MainMenuLink { + type: 'link' + href: string + label: ReactNode +} + +interface MainMenuLabel { + type: 'label' + label: ReactNode +} + +interface MainMenuSeparator { + type: 'separator' +} + +interface MainMenuGroup { + type: 'group' + label: ReactNode + content: (MainMenuLink | MainMenuLabel | MainMenuSeparator)[] +} + +type MenuItemType = + | MainMenuLink + | MainMenuSeparator + | MainMenuLabel + | MainMenuGroup + +const menuContent: MenuItemType[] = [ + { type: 'link', href: '/', label: 'Inicio' } +] + +const RenderMenuItem: FC = (item) => { + switch (item.type) { + case 'link': + return ( + {item.label}}> + {item.label} + + ) + case 'label': + return {item.label} + case 'separator': + return + case 'group': + return ( + + {item.content.map((item, index) => ( + + ))} + + ) + } +} + +const MainMenu: FC = () => { + const DialogMenuRef = useRef(null) + + return ( + <> + + +
+ + + Ir a la página principal + + +
+ } + className={css({ + width: '100%' + })} + > + {menuContent.map((item, index) => ( + + ))} + +
+ + ) +} + +export default MainMenu