Skip to content

Commit 8923a19

Browse files
Merge pull request #155 from acara-app/feat/improve-settings-mobile-nav
feat: improve settings navigation for mobile
2 parents 8101a68 + 2f15310 commit 8923a19

File tree

4 files changed

+78
-17
lines changed

4 files changed

+78
-17
lines changed

lang/mn/auth.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
'remember_me' => 'Намайг санах',
2121
'submit' => 'Нэвтрэх',
2222
'forgot_password' => 'Нууц үгээ мартсан уу?',
23-
'no_account' => "Бүртгэлгүй юу?",
23+
'no_account' => 'Бүртгэлгүй юу?',
2424
'sign_up' => 'Бүртгүүлэх',
2525
'or' => 'Эсвэл',
2626
],

lang/mn/passwords.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
'sent' => 'Нууц үг сэргээх холбоос имэйлээр илгээлээ.',
1919
'throttled' => 'Түр хүлээгээд дахин оролдоно уу.',
2020
'token' => 'Нууц үг сэргээх холбоос буруу байна.',
21-
'user' => "Бүртгэлтэй имэйл хаяг олдсонгүй.",
21+
'user' => 'Бүртгэлтэй имэйл хаяг олдсонгүй.',
2222

2323
];

resources/css/app.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,12 @@
142142
@apply bg-background text-foreground;
143143
}
144144
}
145+
146+
/* Hide scrollbar while preserving scroll functionality */
147+
.no-scrollbar {
148+
-ms-overflow-style: none;
149+
scrollbar-width: none;
150+
}
151+
.no-scrollbar::-webkit-scrollbar {
152+
display: none;
153+
}

resources/js/layouts/settings/layout.tsx

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,47 @@ import { edit as editNotifications } from '@/routes/user-notifications';
1010
import { edit } from '@/routes/user-profile';
1111
import { type NavItem } from '@/types';
1212
import { Link } from '@inertiajs/react';
13-
import { type PropsWithChildren } from 'react';
13+
import { Bell, Lock, Palette, Puzzle, ShieldCheck, User } from 'lucide-react';
14+
import { type PropsWithChildren, useEffect, useRef } from 'react';
1415
import { useTranslation } from 'react-i18next';
1516

1617
const getSidebarNavItems = (t: (key: string) => string): NavItem[] => [
1718
{
1819
title: t('settings_layout.nav.profile'),
1920
href: edit(),
20-
icon: null,
21+
icon: User,
2122
},
2223
{
2324
title: t('settings_layout.nav.password'),
2425
href: editPassword(),
25-
icon: null,
26+
icon: Lock,
2627
},
2728
{
2829
title: t('settings_layout.nav.two_factor'),
2930
href: show(),
30-
icon: null,
31+
icon: ShieldCheck,
3132
},
3233
{
3334
title: t('settings_layout.nav.notifications'),
3435
href: editNotifications(),
35-
icon: null,
36+
icon: Bell,
3637
},
3738
{
3839
title: t('settings_layout.nav.appearance'),
3940
href: editAppearance(),
40-
icon: null,
41+
icon: Palette,
4142
},
4243
{
4344
title: t('settings_layout.nav.integrations'),
4445
href: editIntegrations(),
45-
icon: null,
46+
icon: Puzzle,
4647
},
4748
];
4849

4950
export default function SettingsLayout({ children }: PropsWithChildren) {
5051
const { t } = useTranslation('common');
52+
const scrollRef = useRef<HTMLDivElement>(null);
53+
5154
// When server-side rendering, we only render the layout on the client...
5255
if (typeof window === 'undefined') {
5356
return null;
@@ -56,6 +59,26 @@ export default function SettingsLayout({ children }: PropsWithChildren) {
5659
const currentPath = window.location.pathname;
5760
const sidebarNavItems = getSidebarNavItems(t);
5861

62+
const isActive = (item: NavItem) =>
63+
currentPath ===
64+
(typeof item.href === 'string' ? item.href : item.href.url);
65+
66+
// Auto-scroll the active tab into view on mobile
67+
useEffect(() => {
68+
if (scrollRef.current) {
69+
const activeEl = scrollRef.current.querySelector(
70+
'[data-active="true"]',
71+
);
72+
if (activeEl) {
73+
activeEl.scrollIntoView({
74+
behavior: 'smooth',
75+
inline: 'center',
76+
block: 'nearest',
77+
});
78+
}
79+
}
80+
}, [currentPath]);
81+
5982
return (
6083
<div className="px-4 py-6">
6184
<Heading
@@ -64,20 +87,49 @@ export default function SettingsLayout({ children }: PropsWithChildren) {
6487
/>
6588

6689
<div className="flex flex-col lg:flex-row lg:space-x-12">
67-
<aside className="w-full max-w-xl lg:w-48">
68-
<nav className="flex flex-col space-y-1 space-x-0">
90+
{/* Mobile: Horizontal scrollable pill tab bar */}
91+
<div className="relative lg:hidden">
92+
<div
93+
ref={scrollRef}
94+
className="no-scrollbar flex gap-2 overflow-x-auto pb-4"
95+
>
96+
{sidebarNavItems.map((item, index) => {
97+
const active = isActive(item);
98+
return (
99+
<Link
100+
key={`${typeof item.href === 'string' ? item.href : item.href.url}-${index}`}
101+
href={item.href}
102+
data-active={active}
103+
className={cn(
104+
'inline-flex shrink-0 items-center gap-1.5 rounded-full border px-3.5 py-2 text-sm font-medium transition-all duration-200',
105+
active
106+
? 'border-primary bg-primary text-primary-foreground shadow-sm'
107+
: 'border-border bg-background text-muted-foreground hover:border-foreground/20 hover:bg-accent hover:text-foreground',
108+
)}
109+
>
110+
{item.icon && (
111+
<item.icon className="h-4 w-4" />
112+
)}
113+
{item.title}
114+
</Link>
115+
);
116+
})}
117+
</div>
118+
{/* Right fade gradient for scroll hint */}
119+
<div className="pointer-events-none absolute top-0 right-0 h-full w-8 bg-linear-to-l from-background to-transparent" />
120+
</div>
121+
122+
{/* Desktop: Vertical sidebar nav */}
123+
<aside className="hidden w-48 lg:block">
124+
<nav className="flex flex-col gap-1">
69125
{sidebarNavItems.map((item, index) => (
70126
<Button
71127
key={`${typeof item.href === 'string' ? item.href : item.href.url}-${index}`}
72128
size="sm"
73129
variant="ghost"
74130
asChild
75131
className={cn('w-full justify-start', {
76-
'bg-muted':
77-
currentPath ===
78-
(typeof item.href === 'string'
79-
? item.href
80-
: item.href.url),
132+
'bg-muted': isActive(item),
81133
})}
82134
>
83135
<Link href={item.href}>
@@ -91,7 +143,7 @@ export default function SettingsLayout({ children }: PropsWithChildren) {
91143
</nav>
92144
</aside>
93145

94-
<Separator className="my-6 lg:hidden" />
146+
<Separator className="my-6 hidden" />
95147

96148
<div className="flex-1 md:max-w-2xl">
97149
<section className="max-w-xl space-y-12">

0 commit comments

Comments
 (0)