Skip to content

Commit c1501c8

Browse files
committed
feat(themes): add i18n labels to AppearanceSwitcher and remove ThemePalette from public exports
1 parent d4643dd commit c1501c8

3 files changed

Lines changed: 55 additions & 34 deletions

File tree

packages/core/src/components/appearance-switcher.test.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
33
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
44

55
import { COLOR_THEME_OPTIONS, ThemeProvider } from "@/contexts/theme-context";
6+
import { createAppShellWrapper } from "../../tests/test-utils";
67
import { AppearanceSwitcher } from "./appearance-switcher";
78

89
/** happy-dom / Node can omit a full `localStorage`; ThemeProvider persists via it. */
@@ -62,14 +63,18 @@ afterEach(() => {
6263
cleanup();
6364
});
6465

66+
const AppShellWrapper = createAppShellWrapper("en");
67+
6568
describe("AppearanceSwitcher", () => {
6669
it("opens a menu listing every mode option", async () => {
6770
const user = userEvent.setup();
6871

6972
render(
70-
<ThemeProvider defaultColorTheme="light">
71-
<AppearanceSwitcher />
72-
</ThemeProvider>,
73+
<AppShellWrapper>
74+
<ThemeProvider defaultColorTheme="light">
75+
<AppearanceSwitcher />
76+
</ThemeProvider>
77+
</AppShellWrapper>,
7378
);
7479

7580
await user.click(screen.getByRole("button", { name: "Appearance" }));
@@ -87,9 +92,11 @@ describe("AppearanceSwitcher", () => {
8792
const user = userEvent.setup();
8893

8994
render(
90-
<ThemeProvider>
91-
<AppearanceSwitcher />
92-
</ThemeProvider>,
95+
<AppShellWrapper>
96+
<ThemeProvider>
97+
<AppearanceSwitcher />
98+
</ThemeProvider>
99+
</AppShellWrapper>,
93100
);
94101

95102
await user.click(screen.getByRole("button", { name: "Appearance" }));
@@ -104,9 +111,11 @@ describe("AppearanceSwitcher", () => {
104111

105112
it("exposes the resolved mode on the trigger when system mode is selected", () => {
106113
render(
107-
<ThemeProvider defaultColorTheme="system">
108-
<AppearanceSwitcher />
109-
</ThemeProvider>,
114+
<AppShellWrapper>
115+
<ThemeProvider defaultColorTheme="system">
116+
<AppearanceSwitcher />
117+
</ThemeProvider>
118+
</AppShellWrapper>,
110119
);
111120

112121
const btn = screen.getByRole("button", { name: "Appearance" });
@@ -118,9 +127,11 @@ describe("AppearanceSwitcher", () => {
118127
const user = userEvent.setup();
119128

120129
render(
121-
<ThemeProvider defaultColorTheme="light">
122-
<AppearanceSwitcher />
123-
</ThemeProvider>,
130+
<AppShellWrapper>
131+
<ThemeProvider defaultColorTheme="light">
132+
<AppearanceSwitcher />
133+
</ThemeProvider>
134+
</AppShellWrapper>,
124135
);
125136

126137
await user.click(screen.getByRole("button", { name: "Appearance" }));

packages/core/src/components/appearance-switcher.tsx

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,29 @@ import { Palette } from "lucide-react";
33
import { Menu } from "@/components/menu";
44
import { Button } from "@/components/button";
55
import { cn } from "@/lib/utils";
6-
import {
7-
useTheme,
8-
type ResolvedColorTheme,
9-
type ColorTheme,
10-
COLOR_THEME_OPTIONS,
11-
} from "@/contexts/theme-context";
6+
import { defineI18nLabels } from "@/hooks/i18n";
7+
import { useTheme, type ColorTheme, COLOR_THEME_OPTIONS } from "@/contexts/theme-context";
128

13-
const RESOLVED_COLOR_THEME_SHORT: Record<ResolvedColorTheme, string> = {
14-
light: "Light",
15-
dark: "Dark",
16-
};
9+
const appearanceSwitcherLabels = defineI18nLabels({
10+
en: {
11+
appearance: "Appearance",
12+
chooseAppearance: "Choose appearance",
13+
followingSystem: (props: { resolved: string }) =>
14+
`Following system — currently ${props.resolved}`,
15+
light: "Light",
16+
dark: "Dark",
17+
system: "System",
18+
},
19+
ja: {
20+
appearance: "外観",
21+
chooseAppearance: "外観を選択",
22+
followingSystem: (props: { resolved: string }) => `システムに従う — 現在${props.resolved}`,
23+
light: "ライト",
24+
dark: "ダーク",
25+
system: "システム",
26+
},
27+
});
28+
const useT = appearanceSwitcherLabels.useT;
1729

1830
/**
1931
* Decorative dual swatches for the color theme picker. Light/dark show their surface
@@ -70,11 +82,12 @@ function ColorThemePreviewSwatches({ colorTheme }: { colorTheme: ColorTheme }) {
7082
*/
7183
function AppearanceSwitcher() {
7284
const { theme, resolvedTheme, setTheme } = useTheme();
85+
const t = useT();
7386

7487
const triggerTitle =
7588
theme === "system"
76-
? `Following system — currently ${RESOLVED_COLOR_THEME_SHORT[resolvedTheme]}`
77-
: "Choose appearance";
89+
? t("followingSystem", { resolved: t(resolvedTheme) })
90+
: t("chooseAppearance");
7891

7992
return (
8093
<Menu.Root modal={false}>
@@ -85,7 +98,7 @@ function AppearanceSwitcher() {
8598
variant="outline"
8699
size="icon"
87100
className="astw:shrink-0"
88-
aria-label="Appearance"
101+
aria-label={t("appearance")}
89102
title={triggerTitle}
90103
/>
91104
}
@@ -97,7 +110,9 @@ function AppearanceSwitcher() {
97110
className="astw:min-w-[16.5rem] astw:rounded-xl astw:p-3"
98111
>
99112
<Menu.Group>
100-
<Menu.GroupLabel className="astw:px-0 astw:pb-2 astw:pt-0">Appearance</Menu.GroupLabel>
113+
<Menu.GroupLabel className="astw:px-0 astw:pb-2 astw:pt-0">
114+
{t("appearance")}
115+
</Menu.GroupLabel>
101116
<Menu.RadioGroup
102117
className="astw:grid astw:grid-cols-3 astw:gap-2"
103118
value={theme}
@@ -112,11 +127,11 @@ function AppearanceSwitcher() {
112127
className={radioItemClasses(theme === opt.value)}
113128
>
114129
<Menu.RadioItemIndicator className="astw:sr-only">
115-
{opt.label}
130+
{t(opt.value)}
116131
</Menu.RadioItemIndicator>
117132
<ColorThemePreviewSwatches colorTheme={opt.value} />
118133
<span className="astw:block astw:w-full astw:truncate astw:px-0.5">
119-
{opt.label}
134+
{t(opt.value)}
120135
</span>
121136
</Menu.RadioItem>
122137
))}

packages/core/src/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,7 @@ export {
2424
export { WithGuard, type WithGuardProps } from "./components/with-guard";
2525

2626
export { useAppShell, useAppShellConfig, useAppShellData } from "./contexts/appshell-context";
27-
export {
28-
useTheme,
29-
type ColorTheme,
30-
type ResolvedColorTheme,
31-
type ThemePalette,
32-
} from "./contexts/theme-context";
27+
export { useTheme, type ColorTheme, type ResolvedColorTheme } from "./contexts/theme-context";
3328
export { AppearanceSwitcher } from "./components/appearance-switcher";
3429
export { type I18nLabels, defineI18nLabels } from "./hooks/i18n";
3530
export {

0 commit comments

Comments
 (0)