Skip to content

Commit 8d375ad

Browse files
committed
Enhance UI components by updating button text color in CustomHeader for better visibility, adding ThemeSwitcher to Footer for improved user experience, and refactoring LanguageSwitcher to use a button dropdown for language selection with country flags.
1 parent 2d237e7 commit 8d375ad

File tree

4 files changed

+81
-27
lines changed

4 files changed

+81
-27
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as CountryFlags from "country-flag-icons/react/3x2";
2+
import React from "react";
3+
4+
export function CountryFlag({
5+
country,
6+
className,
7+
}: {
8+
country: string;
9+
className?: string;
10+
}) {
11+
const Flag = CountryFlags[country as keyof typeof CountryFlags];
12+
return Flag ? <Flag className={className ?? "w-5"} /> : null;
13+
}

docs/src/components/CustomHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export function CustomHeader() {
111111
<AppLink href="https://app.rybbit.io/signup" target="_blank">
112112
<button
113113
onClick={() => trackAdEvent("signup", { location: "header" })}
114-
className="bg-emerald-600 hover:bg-emerald-500 text-neutral-900 dark:text-white text-sm font-medium px-3 py-1.5 rounded-md transform hover:-translate-y-0.5 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-emerald-200 focus:ring-opacity-50"
114+
className="bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-medium px-3 py-1.5 rounded-md transform hover:-translate-y-0.5 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-emerald-200 focus:ring-opacity-50"
115115
>
116116
{t("Sign up")}
117117
</button>

docs/src/components/Footer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Image from "next/image";
22
import Link from "next/link";
33
import { useExtracted } from "next-intl";
44
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
5+
import { ThemeSwitcher } from "./ThemeSwitcher";
56

67
export function Footer() {
78
const t = useExtracted();
@@ -234,7 +235,8 @@ export function Footer() {
234235
<div className="pt-8 border-t border-neutral-200 dark:border-neutral-800">
235236
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
236237
<div className="text-sm text-neutral-400">{t("© {year} Rybbit. All rights reserved.", { year: String(new Date().getFullYear()) })}</div>
237-
<div className="text-sm text-neutral-400 space-x-4">
238+
<div className="text-sm text-neutral-400 space-x-4 flex items-center">
239+
<ThemeSwitcher />
238240
<LanguageSwitcher />
239241
{t("Made with ❤️ by frogs")}{" "}
240242
<a href="https://tomato.gg" target="_blank" title="Tomato.gg">
Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,82 @@
11
"use client";
22

3+
import { useState, useRef, useEffect } from "react";
34
import { useLocale } from "next-intl";
45
import { usePathname, useRouter } from "@/i18n/navigation";
56
import { routing } from "@/i18n/routing";
7+
import { CountryFlag } from "./CountryFlag";
68

79
const LOCALE_OPTIONS = [
8-
{ value: "en", label: "English", flag: "🇺🇸" },
9-
{ value: "de", label: "Deutsch", flag: "🇩🇪" },
10-
{ value: "fr", label: "Français", flag: "🇫🇷" },
11-
{ value: "zh", label: "简体中文", flag: "🇨🇳" },
12-
{ value: "es", label: "Español", flag: "🇪🇸" },
13-
{ value: "pl", label: "Polski", flag: "🇵🇱" },
14-
{ value: "it", label: "Italiano", flag: "🇮🇹" },
15-
{ value: "ko", label: "한국어", flag: "🇰🇷" },
16-
{ value: "pt", label: "Português", flag: "🇧🇷" },
17-
{ value: "ja", label: "日本語", flag: "🇯🇵" },
18-
] as const satisfies { value: (typeof routing.locales)[number]; label: string; flag: string }[];
10+
{ value: "en", label: "English", country: "US" },
11+
{ value: "de", label: "Deutsch", country: "DE" },
12+
{ value: "fr", label: "Français", country: "FR" },
13+
{ value: "zh", label: "简体中文", country: "CN" },
14+
{ value: "es", label: "Español", country: "ES" },
15+
{ value: "pl", label: "Polski", country: "PL" },
16+
{ value: "it", label: "Italiano", country: "IT" },
17+
{ value: "ko", label: "한국어", country: "KR" },
18+
{ value: "pt", label: "Português", country: "BR" },
19+
{ value: "ja", label: "日本語", country: "JP" },
20+
] as const satisfies {
21+
value: (typeof routing.locales)[number];
22+
label: string;
23+
country: string;
24+
}[];
1925

2026
export function LanguageSwitcher() {
2127
const currentLocale = useLocale();
2228
const router = useRouter();
2329
const pathname = usePathname();
30+
const [open, setOpen] = useState(false);
31+
const ref = useRef<HTMLDivElement>(null);
2432

25-
function handleLocaleChange(e: React.ChangeEvent<HTMLSelectElement>) {
26-
router.replace(pathname, { locale: e.target.value });
33+
const current = LOCALE_OPTIONS.find((o) => o.value === currentLocale) ?? LOCALE_OPTIONS[0];
34+
35+
function handleLocaleChange(locale: string) {
36+
router.replace(pathname, { locale });
37+
setOpen(false);
2738
}
2839

40+
useEffect(() => {
41+
function onClickOutside(e: MouseEvent) {
42+
if (ref.current && !ref.current.contains(e.target as Node)) {
43+
setOpen(false);
44+
}
45+
}
46+
document.addEventListener("mousedown", onClickOutside);
47+
return () => document.removeEventListener("mousedown", onClickOutside);
48+
}, []);
49+
2950
return (
30-
<select
31-
value={currentLocale}
32-
onChange={handleLocaleChange}
33-
className="bg-transparent text-sm text-neutral-500 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors cursor-pointer border border-neutral-300 dark:border-neutral-700 rounded-md px-2 py-1 focus:outline-none focus:ring-1 focus:ring-neutral-400"
34-
aria-label="Select language"
35-
>
36-
{LOCALE_OPTIONS.map((option) => (
37-
<option key={option.value} value={option.value}>
38-
{option.flag} {option.label}
39-
</option>
40-
))}
41-
</select>
51+
<div className="relative" ref={ref}>
52+
<button
53+
type="button"
54+
onClick={() => setOpen((v) => !v)}
55+
className="flex items-center gap-2 bg-transparent text-sm text-neutral-500 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors cursor-pointer border border-neutral-300 dark:border-neutral-700 rounded-md px-2 py-1 focus:outline-none focus:ring-1 focus:ring-neutral-400"
56+
aria-label="Select language"
57+
>
58+
<CountryFlag country={current.country} className="w-4" />
59+
{current.label}
60+
</button>
61+
{open && (
62+
<div className="absolute right-0 bottom-full mb-1 z-50 min-w-[160px] rounded-md border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 shadow-lg py-1">
63+
{LOCALE_OPTIONS.map((option) => (
64+
<button
65+
key={option.value}
66+
type="button"
67+
onClick={() => handleLocaleChange(option.value)}
68+
className={`flex w-full items-center gap-2 px-3 py-1.5 text-sm hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors ${
69+
option.value === currentLocale
70+
? "text-neutral-900 dark:text-white font-medium"
71+
: "text-neutral-600 dark:text-neutral-400"
72+
}`}
73+
>
74+
<CountryFlag country={option.country} className="w-4" />
75+
{option.label}
76+
</button>
77+
))}
78+
</div>
79+
)}
80+
</div>
4281
);
4382
}

0 commit comments

Comments
 (0)