Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MS-880 feat: add language select modal #191

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/storefront/messages/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
"locale": {
"region": "Region",
"language": "Language",
"region-settings": "Region and language settings"
"region-settings": "Region and language settings",
"continents": {
"asia-pacific": "Asia Pacific",
"europe": "Europe",
"north-america": "North America"
}
},
"filters": {
"filters": "Filters",
Expand Down
29 changes: 0 additions & 29 deletions apps/storefront/src/components/locale-switch/actions.ts

This file was deleted.

60 changes: 60 additions & 0 deletions apps/storefront/src/components/locale-switch/continent-row.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client";

import { Label } from "@nimara/ui/components/label";

import { Link, localePrefixes } from "@/i18n/routing";
import { DEFAULT_LOCALE, type Locale, type Market } from "@/regions/types";

export function ContinentRow({
currentLocale,
markets,
name,
}: {
currentLocale: Locale;
markets: Market[];
name: string;
}) {
return (
<>
<Label className="border-bottom block border-b border-stone-200 py-10 text-2xl font-normal leading-8">
{name}
</Label>

<div className="grid grid-cols-4 gap-8 py-4">
{markets.map((market) => (
<Link
style={{
// no idea how to apply conditional styles via tailwind
pointerEvents:
market.defaultLanguage.locale === currentLocale
? "none"
: "unset",
}}
key={market.id}
className="flex gap-1 px-1.5 py-2 hover:cursor-pointer"
href={
market.defaultLanguage.locale === DEFAULT_LOCALE
? "."
: localePrefixes[market.defaultLanguage.locale]
}
>
<div
// reused Button components classes, but I've not reused
// component itself as I don't know how to remove classes
// from it in Tailwind (h-10 and align-center)
className="inline-flex flex-col whitespace-nowrap rounded-md px-4 py-2 text-left text-sm font-normal leading-5 ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
>
<span>{market.name}</span>
{/* I've found only this information in figma: Buttons/'primary' [basic]/disabled. I have no idea how to translate it to tailwind color so I've pasted the rgba value */}
<span style={{ color: "rgba(168, 162, 158, 1)" }}>
{market.defaultLanguage.name}
</span>
</div>
</Link>
))}
</div>
</>
);
}

ContinentRow.displayName = "ContinentRow";
137 changes: 58 additions & 79 deletions apps/storefront/src/components/locale-switch/locale-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,76 @@
"use client";

import { useTranslations } from "next-intl";
import { useState } from "react";
import { createPortal } from "react-dom";

import { Button } from "@nimara/ui/components/button";
import {
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@nimara/ui/components/dialog";
import { Label } from "@nimara/ui/components/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@nimara/ui/components/select";

import { MARKETS } from "@/regions/config";
import type { MarketId, Region } from "@/regions/types";
import { type MarketId, type Region } from "@/regions/types";

import { handleLocaleFormSubmit } from "./actions";
import { ContinentRow } from "./continent-row";

export function LocaleSwitchModal({ region }: { region: Region }) {
export function LocaleSwitchModal({
onClose,
region,
}: {
onClose: () => void;
region: Region;
}) {
const t = useTranslations();
const markets = Object.values(MARKETS);
const defaultMarket = region.market.id.toUpperCase() as Uppercase<MarketId>;
const [currentRegion, setCurrentRegion] = useState<(typeof markets)[number]>(
MARKETS[defaultMarket],
);
const currentLocale = currentRegion.defaultLanguage.locale;

return (
<DialogContent className="bg-white sm:max-w-[425px]">
<form action={handleLocaleFormSubmit}>
<DialogHeader>
<DialogTitle>{t("locale.region-settings")}</DialogTitle>
</DialogHeader>
const currentLocale = MARKETS[defaultMarket].defaultLanguage.locale;

<div className="grid gap-4 py-4">
<Label>{t("locale.region")}</Label>
<Select
defaultValue={defaultMarket.toUpperCase()}
onValueChange={(value: Uppercase<MarketId>) =>
setCurrentRegion(MARKETS[value])
}
>
<SelectTrigger>
<SelectValue placeholder="market" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{markets.map((market) => (
<SelectItem key={market.id} value={market.id.toUpperCase()}>
{market.name} ({market.currency})
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
const marketsByContinent = {
asia_pacific: [], // currently there are no supported countries within that continent.
europe: markets.filter((market) => market.continent === "Europe"),
north_america: markets.filter(
(market) => market.continent === "North America",
),
};

<Label>{t("locale.language")}</Label>
<Select name="locale" value={currentLocale}>
<SelectTrigger>
<SelectValue placeholder="lang" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{currentRegion.supportedLanguages.map((language) => (
<SelectItem key={language.id} value={language.locale}>
{language.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
return createPortal(
<div
style={{
zIndex: 51, // 50 is max in Tailwind and it's used in topbar. I didn't wanted to changed it there to not introduce any regressions
}}
className="absolute inset-0 z-50 flex justify-center bg-white py-24"
>
{/* no idea how to represent 998px max-width in tailwind and I cannot retrieve padding values from figma, so I've moved it to styles */}
{/* as a result I don't really know how to apply mediaquery breakpoints as it's not class-based sizing */}
<div className="grow" style={{ maxWidth: "998px" }}>
<div className="mb-4 flex justify-between">
<Label className="text-lg font-semibold leading-7">
{t("locale.region-settings")}
</Label>
{/* I don't know how to add a new icon */}
<span onClick={onClose}>X</span>
</div>
<DialogFooter>
<div className="flex w-full items-center justify-between">
<DialogClose asChild>
<Button variant="ghost">{t("common.close")}</Button>
</DialogClose>
<DialogClose asChild>
<Button type="submit">{t("common.save")}</Button>
</DialogClose>
</div>
</DialogFooter>
</form>
</DialogContent>
{!!marketsByContinent.asia_pacific.length && (
<ContinentRow
currentLocale={currentLocale}
name={t("locale.continents.asia-pacific")}
markets={marketsByContinent.asia_pacific}
/>
)}
{!!marketsByContinent.europe.length && (
<ContinentRow
currentLocale={currentLocale}
name={t("locale.continents.europe")}
markets={marketsByContinent.europe}
/>
)}
{!!marketsByContinent.north_america.length && (
<ContinentRow
currentLocale={currentLocale}
name={t("locale.continents.north-america")}
markets={marketsByContinent.north_america}
/>
)}
</div>
</div>,
document.body,
);
}

Expand Down
30 changes: 21 additions & 9 deletions apps/storefront/src/components/locale-switch/locale-switch.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
"use client";

import { Globe } from "lucide-react";
import { useState } from "react";

import { Button } from "@nimara/ui/components/button";
import { Dialog, DialogTrigger } from "@nimara/ui/components/dialog";

import type { Region } from "@/regions/types";

import { LocaleSwitchModal } from "./locale-modal";

export const LocaleSwitch = ({ region }: { region: Region }) => {
const [showModal, setShowModal] = useState(false);

return (
<Dialog>
<DialogTrigger asChild>
<Button variant="ghost" size="default" className="gap-1.5">
<Globe className="h-4 w-4" /> {region.market.id.toLocaleUpperCase()}
</Button>
</DialogTrigger>
<LocaleSwitchModal region={region} />
</Dialog>
<>
<Button
variant="ghost"
size="default"
className="gap-1.5"
onClick={() => setShowModal(true)}
>
<Globe className="h-4 w-4" /> {region.market.id.toLocaleUpperCase()}
</Button>
{showModal && (
<LocaleSwitchModal
region={region}
onClose={() => setShowModal(false)}
/>
)}
</>
);
};

Expand Down
2 changes: 2 additions & 0 deletions apps/storefront/src/regions/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const MARKETS = {
name: "United Kingdom",
channel: "channel-uk",
currency: "GBP",
continent: "Europe",
countryCode: "GB",
defaultLanguage: LANGUAGES.GB,
supportedLanguages: [LANGUAGES.GB],
Expand All @@ -45,6 +46,7 @@ export const MARKETS = {
name: "United States of America",
channel: "channel-us",
currency: "USD",
continent: "North America",
countryCode: "US",
defaultLanguage: LANGUAGES.US,
supportedLanguages: [LANGUAGES.US],
Expand Down
3 changes: 3 additions & 0 deletions apps/storefront/src/regions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type CurrencyId = (typeof SUPPORTED_CURRENCIES)[number];

export type Market = {
channel: string;
continent: Continent;
countryCode: CountryCode;
currency: CurrencyId;
defaultLanguage: Language;
Expand All @@ -49,3 +50,5 @@ export type Region = {
language: Language;
market: Market;
};

export type Continent = "Asia Pacific" | "Europe" | "North America";