Skip to content

Commit 080076a

Browse files
Use icon controls for the mobile app header
1 parent c9112ae commit 080076a

4 files changed

Lines changed: 40 additions & 23 deletions

File tree

src/components/app-chrome.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useState } from "react";
44
import Link from "next/link";
55
import { usePathname } from "next/navigation";
6+
import { HistoryIcon, PlusIcon } from "lucide-react";
67

78
import { LanguageSwitcher } from "@/components/language-switcher";
89
import { buttonVariants } from "@/components/ui/button-variants";
@@ -41,7 +42,7 @@ export function AppChrome({
4142
return (
4243
<>
4344
<header className="border-b bg-background/90 backdrop-blur supports-[backdrop-filter]:bg-background/70">
44-
<div className="app-shell flex min-h-16 flex-wrap items-center justify-between gap-x-2 gap-y-3 py-3 sm:flex-nowrap sm:gap-4">
45+
<div className="app-shell flex h-16 items-center justify-between gap-3 sm:gap-4">
4546
<Link
4647
href="/"
4748
className="inline-flex shrink-0 items-center text-base font-semibold tracking-tight sm:text-lg"
@@ -58,21 +59,21 @@ export function AppChrome({
5859
appName
5960
)}
6061
</Link>
61-
<nav className="ml-auto flex min-w-0 flex-wrap items-center justify-end gap-1.5 sm:flex-nowrap sm:gap-2">
62+
<nav className="ml-auto flex shrink-0 items-center gap-1.5 sm:gap-2">
6263
<LanguageSwitcher
63-
className="h-9 min-w-16 px-2.5 text-xs sm:min-w-28 sm:px-3 sm:text-sm"
64-
compactLabel
64+
className="h-9 w-12 px-2 sm:w-[8.75rem] sm:px-3 sm:text-sm"
65+
mobileIcon
6566
/>
6667
<Link
6768
href="/new"
6869
aria-label={messages.appChrome.newEvent}
6970
className={cn(
70-
buttonVariants({ variant: "ghost" }),
71-
"h-9 px-3 text-xs sm:px-4 sm:text-sm",
71+
buttonVariants({ variant: "outline" }),
72+
"size-9 p-0 sm:h-9 sm:w-auto sm:border-transparent sm:bg-transparent sm:px-4 sm:shadow-none sm:hover:bg-accent sm:hover:text-accent-foreground",
7273
)}
7374
>
7475
<span aria-hidden="true" className="sm:hidden">
75-
{messages.appChrome.newEventCompact}
76+
<PlusIcon className="size-4" />
7677
</span>
7778
<span aria-hidden="true" className="hidden sm:inline">
7879
{messages.appChrome.newEvent}
@@ -83,11 +84,11 @@ export function AppChrome({
8384
aria-label={messages.appChrome.recentEvents}
8485
className={cn(
8586
buttonVariants({ variant: "outline" }),
86-
"h-9 px-3 text-xs sm:px-4 sm:text-sm",
87+
"size-9 p-0 sm:h-9 sm:w-auto sm:px-4 sm:text-sm",
8788
)}
8889
>
8990
<span aria-hidden="true" className="sm:hidden">
90-
{messages.appChrome.recentEventsCompact}
91+
<HistoryIcon className="size-4" />
9192
</span>
9293
<span aria-hidden="true" className="hidden sm:inline">
9394
{messages.appChrome.recentEvents}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { screen } from "@testing-library/react";
2+
import { describe, expect, it, vi } from "vitest";
3+
4+
import { LanguageSwitcher } from "./language-switcher";
5+
import { renderWithI18n } from "@/test/render-with-i18n";
6+
7+
vi.mock("next/navigation", () => ({
8+
useRouter: () => ({
9+
refresh: vi.fn(),
10+
}),
11+
}));
12+
13+
describe("LanguageSwitcher", () => {
14+
it("wraps the mobile icon variant in a single direct span inside the trigger", () => {
15+
renderWithI18n(<LanguageSwitcher mobileIcon />, { locale: "de" });
16+
17+
const trigger = screen.getByRole("combobox", { name: "Sprache" });
18+
const directSpans = Array.from(trigger.children).filter((child) => child.tagName === "SPAN");
19+
20+
expect(directSpans).toHaveLength(1);
21+
expect(trigger.querySelector("svg")).not.toBeNull();
22+
});
23+
});

src/components/language-switcher.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useTransition } from "react";
44
import { useRouter } from "next/navigation";
5+
import { LanguagesIcon } from "lucide-react";
56

67
import {
78
Select,
@@ -17,12 +18,12 @@ const oneYearInSeconds = 60 * 60 * 24 * 365;
1718

1819
type LanguageSwitcherProps = {
1920
className?: string;
20-
compactLabel?: boolean;
21+
mobileIcon?: boolean;
2122
};
2223

2324
export function LanguageSwitcher({
2425
className,
25-
compactLabel = false,
26+
mobileIcon = false,
2627
}: LanguageSwitcherProps) {
2728
const router = useRouter();
2829
const [isPending, startTransition] = useTransition();
@@ -47,15 +48,15 @@ export function LanguageSwitcher({
4748
className={className}
4849
disabled={isPending}
4950
>
50-
{compactLabel ? (
51-
<>
51+
{mobileIcon ? (
52+
<span className="inline-flex min-w-0 items-center">
5253
<span aria-hidden="true" className="sm:hidden">
53-
{getCompactLocaleLabel(locale)}
54+
<LanguagesIcon className="size-4" />
5455
</span>
5556
<span aria-hidden="true" className="hidden sm:inline">
5657
{currentLocaleLabel}
5758
</span>
58-
</>
59+
</span>
5960
) : (
6061
<SelectValue />
6162
)}
@@ -80,7 +81,3 @@ function getLocaleLabel(
8081
) {
8182
return locale === "de" ? labels.de : labels.en;
8283
}
83-
84-
function getCompactLocaleLabel(locale: AppLocale) {
85-
return locale.toUpperCase();
86-
}

src/lib/i18n/messages.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ export const en = {
4242
},
4343
appChrome: {
4444
newEvent: "New event",
45-
newEventCompact: "New",
4645
recentEvents: "Recent events",
47-
recentEventsCompact: "Recent",
4846
footerDescription:
4947
"{appName} is a self-hostable, realtime scheduling board for modern teams.",
5048
featureRequest: "Feature request or suggestion",
@@ -605,9 +603,7 @@ export const de: Messages = {
605603
},
606604
appChrome: {
607605
newEvent: "Neues Event",
608-
newEventCompact: "Neu",
609606
recentEvents: "Letzte Events",
610-
recentEventsCompact: "Events",
611607
footerDescription:
612608
"{appName} ist ein selbst hostbares Echtzeit-Planungsboard für moderne Teams.",
613609
featureRequest: "Feature-Wunsch oder Vorschlag",

0 commit comments

Comments
 (0)