Skip to content

Commit 591cc74

Browse files
committed
Add switch theme component
1 parent 2b960fd commit 591cc74

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

src/SwitchColorTheme.tsx

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import React, { memo, forwardRef, useId } from "react";
2+
import { fr } from "./lib";
3+
import { symToStr } from "tsafe/symToStr";
4+
import { createComponentI18nApi } from "./lib/i18n";
5+
import { cx } from "./lib/tools/cx";
6+
import type { Equals } from "tsafe";
7+
import { assert } from "tsafe/assert";
8+
import ArtworkLightSvg from "./dsfr/artwork/light.svg";
9+
import ArtworkDarkSvg from "./dsfr/artwork/dark.svg";
10+
import ArtworkSystemSvg from "./dsfr/artwork/system.svg";
11+
import { getAssetUrl } from "./lib/tools/getAssetUrl";
12+
13+
export type DisplayProps = {
14+
className?: string;
15+
};
16+
17+
export const SwitchColorTheme = memo(
18+
forwardRef<HTMLButtonElement, DisplayProps>((props, ref) => {
19+
const { className, ...rest } = props;
20+
21+
assert<Equals<keyof typeof rest, never>>();
22+
23+
const { t } = useTranslation();
24+
25+
const dialogId = useId();
26+
const dialogTitleId = useId();
27+
28+
return (
29+
<>
30+
<button
31+
className={cx(
32+
fr.cx("fr-link", "fr-icon-theme-fill", "fr-link--icon-left"),
33+
className
34+
)}
35+
aria-controls={dialogId}
36+
data-fr-opened={false}
37+
ref={ref}
38+
{...rest}
39+
>
40+
{t("display settings")}
41+
</button>
42+
43+
<dialog
44+
id={dialogId}
45+
className={fr.cx("fr-modal")}
46+
role="dialog"
47+
aria-labelledby={dialogTitleId}
48+
>
49+
<div
50+
className={fr.cx("fr-container", "fr-container--fluid", "fr-container-md")}
51+
>
52+
<div className={fr.cx("fr-grid-row", "fr-grid-row--center")}>
53+
<div className={fr.cx("fr-col-12", "fr-col-md-6", "fr-col-lg-4")}>
54+
<div className={fr.cx("fr-modal__body")}>
55+
<div className={fr.cx("fr-modal__header")}>
56+
<button
57+
className={fr.cx("fr-btn--close", "fr-btn")}
58+
aria-controls={dialogId}
59+
title={t("close")}
60+
>
61+
{t("close")}
62+
</button>
63+
</div>
64+
<div className="fr-modal__content">
65+
<h1 id={dialogTitleId} className={fr.cx("fr-modal__title")}>
66+
{t("display settings")}
67+
</h1>
68+
<div /*id="fr-display"*/ className={"fr-display"}>
69+
<div /*className={fr.cx("fr-form-group" as any)}*/>
70+
<fieldset className={fr.cx("fr-fieldset")}>
71+
<legend
72+
className={fr.cx(
73+
"fr-fieldset__legend",
74+
"fr-text--regular"
75+
)}
76+
id="-legend"
77+
>
78+
{t("pick a theme")}
79+
</legend>
80+
<div className={fr.cx("fr-fieldset__content")}>
81+
{(["light", "dark", "system"] as const).map(
82+
theme => (
83+
<RadioGroup
84+
key={theme}
85+
theme={theme}
86+
/>
87+
)
88+
)}
89+
</div>
90+
</fieldset>
91+
</div>
92+
</div>
93+
</div>
94+
</div>
95+
</div>
96+
</div>
97+
</div>
98+
</dialog>
99+
</>
100+
);
101+
})
102+
);
103+
104+
SwitchColorTheme.displayName = symToStr({ SwitchColorTheme });
105+
106+
export default SwitchColorTheme;
107+
108+
const RadioGroup = memo((props: { theme: "dark" | "light" | "system" }) => {
109+
const { theme } = props;
110+
111+
const inputId = useId();
112+
113+
const { t } = useTranslation();
114+
115+
const pictogramUrl = getAssetUrl(
116+
(() => {
117+
switch (theme) {
118+
case "dark":
119+
return ArtworkDarkSvg;
120+
case "light":
121+
return ArtworkLightSvg;
122+
case "system":
123+
return ArtworkSystemSvg;
124+
}
125+
})()
126+
);
127+
128+
return (
129+
<div key={theme} className={fr.cx("fr-radio-group", "fr-radio-rich")}>
130+
<input value={theme} type="radio" id={inputId} name="fr-radios-theme" />
131+
<label className="fr-label" htmlFor={inputId}>
132+
{t(`${theme} theme`)}
133+
{theme === "system" && (
134+
<span className={fr.cx("fr-hint-text")}>{t("system theme hint")}</span>
135+
)}
136+
</label>
137+
<div className={fr.cx("fr-radio-rich__img")}>
138+
<svg
139+
xmlns="http://www.w3.org/2000/svg"
140+
//className={fr.cx("fr-artwork")}
141+
width="80px"
142+
height="80px"
143+
viewBox="0 0 80 80"
144+
>
145+
{(["artwork-decorative", "artwork-minor", "artwork-major"] as const).map(
146+
label => (
147+
<use
148+
key={label}
149+
className={fr.cx(`fr-${label}`)}
150+
xlinkHref={`${pictogramUrl}#${label}`}
151+
/>
152+
)
153+
)}
154+
</svg>
155+
</div>
156+
</div>
157+
);
158+
});
159+
160+
RadioGroup.displayName = symToStr({ RadioGroup });
161+
162+
const { useTranslation, addSwitchColorThemeTranslations } = createComponentI18nApi({
163+
"componentName": "SwitchColorTheme",
164+
"frMessages": {
165+
/* spell-checker: disable */
166+
"display settings": "Paramètres d'affichage",
167+
"close": "Fermer",
168+
"pick a theme": `Choisissez un thème pour personnaliser l'apparence du site.`,
169+
"light theme": `Thème clair`,
170+
"dark theme": `Thème sombre`,
171+
"system theme": `Système.`,
172+
"system theme hint": `Utilise les paramètres système.`
173+
/* spell-checker: enable */
174+
}
175+
});
176+
177+
addSwitchColorThemeTranslations({
178+
"lang": "en",
179+
"messages": {
180+
"display settings": "Display settings",
181+
"close": "Close",
182+
"pick a theme": `Pick a theme to customize the website's look.`,
183+
"light theme": `Light theme`,
184+
"dark theme": "Dark theme",
185+
"system theme": `System.`,
186+
"system theme hint": "Use system preference"
187+
}
188+
});
189+
190+
addSwitchColorThemeTranslations({
191+
"lang": "es",
192+
"messages": {
193+
/* spell-checker: disable */
194+
"display settings": "Parámetro de visualización",
195+
"close": "Cerrar",
196+
"pick a theme": `Elija un tema para personalizar el aspecto del sitio.`
197+
/* spell-checker: enable */
198+
}
199+
});
200+
201+
export { addSwitchColorThemeTranslations };

0 commit comments

Comments
 (0)