Public Access
fix(header): prevent mobile header overflow
Reduce responsive padding and gaps below 640 px, move nav labels to lg breakpoint, and position the "view as" badge absolutely to prevent horizontal scroll on narrow phones (360-414 px).
This commit is contained in:
1 parent
b6f3d123fb
commit
04387fcd0c
3 files changed
+10
-9
No files matched your search
@@ -36,7 +36,7 @@ const NAV_ITEMS = [
|
||||
];
|
||||
|
||||
const navLinkClass = (isActive) => cn(
|
||||
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
'flex items-center gap-2 rounded-lg px-2 py-2 text-sm font-medium transition-colors sm:px-3',
|
||||
isActive
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
|
||||
@@ -104,28 +104,29 @@ export default function AppHeader({isAuthenticated, onLogout}) {
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 border-b bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<PageContainer className="flex h-16 items-center gap-2 px-4 md:px-8">
|
||||
<Link to={isAuthenticated ? '/dashboard' : '/'} aria-label="ToBePlayed" className="mr-2 flex items-center gap-2 font-bold tracking-tight whitespace-nowrap">
|
||||
<PageContainer className="flex h-16 items-center gap-1 px-4 sm:gap-2 md:px-8">
|
||||
<Link to={isAuthenticated ? '/dashboard' : '/'} aria-label="ToBePlayed" className="flex items-center gap-2 font-bold tracking-tight sm:mr-2 whitespace-nowrap">
|
||||
<Logo className="h-8 w-auto shrink-0"/>
|
||||
{/* On a narrow phone the menu and buttons need the room; the logo alone stays. */}
|
||||
<span className="hidden text-lg min-[380px]:inline">ToBePlayed</span>
|
||||
<span className="hidden text-lg min-[520px]:inline">ToBePlayed</span>
|
||||
</Link>
|
||||
|
||||
{isAuthenticated && (
|
||||
<nav className="flex items-center gap-1">
|
||||
<nav className="flex items-center sm:gap-1">
|
||||
{NAV_ITEMS.map(({to, labelKey, Icon, alsoActiveOn = []}) => (
|
||||
<NavLink key={to} to={to} title={t(labelKey)}
|
||||
className={({isActive}) => navLinkClass(isActive || alsoActiveOn.includes(pathname))}>
|
||||
<Icon className="size-4"/>
|
||||
<span className="hidden sm:inline">{t(labelKey)}</span>
|
||||
<span className="hidden lg:inline">{t(labelKey)}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
{/* Below md the badge hangs on the header's bottom edge: in the row it would push the page sideways. */}
|
||||
{isAuthenticated && viewingAs && (
|
||||
<Badge variant="secondary" className="mr-1">{t('NAV.VIEW_AS.BADGE', {role: t(`ROLES.${viewingAs}`)})}</Badge>
|
||||
<Badge variant="secondary" className="max-md:absolute max-md:top-full max-md:left-1/2 max-md:-translate-x-1/2 max-md:-translate-y-1/2 max-md:pointer-events-none max-md:border-border md:mr-1">{t('NAV.VIEW_AS.BADGE', {role: t(`ROLES.${viewingAs}`)})}</Badge>
|
||||
)}
|
||||
{isAuthenticated && wallet && (
|
||||
<Button asChild variant="ghost" size="sm" title={t('POINTS.TITLE')}>
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('AppHeader', () => {
|
||||
await vi.waitFor(() => expect(toast.error).toHaveBeenCalledWith('SETTINGS.ERROR_SAVE'));
|
||||
});
|
||||
|
||||
// jsdom applies no media queries, so the test hides the name the way the 380px breakpoint does.
|
||||
// jsdom applies no media queries, so the test hides the name the way the 520px breakpoint does.
|
||||
it('keeps a name on the home link when a narrow phone hides the app name', () => {
|
||||
fetchRoutes({});
|
||||
renderWithProviders(<AppHeader isAuthenticated={false} onLogout={() => {}}/>);
|
||||
|
||||
@@ -189,7 +189,7 @@ export default function AdminBroadcastPage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">{t('ADMIN_BROADCAST.RECIPIENTS')}</p>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{['all', 'picked'].map((value) => (
|
||||
<Button key={value} type="button" variant={mode === value ? 'default' : 'outline'}
|
||||
aria-pressed={mode === value} onClick={() => setMode(value)}>
|
||||
|
||||
Reference in new issue
Block a user