Skip to content

Commit 3aaab60

Browse files
committed
feat: add mobile header
1 parent efe3c5c commit 3aaab60

5 files changed

Lines changed: 341 additions & 6 deletions

File tree

src/components/HeaderFooter/BrandLogo.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { CustomLink } from '@/components/Other/CustomLink'
22
import Image, { StaticImageData } from 'next/image'
3+
import { cn } from '@/lib/utils/cn'
34

4-
export function BrandLogo({logoLight, logoDark}:{logoLight: StaticImageData, logoDark: StaticImageData}) {
5+
export function BrandLogo({logoLight, logoDark, mobile = false}:{logoLight: StaticImageData, logoDark: StaticImageData, mobile?:boolean}) {
56
return (
6-
<CustomLink href="/" className="py-2">
7+
<CustomLink href="/" className={cn( mobile ? 'py-1' : 'py-2')}>
78
<Image
89
className="logo-light dark:hidden"
910
src={logoLight.src}
@@ -16,7 +17,7 @@ export function BrandLogo({logoLight, logoDark}:{logoLight: StaticImageData, log
1617
// fill
1718
priority
1819
style={{
19-
width: '200px',
20+
// width: '200px',
2021
height: 'auto',
2122
}}
2223
/>
@@ -32,8 +33,10 @@ export function BrandLogo({logoLight, logoDark}:{logoLight: StaticImageData, log
3233
// fill
3334
priority
3435
style={{
35-
width: '200px',
36-
height: 'auto',
36+
// width: '200px',
37+
width: 'auto',
38+
height : mobile ? 35 : 50
39+
// height: 'auto',
3740
}}
3841
/>
3942
</CustomLink>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React from 'react'
2+
import type { StoryObj, Meta } from '@storybook/react'
3+
4+
import { HeaderMobile } from './HeaderMobile'
5+
import { ChartPieIcon, CursorArrowRaysIcon, FingerPrintIcon } from '@heroicons/react/24/outline'
6+
import { PhoneIcon, PlayCircleIcon, RectangleGroupIcon } from '@heroicons/react/20/solid'
7+
import { DynamicIcon } from '../Images'
8+
9+
import logoLight from '@/images/cortex-reply-light.png'
10+
import logoDark from '@/images/cortex-reply-dark.png'
11+
12+
13+
export default {
14+
title: 'Header/HeaderMobile',
15+
component: HeaderMobile,
16+
} as Meta<typeof HeaderMobile>
17+
18+
type Story = StoryObj<typeof HeaderMobile>
19+
20+
const GithubIcon = (props: any) => <DynamicIcon iconName="github" type="brands" {...props} />
21+
export const Primary: Story = {
22+
args: {
23+
title: 'Your Company',
24+
logoDark: logoDark,
25+
logoLight: logoLight,
26+
menuItems: [
27+
{
28+
name: 'Product',
29+
items: [
30+
{
31+
name: 'Analytics',
32+
description: 'Understand your traffic',
33+
href: '#',
34+
icon: ChartPieIcon, // Pass the component, not <ChartPieIcon />
35+
},
36+
{
37+
name: 'Engagement',
38+
description: 'Interact with customers',
39+
href: '#',
40+
icon: CursorArrowRaysIcon,
41+
},
42+
{
43+
name: 'Security',
44+
description: 'Protect your data',
45+
href: '#',
46+
icon: FingerPrintIcon,
47+
},
48+
],
49+
},
50+
{
51+
name: 'Resources',
52+
items: [
53+
{ name: 'Documentation', href: '#', icon: ChartPieIcon },
54+
{ name: 'API Reference', href: '#', icon: CursorArrowRaysIcon },
55+
{ name: 'Github', href: '#', icon: GithubIcon },
56+
],
57+
actions: [
58+
{ name: 'Watch demo', href: '#', icon: PlayCircleIcon },
59+
{ name: 'Contact sales', href: '#', icon: PhoneIcon },
60+
{ name: 'View all products', href: '#', icon: RectangleGroupIcon },
61+
],
62+
},
63+
{
64+
name: 'Company',
65+
href: '#',
66+
},
67+
],
68+
},
69+
}
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
'use client'
2+
3+
import React, { useState, useEffect } from 'react'
4+
import {
5+
Dialog,
6+
DialogPanel,
7+
Disclosure,
8+
DisclosureButton,
9+
DisclosurePanel,
10+
Popover,
11+
PopoverGroup,
12+
} from '@headlessui/react'
13+
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
14+
import { SunIcon, MoonIcon } from '@heroicons/react/20/solid'
15+
import HeaderMenu, { type HeaderMenuProps } from './HeaderMenu'
16+
// import Image from 'next/image'
17+
import { Container } from '@/components/Other/Container'
18+
import { BrandLogo } from './BrandLogo'
19+
import { Moon, Sun } from 'lucide-react'
20+
import { type StaticImageData } from 'next/image'
21+
interface SimpleHeaderProps {
22+
title: string
23+
logoLight: StaticImageData
24+
logoDark: StaticImageData
25+
menuItems: HeaderMenuProps[]
26+
isMenuOpen?: boolean
27+
28+
}
29+
const themes = ['light', 'dark']
30+
31+
export function HeaderMobile({ title, logoLight, logoDark, menuItems, isMenuOpen = true }: SimpleHeaderProps) {
32+
const [isScrolled , setIsScrolled] = useState(false)
33+
34+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
35+
const [isDarkMode, setIsDarkMode] = useState(false)
36+
const [currentTheme, setCurrentTheme] = useState('light')
37+
// const logoBase = logo?.split('.')[0]
38+
// const logoExt = logo?.split('.')[1]
39+
// const [themeLogo, setThemeLogo] = useState(`${logoBase}.${logoExt}`)
40+
41+
// const updateLogo = (theme: string) => {
42+
// if (themes.includes(theme)) {
43+
// if (theme === 'light') {
44+
// setThemeLogo(`${logoBase}.${logoExt}`)
45+
// } else {
46+
// setThemeLogo(`${logoBase}-${theme}.${logoExt}`)
47+
// }
48+
// } else {
49+
// setThemeLogo(`${logoBase}.${logoExt}`)
50+
// }
51+
// }
52+
53+
useEffect(() => {
54+
const handleScroll = () => {
55+
const currentScrollY = window.scrollY
56+
if (currentScrollY > 100) {
57+
setIsScrolled(true)
58+
} else {
59+
setIsScrolled(false)
60+
}
61+
}
62+
63+
window.addEventListener('scroll', handleScroll, { passive: true })
64+
65+
return () => window.removeEventListener('scroll', handleScroll)
66+
}, [])
67+
68+
useEffect(() => {
69+
// Load theme from local storage or set to default 'light'
70+
const storedTheme = localStorage.getItem('theme') || 'light'
71+
// console.log('storedTheme', storedTheme)
72+
setCurrentTheme(storedTheme)
73+
document.documentElement.setAttribute('class', storedTheme)
74+
// updateLogo(storedTheme)
75+
}, [])
76+
77+
const toggleTheme = () => {
78+
const nextTheme = themes[(themes.indexOf(currentTheme) + 1) % themes.length]
79+
setCurrentTheme(nextTheme)
80+
document.documentElement.setAttribute('class', nextTheme)
81+
localStorage.setItem('theme', nextTheme) // Save theme to local storage
82+
// updateLogo(nextTheme)
83+
}
84+
85+
86+
return (
87+
<>
88+
<header
89+
className={`z-50 pb-10 transition-all duration-300 block lg:hidden ${
90+
isScrolled || isMenuOpen ? 'translate-y-0' : '-translate-y-full bg-transparent'
91+
}`}
92+
>
93+
{/* <Container className='px-0'> */}
94+
<nav className="backdrop-blur-sm text-white p-0">
95+
<div className="flex items-stretch justify-between bg-accent px-2 py-0 dark:bg-[#212124] [&_.logo-light]:[filter:brightness(0)_invert(1)] dark:border-b dark:border-accent">
96+
<BrandLogo logoDark={logoDark} logoLight={logoLight} mobile={true}/>
97+
<div className='flex items-center space-x-4'>
98+
<button
99+
type="button"
100+
onClick={() => setMobileMenuOpen(true)}
101+
className="fixed right-4 z-[60] p-2 rounded-full bg-white/10 backdrop-blur-sm transition-all duration-300 hover:bg-white/20 text-white dark:hover:text-accent"
102+
>
103+
<span className="sr-only">Open main menu</span>
104+
<Bars3Icon aria-hidden="true" className="h-4 w-4" />
105+
</button>
106+
<button
107+
className="fixed right-16 z-[60] p-2 rounded-full bg-white/10 backdrop-blur-sm transition-all duration-300 hover:bg-white/20 text-white dark:hover:text-accent"
108+
onClick={toggleTheme}
109+
>
110+
<div className="relative">
111+
112+
<Sun className="absolute h-4 w-4 rotate-0 scale-100 transition-transform dark:-rotate-90 dark:scale-0" />
113+
<Moon className="absoulte h-4 w-4 rotate-0 scale-0 transition-transform dark:rotate-0 dark:scale-100" />
114+
{/* <X className="w-6 h-6 text-white" /> */}
115+
</div>
116+
</button>
117+
118+
</div>
119+
</div>
120+
</nav>
121+
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen} className="lg:hidden">
122+
<div className="fixed inset-0 z-10 bg-black bg-opacity-50" aria-hidden="true" />
123+
<DialogPanel className="fixed inset-y-0 right-0 z-20 w-full overflow-y-auto bg-white dark:bg-[#212124] px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10">
124+
<div className="flex items-center justify-between">
125+
<a href="/" className="-m-1.5 p-1.5 outline-none">
126+
<BrandLogo logoDark={logoDark} logoLight={logoLight} mobile={true}/>
127+
128+
{/* <img alt={title} src={logo} className="h-8 w-auto" /> */}
129+
</a>
130+
<button
131+
type="button"
132+
onClick={() => setMobileMenuOpen(false)}
133+
className="-m-2.5 rounded-md p-2.5 text-gray-700 dark:text-foreground outline-none hover:text-accent"
134+
>
135+
<span className="sr-only">Close menu</span>
136+
<XMarkIcon aria-hidden="true" className="h-6 w-6" />
137+
</button>
138+
</div>
139+
<div className="mt-6 flow-root">
140+
<div className="-my-6 divide-y divide-gray-500/10">
141+
<div className="space-y-2 py-6">
142+
{menuItems.map((menu) => (
143+
<DisclosureItem key={menu.name} {...menu} />
144+
))}
145+
</div>
146+
</div>
147+
</div>
148+
</DialogPanel>
149+
</Dialog>
150+
{/* </Container> */}
151+
</header>
152+
</>
153+
)
154+
155+
156+
return (
157+
<header className="relative isolate z-10 bg-background h-14">
158+
<nav
159+
aria-label="Global"
160+
className="relative mx-auto flex items-center justify-between p-3 lg:px-8"
161+
>
162+
<div className="flex lg:flex-1">
163+
<a href="#" className="-m-1.5 p-1.5 outline-none">
164+
<span className="sr-only">{title}</span>
165+
{/* eslint-disable-next-line @next/next/no-img-element */}
166+
{/* <img alt={title} src={themeLogo} className="h-9 w-auto" /> */}
167+
</a>
168+
</div>
169+
<div className="flex lg:hidden">
170+
<button
171+
type="button"
172+
onClick={() => setMobileMenuOpen(true)}
173+
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700 outline-none"
174+
>
175+
<span className="sr-only">Open main menu</span>
176+
<Bars3Icon aria-hidden="true" className="h-6 w-6" />
177+
</button>
178+
</div>
179+
<PopoverGroup className="hidden lg:flex lg:gap-x-12">
180+
{menuItems.map((menu) => (
181+
<HeaderMenu key={menu.name} {...menu} />
182+
))}
183+
</PopoverGroup>
184+
<div className="hidden lg:flex lg:flex-1 lg:justify-end">
185+
{/* Theme Toggle Button */}
186+
<button onClick={toggleTheme} className="p-2 text-gray-700 outline-none">
187+
{currentTheme === 'dark' ? (
188+
<SunIcon className="h-6 w-6" />
189+
) : currentTheme === 'green' ? (
190+
<MoonIcon className="h-6 w-6" />
191+
) : (
192+
<SunIcon className="h-6 w-6" />
193+
)}
194+
</button>
195+
</div>
196+
</nav>
197+
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen} className="lg:hidden">
198+
<div className="fixed inset-0 z-10 bg-black bg-opacity-50" aria-hidden="true" />
199+
<DialogPanel className="fixed inset-y-0 right-0 z-20 w-full overflow-y-auto bg-white px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10">
200+
<div className="flex items-center justify-between">
201+
<a href="#" className="-m-1.5 p-1.5 outline-none">
202+
<span className="sr-only">Your Company</span>
203+
{/* <img alt={title} src={logo} className="h-8 w-auto" /> */}
204+
</a>
205+
<button
206+
type="button"
207+
onClick={() => setMobileMenuOpen(false)}
208+
className="-m-2.5 rounded-md p-2.5 text-gray-700 outline-none"
209+
>
210+
<span className="sr-only">Close menu</span>
211+
<XMarkIcon aria-hidden="true" className="h-6 w-6" />
212+
</button>
213+
</div>
214+
<div className="mt-6 flow-root">
215+
<div className="-my-6 divide-y divide-gray-500/10">
216+
<div className="space-y-2 py-6">
217+
{menuItems.map((menu) => (
218+
<DisclosureItem key={menu.name} {...menu} />
219+
))}
220+
</div>
221+
</div>
222+
</div>
223+
</DialogPanel>
224+
</Dialog>
225+
</header>
226+
)
227+
}
228+
229+
function DisclosureItem({ name, items, actions, href }: HeaderMenuProps) {
230+
if (!items && href) {
231+
return (
232+
<a
233+
href={href}
234+
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold dark:text-foreground text-gray-900 hover:bg-gray-50 hover:text-accent"
235+
>
236+
{name}
237+
</a>
238+
)
239+
}
240+
return (
241+
<Disclosure as="div" className="-mx-3">
242+
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg py-2 pl-3 pr-3.5 text-base font-semibold dark:text-foreground text-gray-900 hover:bg-gray-50 hover:text-accent">
243+
{name}
244+
</DisclosureButton>
245+
<DisclosurePanel className="mt-2 space-y-2">
246+
{items &&
247+
items.map((item) => (
248+
<DisclosureButton
249+
key={item.name}
250+
as="a"
251+
href={item.href}
252+
className="block rounded-lg py-2 pl-6 pr-3 text-sm font-semibold dark:text-foreground text-gray-900 hover:bg-gray-50 hover:text-accent"
253+
>
254+
{item.name}
255+
</DisclosureButton>
256+
))}
257+
</DisclosurePanel>
258+
</Disclosure>
259+
)
260+
}

src/components/HeaderFooter/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export * from "./HeaderMenu"
44
export * from "./HeaderMenuItem"
55
export * from "./VideoHeader"
66
export * from "./HeaderDesktop"
7+
export * from "./HeaderMobile"
78

src/components/Pages/Website.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import {
7171

7272
import { SidebarRight } from '@/components//Menus/SidebarRight'
7373
import { SidebarLeft } from '@/components//Menus/SidebarLeft'
74-
import { HeaderDesktop } from '../HeaderFooter'
74+
import { HeaderDesktop, HeaderMobile } from '../HeaderFooter'
7575
// import GithubControl from '@/components//Editor/GithubControl'
7676
import { Toaster } from '@/components//ui/toaster'
7777
import { LowImpactHero } from '@/components/Heros/LowImpact'
@@ -90,6 +90,8 @@ export default function WebsiteSection({ ...args }) {
9090
<div className="fixed top-0 left-0 right-0 z-50">
9191

9292
<HeaderDesktop isMenuOpen={true} logoLight={logoLight} logoDark={logoDark} />
93+
<HeaderMobile isMenuOpen={true} logoLight={logoLight} logoDark={logoDark} {...args.header}/>
94+
9395
</div>
9496

9597
{/* Hero Section */}

0 commit comments

Comments
 (0)