From 79bcfd522e99f6ba4f809a156b2293f2137c5be5 Mon Sep 17 00:00:00 2001 From: Bryan Lundberg Date: Wed, 5 Jun 2024 05:02:13 -0600 Subject: [PATCH] feat: custom language picker (#311) --- package-lock.json | 28 +++++++- package.json | 2 + src/app/[locale]/solves/page.tsx | 4 +- src/components/button/Button.tsx | 31 +++++---- src/components/button/ButtonContainer.tsx | 26 -------- src/components/button/ButtonContent.tsx | 16 ----- .../menu-settings/MenuSelectLanguage.tsx | 65 +++++++++++++----- src/lib/const/languages.ts | 36 ---------- src/lib/const/languages.tsx | 66 +++++++++++++++++++ 9 files changed, 161 insertions(+), 113 deletions(-) delete mode 100644 src/components/button/ButtonContainer.tsx delete mode 100644 src/components/button/ButtonContent.tsx delete mode 100644 src/lib/const/languages.ts create mode 100644 src/lib/const/languages.tsx diff --git a/package-lock.json b/package-lock.json index e7622bea..1129ab20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "autoprefixer": "10.4.15", + "country-flag-icons": "^1.5.11", "cube-solver": "^2.4.1", "cubing": "^0.44.1", "date-fns": "^2.30.0", @@ -36,6 +37,7 @@ "recharts": "^2.8.0", "scrambow": "^1.8.1", "sharp": "0.32.6", + "tailwind-merge": "^2.3.0", "tailwindcss": "^3.4.0", "ts-deepmerge": "^7.0.0", "typescript": "5.2.2", @@ -68,9 +70,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", + "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1489,6 +1492,12 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/country-flag-icons": { + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.11.tgz", + "integrity": "sha512-B+mvFywunkRJs270k7kCBjhogvIA0uNn6GAXv6m2cPn3rrwqZzZVr2gBWcz+Cz7OGVWlcbERlYRIX0S6OGr8Bw==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5123,6 +5132,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.3.0.tgz", + "integrity": "sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", diff --git a/package.json b/package.json index 68720757..cc8659bc 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "autoprefixer": "10.4.15", + "country-flag-icons": "^1.5.11", "cube-solver": "^2.4.1", "cubing": "^0.44.1", "date-fns": "^2.30.0", @@ -39,6 +40,7 @@ "recharts": "^2.8.0", "scrambow": "^1.8.1", "sharp": "0.32.6", + "tailwind-merge": "^2.3.0", "tailwindcss": "^3.4.0", "ts-deepmerge": "^7.0.0", "typescript": "5.2.2", diff --git a/src/app/[locale]/solves/page.tsx b/src/app/[locale]/solves/page.tsx index 470ab3ae..5927b3f1 100644 --- a/src/app/[locale]/solves/page.tsx +++ b/src/app/[locale]/solves/page.tsx @@ -57,7 +57,7 @@ export default function SolvesPage() { onClick={() => setIsOpenMoveModal(true)} icon={} label={t("Inputs.move-all")} - isDisabled={ + disabled={ selectedCube && selectedCube.solves.session.length > 0 ? false : true @@ -67,7 +67,7 @@ export default function SolvesPage() { onClick={() => setIsOpenDeleteModal(true)} icon={} label={t("Inputs.trash-all")} - isDisabled={ + disabled={ selectedCube && selectedCube.solves.session.length > 0 ? false : true diff --git a/src/components/button/Button.tsx b/src/components/button/Button.tsx index 2a4525a8..e3dc319f 100644 --- a/src/components/button/Button.tsx +++ b/src/components/button/Button.tsx @@ -1,28 +1,35 @@ -import { ButtonContainer } from "./ButtonContainer"; -import { ButtonContent } from "./ButtonContent"; +import { twMerge } from "tailwind-merge"; -interface Button { +interface Button extends React.HTMLAttributes { className?: string; icon?: React.ReactNode; label: string; - onClick: () => void; minimalistic?: boolean; - isDisabled?: boolean; + disabled?: boolean; } export default function Button({ className, icon, label, - onClick, + disabled = false, minimalistic = true, - isDisabled = false + ...props }: Button) { return ( - <> - - - - + ); } diff --git a/src/components/button/ButtonContainer.tsx b/src/components/button/ButtonContainer.tsx deleted file mode 100644 index 8a224a37..00000000 --- a/src/components/button/ButtonContainer.tsx +++ /dev/null @@ -1,26 +0,0 @@ -interface ButtonContainer { - children: React.ReactNode; - className?: string; - handleClick: () => void; - isDisabled?: boolean; -} - -export function ButtonContainer({ - className, - children, - handleClick, - isDisabled, -}: ButtonContainer) { - return ( - <> - - - ); -} diff --git a/src/components/button/ButtonContent.tsx b/src/components/button/ButtonContent.tsx deleted file mode 100644 index 80623b84..00000000 --- a/src/components/button/ButtonContent.tsx +++ /dev/null @@ -1,16 +0,0 @@ -interface ButtonContent { - icon: React.ReactNode; - label: string; - minimalistic: boolean; -} - -export function ButtonContent({ icon, label, minimalistic }: ButtonContent) { - return ( - <> -
-
{icon}
-
{label}
-
- - ); -} diff --git a/src/components/menu-settings/MenuSelectLanguage.tsx b/src/components/menu-settings/MenuSelectLanguage.tsx index 1203a4a0..8184b596 100644 --- a/src/components/menu-settings/MenuSelectLanguage.tsx +++ b/src/components/menu-settings/MenuSelectLanguage.tsx @@ -1,48 +1,77 @@ +"use client"; + import { MenuSection } from "./MenuSection"; import { useLocale, useTranslations } from "next-intl"; import { languages } from "@/lib/const/languages"; -import { sort } from "fast-sort"; -import { ChangeEvent } from "react"; +import { useRef } from "react"; import { useRouter } from "@/navigation"; import { useSettingsModalStore } from "@/store/SettingsModalStore"; import { GlobeAltIcon } from "@heroicons/react/24/solid"; +import { Button } from "../button"; +import useClickOutside from "@/hooks/useClickOutside"; +import useOpenClose from "@/hooks/useOpenClose"; export default function MenuSelectLanguage() { + const { isOpen, close } = useOpenClose(false); + const componentRef = useRef(null); + useClickOutside(componentRef, () => close()); const { setSettingsOpen } = useSettingsModalStore(); const t = useTranslations("Index.Settings-menu"); const router = useRouter(); const locale = useLocale(); - function onSelectChange(event: ChangeEvent) { - const nextLocale = event.target.value; + function onSelectChange(event: any) { + const nextLocale = event.target.id; setSettingsOpen(false); router.replace(`${window.location.origin}/${nextLocale}`); } + const localeData = (locale: string) => { + const language = languages.find((item) => item.code === locale); + if (!language) return languages[0]; + return language; + }; + const labelData = localeData(locale)?.name.toString(); + const flagIcon = localeData(locale)?.flag; return ( <> } title={t("locale")} > -
+
{t("language")}
-
- +
+ )}
diff --git a/src/lib/const/languages.ts b/src/lib/const/languages.ts deleted file mode 100644 index fc77b91c..00000000 --- a/src/lib/const/languages.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { locales } from "@/navigation"; - -interface LanguageProp { - code: (typeof locales)[number]; - name: string; -} - -export const languages: LanguageProp[] = [ - { code: "en", name: "English" }, - { code: "es", name: "Español" }, - { code: "fr", name: "Français" }, - { code: "de", name: "Deutsch" }, - { code: "ja", name: "日本語" }, - { code: "zh", name: "中文" }, - { code: "ru", name: "Русский" }, - { code: "hi", name: "हिन्दी" }, - { code: "pt", name: "Português" }, - { code: "it", name: "Italiano" }, - { code: "ko", name: "한국어" }, - { code: "nl", name: "Nederlands" }, - { code: "sv", name: "Svenska" }, - { code: "tr", name: "Türkçe" }, - { code: "pl", name: "Polski" }, - { code: "vi", name: "Tiếng Việt" }, - { code: "fi", name: "Suomi" }, - { code: "uk", name: "Українська" }, - { code: "cs", name: "Čeština" }, - { code: "ro", name: "Română" }, - { code: "no", name: "Norsk" }, - { code: "ms", name: "Bahasa Melayu" }, - { code: "hu", name: "Magyar" }, - { code: "id", name: "Bahasa Indonesia" }, - { code: "bn", name: "বাংলা" }, - { code: "sk", name: "Slovenčina" }, - { code: "et", name: "Eesti" }, -]; diff --git a/src/lib/const/languages.tsx b/src/lib/const/languages.tsx new file mode 100644 index 00000000..6232320a --- /dev/null +++ b/src/lib/const/languages.tsx @@ -0,0 +1,66 @@ +import { locales } from "@/navigation"; +import { + US, + ES, + FR, + DE, + JP, + CN, + RU, + IN, + PT, + IT, + KR, + NL, + SE, + TR, + PL, + VN, + FI, + UA, + CZ, + RO, + NO, + MY, + HU, + ID, + BD, + SK, + EE, +} from "country-flag-icons/react/3x2"; + +interface LanguageProp { + code: (typeof locales)[number]; + name: string; + flag: React.ReactNode; +} + +export const languages: LanguageProp[] = [ + { code: "en", name: "English", flag: }, + { code: "es", name: "Español", flag: }, + { code: "fr", name: "Français", flag: }, + { code: "de", name: "Deutsch", flag: }, + { code: "ja", name: "日本語", flag: }, + { code: "zh", name: "中文", flag: }, + { code: "ru", name: "Русский", flag: }, + { code: "hi", name: "हिन्दी", flag: }, + { code: "pt", name: "Português", flag: }, + { code: "it", name: "Italiano", flag: }, + { code: "ko", name: "한국어", flag: }, + { code: "nl", name: "Nederlands", flag: }, + { code: "sv", name: "Svenska", flag: }, + { code: "tr", name: "Türkçe", flag: }, + { code: "pl", name: "Polski", flag: }, + { code: "vi", name: "Tiếng Việt", flag: }, + { code: "fi", name: "Suomi", flag: }, + { code: "uk", name: "Українська", flag: }, + { code: "cs", name: "Čeština", flag: }, + { code: "ro", name: "Română", flag: }, + { code: "no", name: "Norsk", flag: }, + { code: "ms", name: "Bahasa Melayu", flag: }, + { code: "hu", name: "Magyar", flag: }, + { code: "id", name: "Bahasa Indonesia", flag: }, + { code: "bn", name: "বাংলা", flag: }, + { code: "sk", name: "Slovenčina", flag: }, + { code: "et", name: "Eesti", flag: }, +];