Skip to content

Commit 08a50e4

Browse files
create theme-picker component
1 parent 273a8e4 commit 08a50e4

File tree

6 files changed

+148
-21
lines changed

6 files changed

+148
-21
lines changed

src/features/common/components/headers/header/header.component.tsx

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import React from "react";
3+
import React, { useCallback, useMemo, useState } from "react";
44
import { usePathname } from "next/navigation";
55
import { DEFAULT_LANGUAGE_CODE } from "@/features/localization/localization.config";
66
import { LayoutDictionaryModel } from "@/features/localization/models/layout-dictionary.model";
@@ -10,13 +10,30 @@ import styles from "./header.module.scss";
1010
import { BoxComponent } from "@/features/common/components/box/box.component";
1111
import Link from "next/link";
1212
import { SiteBrandComponent } from "@/features/common/components/site-brand/site-brand.component";
13+
import { ThemePickerComponent } from "../../theme-picker/theme-picker.component";
14+
import {
15+
getSanitizedThemePickerCodeValue,
16+
isLightThemePreference,
17+
isSystemThemePreference,
18+
} from "@/features/themes/services/theme.utils";
19+
import { SystemIconComponent } from "../../bars/ribbon/assets/system-icon.component";
20+
import { LightIconComponent } from "../../bars/ribbon/assets/light-icon.component";
21+
import { DarkIconComponent } from "../../bars/ribbon/assets/dark-icon.component";
22+
import {
23+
ThemeCookieValues,
24+
ThemePickerCodeValues,
25+
} from "@/features/common/values/theme.values";
26+
import { ThemeModel } from "@/features/common/models/theme.model";
27+
import { savePreferredThemeInCookie } from "@/features/themes/services/theme.client.utils";
1328

1429
interface HeaderComponentProps {
30+
themeCode: ThemeCookieValues;
1531
languageCode: string;
16-
dictionary: LayoutDictionaryModel["header"];
32+
dictionary: LayoutDictionaryModel;
1733
}
1834

1935
export const HeaderComponent: React.FC<HeaderComponentProps> = ({
36+
themeCode,
2037
languageCode,
2138
dictionary,
2239
}) => {
@@ -33,6 +50,50 @@ export const HeaderComponent: React.FC<HeaderComponentProps> = ({
3350
? sitePaths.root
3451
: createUrlPath([languageCode]);
3552

53+
const themeOptions = useMemo(
54+
() =>
55+
dictionary.ribbon.themePicker.options.map((option) => {
56+
return {
57+
code: option.code,
58+
label: option.label,
59+
icon: isSystemThemePreference(option.code) ? (
60+
<SystemIconComponent />
61+
) : isLightThemePreference(option.code) ? (
62+
<LightIconComponent />
63+
) : (
64+
<DarkIconComponent />
65+
),
66+
};
67+
}),
68+
[dictionary.ribbon.themePicker.options]
69+
);
70+
71+
const sanitizedThemePickerCodeValue = useMemo(() => {
72+
return getSanitizedThemePickerCodeValue(themeCode);
73+
}, [themeCode]);
74+
75+
const [currentTheme, setCurrentTheme] = useState<ThemeModel>(
76+
dictionary.ribbon.themePicker.options.filter((element) =>
77+
isSystemThemePreference(themeCode)
78+
? isSystemThemePreference(element.code)
79+
: element.code === sanitizedThemePickerCodeValue
80+
)[0]
81+
);
82+
83+
const handleThemeSelection = useCallback(
84+
async (value: ThemePickerCodeValues) => {
85+
const themePreference = await savePreferredThemeInCookie(
86+
value,
87+
languageCode
88+
);
89+
90+
if (themePreference) {
91+
setCurrentTheme(themePreference);
92+
}
93+
},
94+
[languageCode]
95+
);
96+
3697
return (
3798
<BoxComponent
3899
contentAs="nav"
@@ -42,12 +103,15 @@ export const HeaderComponent: React.FC<HeaderComponentProps> = ({
42103
aria-label="Main navigation"
43104
>
44105
<div className={styles.brand}>
45-
<SiteBrandComponent path={languagePathPrefix} languageCode={languageCode} />
106+
<SiteBrandComponent
107+
path={languagePathPrefix}
108+
languageCode={languageCode}
109+
/>
46110
</div>
47111
<div className={styles.navContainer}>
48112
<div className={styles.navTabs}>
49113
<ul className={styles.navList}>
50-
{dictionary.links.map((link) => {
114+
{dictionary.header.links.map((link) => {
51115
const linkPath =
52116
languageCode === DEFAULT_LANGUAGE_CODE || link.isExternal
53117
? link.path
@@ -73,6 +137,13 @@ export const HeaderComponent: React.FC<HeaderComponentProps> = ({
73137
</ul>
74138
</div>
75139
</div>
140+
<div className={styles.actions}>
141+
<ThemePickerComponent
142+
options={themeOptions}
143+
handleSelection={handleThemeSelection}
144+
selectedOptionCode={currentTheme.code}
145+
/>
146+
</div>
76147
</BoxComponent>
77148
);
78149
};

src/features/common/components/headers/mobile-header/mobile-header.component.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import { DEFAULT_LANGUAGE_CODE } from "@/features/localization/localization.conf
1111
import { sitePaths } from "@/features/seo/site-tree";
1212
import { createUrlPath, getPathnameSegments } from "@/libs/utils/path.utils";
1313
import { SiteBrandComponent } from "@/features/common/components/site-brand/site-brand.component";
14+
import { ThemeCookieValues } from "@/features/common/values/theme.values";
1415

1516
interface MobileHeaderComponentProps {
17+
themeCode: ThemeCookieValues;
1618
languageCode: string;
17-
dictionary: LayoutDictionaryModel["header"];
19+
dictionary: LayoutDictionaryModel;
1820
}
1921

2022
export const MobileHeaderComponent: React.FC<MobileHeaderComponentProps> = ({
@@ -90,8 +92,8 @@ export const MobileHeaderComponent: React.FC<MobileHeaderComponentProps> = ({
9092
onClick={toggleMobileMenu}
9193
aria-label={
9294
mobileMenuState === MobileMenuStateValues.OPEN
93-
? dictionary.labels.close
94-
: dictionary.labels.open
95+
? dictionary.header.labels.close
96+
: dictionary.header.labels.open
9597
}
9698
aria-expanded={mobileMenuState === MobileMenuStateValues.OPEN}
9799
>
@@ -110,7 +112,7 @@ export const MobileHeaderComponent: React.FC<MobileHeaderComponentProps> = ({
110112
contentClassName={styles.menuContent}
111113
>
112114
<ul className={styles.menu__list}>
113-
{dictionary.links.map((link) => {
115+
{dictionary.header.links.map((link) => {
114116
const linkPath =
115117
languageCode === DEFAULT_LANGUAGE_CODE || link.isExternal
116118
? link.path

src/features/common/components/layout/page-header/page-header.component.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
"use client";
2+
13
import React from "react";
2-
import { RibbonComponent } from "@/features/common/components/bars/ribbon/ribbon.component";
34
import { MobileHeaderComponent } from "@/features/common/components/headers/mobile-header/mobile-header.component";
45
import { HeaderComponent } from "@/features/common/components/headers/header/header.component";
56
import { ThemeCookieValues } from "@/features/common/values/theme.values";
@@ -19,12 +20,14 @@ export const PageHeaderComponent: React.FC<PageHeaderComponentProps> = ({
1920
return (
2021
<header>
2122
<MobileHeaderComponent
23+
themeCode={themeCode}
2224
languageCode={languageCode}
23-
dictionary={layoutDictionary.header}
25+
dictionary={layoutDictionary}
2426
/>
2527
<HeaderComponent
28+
themeCode={themeCode}
2629
languageCode={languageCode}
27-
dictionary={layoutDictionary.header}
30+
dictionary={layoutDictionary}
2831
/>
2932
</header>
3033
);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import styles from "./theme-picker.module.scss";
2+
import { ThemePickerCodeValues } from "../../values/theme.values";
3+
4+
interface ThemePickerComponentProps {
5+
options: {
6+
code: ThemePickerCodeValues;
7+
icon: React.ReactNode;
8+
label: string;
9+
}[];
10+
selectedOptionCode: string;
11+
handleSelection: (value: ThemePickerCodeValues) => Promise<void>;
12+
}
13+
14+
enum PicketListItemStateValues {
15+
INACTIVE = "INACTIVE",
16+
ACTIVE = "ACTIVE",
17+
}
18+
19+
export const ThemePickerComponent: React.FC<ThemePickerComponentProps> = ({
20+
options,
21+
selectedOptionCode,
22+
handleSelection,
23+
}) => {
24+
return (
25+
<div className={styles.container}>
26+
<div className={styles.wrapper}>
27+
{options.map((option) => {
28+
return (
29+
<div
30+
key={option.code}
31+
className={styles.option}
32+
data-active={
33+
option.code === selectedOptionCode
34+
? PicketListItemStateValues.ACTIVE
35+
: PicketListItemStateValues.INACTIVE
36+
}
37+
title={option.label}
38+
onClick={async () => {
39+
console.log(option.label)
40+
await handleSelection(option.code);
41+
}}
42+
>
43+
<span className={styles.option__icon}>{option.icon}</span>
44+
</div>
45+
);
46+
})}
47+
</div>
48+
</div>
49+
);
50+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.container {
2+
3+
}
4+
5+
.wrapper {
6+
7+
}
8+
9+
.option {
10+
11+
}

src/features/common/components/theme-toggle/theme-toggle.component.tsx

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)