Skip to content

Commit d508620

Browse files
committed
- Dark Mode issue#157
- ThemeContext Implemented ThemeContext to provide a light/dark theme. - ThemeToggleButton Added a toggle button to header - _app Refined the structure of Map to now utilize PrimerTheme and ThemeContext to utilize theme change easily. - Header Adding ThemeToggleButton to the header. Fixing UI with flex and div - Header.scss Fixing UI with flex and justify content - package.json Added octicons to utilize theme icons - global.scss utilizing react-primer's built in theming to implement light/dark mode. The global css simply applied using the themeProvider's data-color-mode - ColourStyles.tsx Added in order to override the react-select components to override default styles and follow current theme. - LanguageFilter & SDGFilter Override default react-select style with newly implemented ColourStyles.
1 parent 9f714a9 commit d508620

11 files changed

+180
-40
lines changed

components/ColorStyles.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { StylesConfig } from "react-select";
2+
3+
export const ColorStyles: StylesConfig = {
4+
control: (styles) => ({ ...styles, backgroundColor: "var(--brand-color-canvas-default)" }),
5+
option: (styles, { isDisabled, isFocused, isSelected }) => {
6+
return {
7+
...styles,
8+
backgroundColor: isDisabled
9+
? undefined
10+
: isSelected
11+
? "var(--brand-color-text-subtle)"
12+
: isFocused
13+
? "var(--brand-color-focus)"
14+
: "var(--brand-color-canvas-default)",
15+
color: isDisabled ? "var(--brand-color-text-muted)" : "var(--brand-color-text-default)",
16+
cursor: isDisabled ? "not-allowed" : "default",
17+
18+
":active": {
19+
...styles[":active"],
20+
backgroundColor: !isDisabled ? "var(--brand-color-focus)" : undefined
21+
}
22+
};
23+
},
24+
menu: (styles) => ({
25+
...styles,
26+
backgroundColor: "var(--brand-color-canvas-default)",
27+
border: "1px solid var(--brand-color-border-default)"
28+
})
29+
};

components/Header/Header.module.scss

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
21
.siteHeader {
3-
background: #26292E;
2+
background: #26292e;
43

54
> div {
65
height: 72px;
@@ -13,7 +12,13 @@
1312
margin: 0 auto;
1413
}
1514

16-
@media(max-width: 800px) {
15+
> div > div {
16+
display: flex;
17+
align-items: center;
18+
grid-gap: 10px;
19+
}
20+
21+
@media (max-width: 800px) {
1722
.hideSm {
1823
display: none;
1924
}

components/Header/Header.tsx

+17-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import styles from "./Header.module.scss";
2-
import Link from 'next/link';
3-
import Image from 'next/image';
4-
import {Button} from '@primer/react-brand';
2+
import Link from "next/link";
3+
import Image from "next/image";
4+
import { Button } from "@primer/react-brand";
5+
import { ThemeToggleButton } from "./ThemeToggleButton";
56

67
export const Header = () => {
78
return (
89
<>
9-
<header className={styles.siteHeader} data-color-mode="dark">
10+
<header className={styles.siteHeader}>
1011
<div>
1112
<Link href="/" className={styles.homeLink}>
1213
<Image
@@ -16,15 +17,18 @@ export const Header = () => {
1617
alt="For Good First Issue logo"
1718
/>
1819
</Link>
19-
<Button
20-
variant="primary"
21-
as="a"
22-
href="https://github.com/rubyforgood/happycommits/issues/new?assignees=&labels=💪+New+Project&projects=&template=suggest_project.yml&title=%5BNew+Project%5D%3A+%3Ctitle%3E"
23-
target="_blank"
24-
rel="noopener noreferrer"
25-
>
26-
<span className={styles.btnText}>Recommend a project</span>
27-
</Button>
20+
<div>
21+
<Button
22+
variant="primary"
23+
as="a"
24+
href="https://github.com/rubyforgood/happycommits/issues/new?assignees=&labels=💪+New+Project&projects=&template=suggest_project.yml&title=%5BNew+Project%5D%3A+%3Ctitle%3E"
25+
target="_blank"
26+
rel="noopener noreferrer"
27+
>
28+
<span className={styles.btnText}>Recommend a project</span>
29+
</Button>
30+
<ThemeToggleButton />
31+
</div>
2832
</div>
2933
</header>
3034
</>
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from "react";
2+
import { useTheme } from "../../context/ThemeContext";
3+
import { SunIcon } from "@primer/octicons-react";
4+
import { MoonIcon } from "@primer/octicons-react";
5+
6+
import { Button } from "@primer/react-brand";
7+
8+
export const ThemeToggleButton = () => {
9+
const { colorMode, toggleColorMode } = useTheme();
10+
11+
return (
12+
<>
13+
<Button
14+
variant="subtle"
15+
size="medium"
16+
onClick={toggleColorMode}
17+
hasArrow={false}
18+
colorMode={colorMode}
19+
>
20+
{colorMode === "light" ? <MoonIcon size={24} fill="#fff" /> : <SunIcon size={24} />}
21+
</Button>
22+
</>
23+
);
24+
};

components/LanguageFilter.tsx

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Select from "react-select";
2+
import { ColorStyles } from "./ColorStyles";
23

34
type LanguageFilterProps = {
45
setSelectedLanguages: (languages: string[]) => void;
@@ -10,9 +11,18 @@ export const LanguageFilter = ({ setSelectedLanguages, languageOptions }: Langua
1011
<>
1112
<div>
1213
<label className="label">Language</label>
13-
<Select isMulti closeMenuOnSelect={false} className="" onChange={(selectedOptions) => setSelectedLanguages(selectedOptions.map((option) => option.value))} options={languageOptions} classNamePrefix="select" />
14+
<Select
15+
styles={ColorStyles}
16+
isMulti
17+
closeMenuOnSelect={false}
18+
className=""
19+
onChange={(selectedOptions) =>
20+
setSelectedLanguages(selectedOptions.map((option) => option.value))
21+
}
22+
options={languageOptions}
23+
classNamePrefix="select"
24+
/>
1425
</div>
1526
</>
16-
1727
);
1828
};

components/SDGFilter.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Select from "react-select";
2+
import { ColorStyles } from "./ColorStyles";
23

34
type SDGFilterProps = {
45
setSelectedTopics: (topics: string[]) => void;
@@ -13,6 +14,7 @@ export const SDGFilter = ({ setSelectedTopics, topicOptions }: SDGFilterProps) =
1314
<Select
1415
isMulti
1516
className=""
17+
styles={ColorStyles}
1618
options={topicOptions}
1719
getOptionLabel={(option) => option.label}
1820
getOptionValue={(option) => option.value ?? ""}

context/ThemeContext.tsx

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React, { createContext, useContext, useState } from "react";
2+
3+
interface ThemeContextType {
4+
colorMode: "light" | "dark";
5+
toggleColorMode: () => void;
6+
}
7+
8+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
9+
10+
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
11+
const [colorMode, setColorMode] = useState<"light" | "dark">("light");
12+
13+
const toggleColorMode = () => {
14+
setColorMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
15+
};
16+
17+
return (
18+
<ThemeContext.Provider value={{ colorMode, toggleColorMode }}>{children}</ThemeContext.Provider>
19+
);
20+
};
21+
22+
export const useTheme = () => {
23+
const context = useContext(ThemeContext);
24+
if (!context) {
25+
throw new Error("useTheme must be used within a ThemeProvider");
26+
}
27+
return context;
28+
};

package-lock.json

+19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@fortawesome/free-regular-svg-icons": "^6.5.0",
1616
"@fortawesome/free-solid-svg-icons": "^6.4.0",
1717
"@fortawesome/react-fontawesome": "^0.2.1",
18+
"@primer/octicons-react": "^19.15.0",
1819
"@primer/react-brand": "^0.44.1",
1920
"@types/node": "20.13.0",
2021
"@types/react": "^18.2.0",

pages/_app.tsx

+22-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
import '@primer/react-brand/lib/css/main.css';
2-
import '@primer/react-brand/fonts/fonts.css';
1+
import "@primer/react-brand/lib/css/main.css";
2+
import "@primer/react-brand/fonts/fonts.css";
33
import type { AppProps } from "next/app";
44
import Head from "next/head";
55
import { Layout } from "../components/Layout";
66
import { AppDataProvider } from "../context/AppDataContext";
77
import "../styles/globals.scss";
8-
import {ThemeProvider} from '@primer/react-brand'
8+
import { ThemeProvider as PrimerThemeProvider } from "@primer/react-brand";
9+
import { ThemeProvider, useTheme } from "../context/ThemeContext";
910

10-
// Fontawesome and TailwindCSS related settings
11-
//config.autoAddCss = false;
11+
function AppWithTheme({ Component, pageProps }: AppProps) {
12+
const { colorMode } = useTheme();
1213

13-
// Entry point for the app
14-
export default function App({ Component, pageProps }: AppProps) {
1514
return (
16-
<ThemeProvider>
15+
<PrimerThemeProvider
16+
colorMode={colorMode}
17+
style={{ backgroundColor: "var(--brand-color-canvas-default)" }}
18+
>
1719
<Head>
1820
<meta name="viewport" content="width=device-width, initial-scale=1" />
1921
</Head>
@@ -24,6 +26,18 @@ export default function App({ Component, pageProps }: AppProps) {
2426
</Layout>
2527
</main>
2628
</AppDataProvider>
29+
</PrimerThemeProvider>
30+
);
31+
}
32+
33+
// Fontawesome and TailwindCSS related settings
34+
//config.autoAddCss = false;
35+
36+
// Entry point for the app
37+
export default function App(props: AppProps) {
38+
return (
39+
<ThemeProvider>
40+
<AppWithTheme {...props} />
2741
</ThemeProvider>
2842
);
2943
}

0 commit comments

Comments
 (0)