Skip to content

Commit 4f4aba6

Browse files
marco-souzaCopilot
andauthored
feat: add mobile-friendly navigation with drawer menu (#253)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marco-souza <4452113+marco-souza@users.noreply.github.com>
1 parent e143e9e commit 4f4aba6

4 files changed

Lines changed: 144 additions & 40 deletions

File tree

src/components/Header.astro

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import { getRelativeLocaleUrl } from 'astro:i18n';
33
import Logo from '@/components/Logo.astro';
4+
import MobileMenu from '@/components/MobileMenu.astro';
45
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
56
67
const lang = getLangFromUrl(Astro.url);
@@ -26,33 +27,53 @@ const ctaLinks = {
2627
class="mx-auto flex w-full max-w-5xl flex-wrap items-center justify-between gap-3 rounded-lg bg-base-100/80 px-4 py-2 shadow-md backdrop-blur-sm"
2728
aria-label="Primary"
2829
>
29-
<div class="flex min-w-0 flex-1 flex-wrap items-center gap-2 lg:gap-4">
30-
<Logo size="nav" showTitle />
31-
<ul class="menu menu-horizontal flex flex-1 flex-wrap justify-center gap-0 px-0 sm:justify-start lg:gap-1">
32-
{
33-
navigationLinks.map((link) => (
34-
<li>
35-
<a
36-
class:list={[
37-
'btn btn-xs btn-ghost',
38-
link.href === pathname && 'btn-outline btn-primary',
39-
]}
40-
href={link.href}
41-
>
42-
{link.label}
43-
</a>
44-
</li>
45-
))
46-
}
47-
</ul>
30+
<!-- Mobile: Logo + Hamburger in a flex row -->
31+
<div class="flex w-full items-center justify-between lg:hidden">
32+
<div class="flex items-center gap-2">
33+
<MobileMenu {navigationLinks} {ctaLinks} {pathname} />
34+
</div>
35+
36+
<div class="flex items-center gap-2">
37+
<Logo size="nav" showTitle />
38+
</div>
39+
40+
<div class="flex items-center gap-2">
41+
<a href={ctaLinks.primary.href} class="btn btn-primary btn-sm">
42+
{ctaLinks.primary.label}
43+
</a>
44+
</div>
4845
</div>
49-
<div class="flex items-center gap-1 sm:gap-2">
50-
<a href={ctaLinks.secondary.href} class="btn btn-ghost btn-sm">
51-
{ctaLinks.secondary.label}
52-
</a>
53-
<a href={ctaLinks.primary.href} class="btn btn-primary btn-sm">
54-
{ctaLinks.primary.label}
55-
</a>
46+
47+
<!-- Desktop: Original layout (Logo + Nav left, CTAs right) -->
48+
<div class="hidden w-full lg:flex lg:items-center lg:justify-between">
49+
<div class="flex min-w-0 flex-1 flex-wrap gap-2 lg:gap-4">
50+
<Logo size="nav" showTitle />
51+
<ul class="menu menu-horizontal flex flex-1 flex-wrap gap-0 px-0 lg:gap-1">
52+
{
53+
navigationLinks.map((link) => (
54+
<li>
55+
<a
56+
class:list={[
57+
'btn btn-xs btn-ghost',
58+
link.href === pathname && 'btn-outline btn-primary',
59+
]}
60+
href={link.href}
61+
>
62+
{link.label}
63+
</a>
64+
</li>
65+
))
66+
}
67+
</ul>
68+
</div>
69+
<div class="flex items-center gap-1 sm:gap-2">
70+
<a href={ctaLinks.secondary.href} class="btn btn-ghost btn-sm">
71+
{ctaLinks.secondary.label}
72+
</a>
73+
<a href={ctaLinks.primary.href} class="btn btn-primary btn-sm">
74+
{ctaLinks.primary.label}
75+
</a>
76+
</div>
5677
</div>
5778
</nav>
5879
</header>

src/components/MobileMenu.astro

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
import { Icon } from 'astro-icon/components';
3+
import Logo from '@/components/Logo.astro';
4+
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
5+
6+
interface Props {
7+
navigationLinks: Array<{ href: string; label: string }>;
8+
ctaLinks: {
9+
secondary: { href: string; label: string };
10+
primary: { href: string; label: string };
11+
};
12+
pathname: string;
13+
}
14+
15+
const { navigationLinks, ctaLinks, pathname } = Astro.props;
16+
const lang = getLangFromUrl(Astro.url);
17+
const t = useTranslations(lang);
18+
---
19+
20+
<div class="drawer drawer-start lg:hidden">
21+
<input id="mobile-menu-drawer" type="checkbox" class="drawer-toggle" />
22+
23+
<!-- Hamburger button -->
24+
<div class="drawer-content">
25+
<label
26+
for="mobile-menu-drawer"
27+
class="btn btn-ghost btn-sm drawer-button"
28+
aria-label={t('nav.menu')}
29+
>
30+
<Icon name="lucide:menu" class="h-5 w-5" />
31+
</label>
32+
</div>
33+
34+
<!-- Drawer side panel -->
35+
<div class="drawer-side fixed z-50 m-[-1rem] w-screen">
36+
<label
37+
for="mobile-menu-drawer"
38+
aria-label="close sidebar"
39+
class="drawer-overlay w-full"
40+
></label>
41+
<div class="flex h-full w-[80vw] max-w-sm flex-col bg-base-100 p-4">
42+
<!-- Top: Logo + Navigation links -->
43+
<div class="flex flex-col gap-4">
44+
<div class="flex items-center gap-2">
45+
<Logo size="nav" showTitle />
46+
</div>
47+
<ul class="menu w-full gap-1 p-0">
48+
{
49+
navigationLinks.map((link) => (
50+
<li>
51+
<a
52+
class:list={[
53+
'btn btn-ghost btn-sm w-full justify-start',
54+
link.href === pathname && 'btn-outline btn-primary',
55+
]}
56+
href={link.href}
57+
>
58+
{link.label}
59+
</a>
60+
</li>
61+
))
62+
}
63+
</ul>
64+
</div>
65+
66+
<!-- Center: empty space (flex-1 pushes content apart) -->
67+
<div class="flex-1"></div>
68+
69+
<!-- Bottom: CTAs -->
70+
<div class="flex flex-col gap-2">
71+
<a
72+
href={ctaLinks.secondary.href}
73+
class="btn btn-ghost btn-sm w-full"
74+
>
75+
{ctaLinks.secondary.label}
76+
</a>
77+
<a
78+
href={ctaLinks.primary.href}
79+
class="btn btn-primary btn-sm w-full"
80+
>
81+
{ctaLinks.primary.label}
82+
</a>
83+
</div>
84+
</div>
85+
</div>
86+
</div>

src/components/marketing/ActivityGrid.astro

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
---
2+
import { Icon } from 'astro-icon/components';
23
import type { Activity } from '@/data/marketing';
34
45
interface Props {
@@ -7,17 +8,13 @@ interface Props {
78
89
const { items } = Astro.props;
910
10-
/** Heroicons 24 outline: interview, career, group, terminal, calendar */
11-
const icons: Record<Activity['icon'], string> = {
12-
interview:
13-
'M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 01-.825-.242m9.345-8.334a2.126 2.126 0 00-.476-.095 48.64 48.64 0 00-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0011.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155',
14-
career:
15-
'M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 00.75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 00-3.413-.387m4.5 8.006c-.194.165-.42.295-.673.38A23.978 23.978 0 0112 15.75c-2.648 0-5.195-.429-7.577-1.22a2.016 2.016 0 01-.673-.38m0 0A2.18 2.18 0 013 12.489V8.706c0-1.081.768-2.015 1.837-2.175a48.111 48.111 0 013.413-.387m7.5 0V5.25A2.25 2.25 0 0013.5 3h-3a2.25 2.25 0 00-2.25 2.25v.894m7.5 0a48.667 48.667 0 00-7.5 0M12 12.75h.008v.008H12v-.008Z',
16-
groups:
17-
'M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0Zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0Zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0Z',
18-
project:
19-
'm6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z',
20-
cafe: 'M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5',
11+
/** Iconify icon names for activity items */
12+
const iconMap: Record<Activity['icon'], string> = {
13+
interview: 'lucide:mic',
14+
career: 'lucide:briefcase',
15+
groups: 'lucide:users',
16+
project: 'lucide:terminal',
17+
cafe: 'lucide:coffee',
2118
};
2219
---
2320

@@ -30,9 +27,7 @@ const icons: Record<Activity['icon'], string> = {
3027
class="bg-primary/15 text-primary inline-flex h-12 w-12 items-center justify-center rounded-xl"
3128
aria-hidden="true"
3229
>
33-
<svg class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
34-
<path stroke-linecap="round" stroke-linejoin="round" d={icons[item.icon]} />
35-
</svg>
30+
<Icon name={iconMap[item.icon]} class="h-7 w-7" />
3631
</div>
3732
<h3 class="card-title text-xl">{item.title}</h3>
3833
<p class="text-base-content/80 text-sm leading-relaxed">{item.description}</p>

src/i18n/ui.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export const ui = {
99
'nav.contributing': 'Como posso ajudar?',
1010
'nav.join_us': 'Faça parte!',
1111
'nav.transparency': 'Transparência',
12+
'nav.menu': 'Menu',
13+
'nav.close_menu': 'Fechar menu',
1214
'footer.copyright': 'Todos os direitos reservados.',
1315
// Transparency page
1416
'transparency.title': 'Transparência',

0 commit comments

Comments
 (0)