Skip to content

Commit 5bd84f6

Browse files
nelldinostrdr4605
andauthored
Styling (#37)
* add card flip animation * add dark/light theme * fix styling to handle light mode * add cursor pointer for buttons * move from styled components to tailwind * fix typo * change margin sizes * implement dark/light theme switcher with custom variables * add theme change according to the system preference * chore: remove unused useTheme, change eslintrc --------- Co-authored-by: Dragoș Străinu <[email protected]>
1 parent 80189f7 commit 5bd84f6

File tree

13 files changed

+221
-141
lines changed

13 files changed

+221
-141
lines changed

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ module.exports = {
2525
rules: {
2626
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
2727
'react/react-in-jsx-scope': 'off',
28+
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
2829
},
2930
};

index.html

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,6 @@
2727
<link rel="preconnect" href="https://fonts.googleapis.com">
2828
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
2929
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
30-
<style>
31-
body {
32-
margin: 0;
33-
padding: 0;
34-
display: flex;
35-
background-color: #0A0A0C;
36-
justify-content: center;
37-
}
38-
</style>
3930
</head>
4031

4132
<body>

src/assets/icons/Logo.tsx

Lines changed: 29 additions & 24 deletions
Large diffs are not rendered by default.

src/components/DayCard/DayCard.tsx

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface IProps {
88

99
export const DayCard: React.FC<IProps> = (props: IProps): React.ReactElement | null => {
1010
const [flip, setFlip] = useState(false);
11-
11+
1212
function readableDate(date: string): string {
1313
return DateTime.fromISO(date).toFormat('dd LLLL yyyy');
1414
}
@@ -22,50 +22,54 @@ export const DayCard: React.FC<IProps> = (props: IProps): React.ReactElement | n
2222
}
2323

2424
return (
25-
<div className="flex flex-col mt-[30px] m-8" onClick={toggleFlip}>
26-
{flip ? (
27-
// Back of the card
28-
<div className="p-4 border border-white border-opacity-30 cursor-pointer hover:scale-105 transform-3d perspective-[1000px] rotate-y-12">
29-
<p className="font-ibm text-white opacity-75 text-[10px] text-justify">
30-
{props.dayPicture.explanation}
31-
</p>
32-
</div>
33-
) : (
34-
// Front of the card
35-
props.dayPicture.media_type === 'image' ? (
36-
<div
37-
className="h-90 bg-cover bg-center cursor-pointer"
38-
style={{ backgroundImage: `url(${props.dayPicture.url})` }}
39-
/>
40-
) : (
41-
<a
42-
target="_blank"
43-
rel="noopener noreferrer"
44-
href={props.dayPicture.url}
45-
onClick={(e) => e.stopPropagation()}
46-
className="flex items-center justify-center font-ibm h-90 text-white cursor-pointer p-4 border border-white border-opacity-30"
47-
>
48-
This is a {props.dayPicture.media_type}. Follow link ...
49-
</a>
50-
)
51-
)}
25+
<div className="text-theme">
26+
<div className="flex flex-col mt-8 m-2">
27+
<div className="h-90 w-90 perspective-1000 cursor-pointer" onClick={toggleFlip}>
28+
<div
29+
className={`relative h-90 transition-transform duration-1000 [transform-style:preserve-3d] ${
30+
flip ? '[transform:rotateY(180deg)]' : ''
31+
}`}
32+
>
33+
{/* Front */}
34+
{props.dayPicture.media_type === 'image' ? (
35+
<div className="absolute inset-0 [backface-visibility:hidden]">
36+
<img className="object-cover h-90 w-full" src={props.dayPicture.url} alt="NASA Pic" />
37+
</div>
38+
) : (
39+
<a
40+
target="_blank"
41+
rel="noopener noreferrer"
42+
href={props.dayPicture.url}
43+
onClick={(e) => e.stopPropagation()}
44+
className="block h-90 w-full backface-hidden"
45+
>
46+
<p className="text-theme font-ibm border border-theme p-4 h-full w-full text-center flex items-center justify-center text-base">
47+
This is a {props.dayPicture.media_type}. Follow link...
48+
</p>
49+
</a>
50+
)}
5251

53-
{/* Info Container (always visible) */}
54-
<div className="bg-accent mt-[16px]">
55-
<div className="p-[12px]">
56-
<p className="font-ibm text-dark font-normal text-[16px]">
57-
{readableDate(props.dayPicture.date)}
58-
</p>
59-
<p className="font-ibm text-light font-bold text-[16px] mt-[3px]">
60-
{props.dayPicture.title}
61-
</p>
62-
{!!props.dayPicture.copyright && (
63-
<p className="font-ibm text-white opacity-75 text-[8px] mt-[5px]">
64-
IMAGE CREDIT: {props.dayPicture.copyright}
65-
</p>
66-
)}
52+
{/* Back */}
53+
<div className="absolute inset-0 p-4 border border-theme h-90 w-full [transform:rotateY(180deg)] [backface-visibility:hidden]">
54+
<p className="font-ibm text-theme opacity-75 text-[9px] text-justify font-medium">
55+
{props.dayPicture.explanation}
56+
</p>
57+
</div>
58+
</div>
59+
</div>
60+
61+
{/* Info Container */}
62+
<div className="bg-accent mt-4">
63+
<div className="p-3">
64+
<p className="font-ibm text-text-dark font-normal text-base">{readableDate(props.dayPicture.date)}</p>
65+
<p className="font-ibm text-text-light font-bold text-base mt-1">{props.dayPicture.title}</p>
66+
{!!props.dayPicture.copyright && (
67+
<p className="font-ibm text-white opacity-75 text-xs mt-1">IMAGE CREDIT: {props.dayPicture.copyright}</p>
68+
)}
69+
</div>
70+
</div>
71+
</div>
6772
</div>
68-
</div>
69-
</div>
7073
);
71-
};
74+
};
75+

src/components/Header/Header.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
import React from 'react';
2+
import { useTheme } from '../ThemeContext';
23
import { Logo } from '../../assets/icons/Logo';
34

45
export const Header: React.FC = (): React.ReactElement => {
6+
const { theme, setTheme } = useTheme();
7+
const isActive = (currentTheme: string): boolean => theme === currentTheme;
58

69
return (
7-
<header className="m-8">
8-
<Logo />
10+
<header className="mt-8 mr-2 ml-2 flex flex-row items-center">
11+
<Logo theme={theme} />
12+
13+
<div className="ml-auto text-right text-[12px] space-x-2 ">
14+
<button
15+
onClick={() => setTheme('dark')}
16+
className={` cursor-pointer font-ibm ${isActive('dark') ? 'text-light underline' : 'text-dark'}`}
17+
>
18+
Dark Mode
19+
</button>
20+
21+
<button
22+
onClick={() => setTheme('light')}
23+
className={`cursor-pointer font-ibm ${isActive('light') ? 'text-dark underline' : 'text-light'}`}
24+
>
25+
Light Mode
26+
</button>
27+
</div>
928
</header>
1029
);
1130
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { createContext, useState, useContext, useEffect, ReactNode } from 'react';
2+
3+
type ThemeContextType = {
4+
theme: 'light' | 'dark';
5+
setTheme: React.Dispatch<React.SetStateAction<'light' | 'dark'>>;
6+
};
7+
8+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
9+
10+
type Props = {
11+
children: ReactNode;
12+
};
13+
14+
export const ThemeProvider: React.FC<Props> = ({ children }) => {
15+
const getInitialTheme = (): 'light' | 'dark' => {
16+
if (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
17+
return 'dark';
18+
}
19+
return 'light';
20+
};
21+
22+
const [theme, setTheme] = useState<'light' | 'dark'>(getInitialTheme);
23+
24+
useEffect(() => {
25+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
26+
27+
const handleChange = (e: MediaQueryListEvent) => {
28+
setTheme(e.matches ? 'dark' : 'light');
29+
};
30+
31+
mediaQuery.addEventListener('change', handleChange);
32+
33+
return () => {
34+
mediaQuery.removeEventListener('change', handleChange);
35+
};
36+
}, []);
37+
38+
useEffect(() => {
39+
if (theme === 'dark') {
40+
document.body.classList.add('dark');
41+
document.body.classList.remove('light');
42+
} else {
43+
document.body.classList.add('light');
44+
document.body.classList.remove('dark');
45+
}
46+
}, [theme]);
47+
48+
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
49+
};
50+
51+
export const useTheme = () => {
52+
const context = useContext(ThemeContext);
53+
if (!context) {
54+
throw new Error('useTheme must be used within a ThemeProvider');
55+
}
56+
return context;
57+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ThemeContext';

src/components/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './Header';
2-
export * from './DayCard';
2+
export * from './DayCard';
3+
export { ThemeProvider, useTheme } from './ThemeContext';

src/index.css

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,45 @@
11
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap');
2-
32
@import "tailwindcss";
3+
@custom-variant dark (&:where(.dark, .dark *));
44

55
@theme {
6-
--font-ibm: "IBM Plex Mono";
7-
--color-accent: #ff521f;
8-
--color-dark: #010102;
9-
--color-light: #eae0D4;
10-
--color-white: #ffffff;
11-
--my-rotation: 180deg;
12-
}
13-
.perspective-1000 {
14-
perspective: 1000px;
15-
}
16-
.transform-style-preserve-3d {
17-
transform-style: preserve-3d;
18-
}
19-
.backface-hidden {
20-
backface-visibility: hidden;
21-
}
22-
.rotate-y-180 {
23-
transform: rotateY(180deg);
24-
}
6+
--font-ibm: "IBM Plex Mono";
7+
--color-accent: #FF3B01;
8+
--color-text-light: #FFF9F1;
9+
--color-text-dark: #0A0A0C;
10+
}
11+
12+
/* Dark Mode */
13+
.dark {
14+
--color-background: #0A0A0C;
15+
--color-text: #FFF9F1;
16+
}
17+
18+
/* Light Mode */
19+
.light {
20+
--color-background: #FFF9F1;
21+
--color-text: #0A0A0C;
22+
}
23+
24+
/* Global styles */
25+
.bg-theme {
26+
background-color: var(--color-background);
27+
}
28+
29+
.text-theme {
30+
color: var(--color-text);
31+
}
32+
33+
.text-accent {
34+
color: var(--color-accent);
35+
}
36+
37+
.bg-accent {
38+
background-color: var(--color-accent);
39+
}
40+
41+
body {
42+
@apply m-0 p-0 flex justify-center font-ibm;
43+
background-color: var(--color-background);
44+
color: var(--color-text);
45+
}

src/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import ReactDOM from 'react-dom/client';
22
import { MainPage } from './pages';
3+
import { ThemeProvider } from './components'; // make sure the path is correct
34

4-
// Create a root element
55
const root = ReactDOM.createRoot(document.getElementById('root')!);
66

7-
// Use the render method on the root element
8-
root.render(<MainPage />);
7+
root.render(
8+
<ThemeProvider>
9+
<MainPage />
10+
</ThemeProvider>
11+
);

0 commit comments

Comments
 (0)