WIP on theme selector of about-me, docs: updated README.mds
parent
70f7b14a67
commit
064a9355e6
11
README.md
11
README.md
|
@ -5,7 +5,6 @@ Scaffolded by `pnpm create turbo@latest`
|
||||||
This project manages my homelab infrastructure under a monorepo to keep things
|
This project manages my homelab infrastructure under a monorepo to keep things
|
||||||
centralized and easily sharable across different modules
|
centralized and easily sharable across different modules
|
||||||
|
|
||||||
|
|
||||||
# Turbo-repo details
|
# Turbo-repo details
|
||||||
|
|
||||||
## What's inside?
|
## What's inside?
|
||||||
|
@ -14,11 +13,11 @@ This turborepo uses [pnpm](https://pnpm.io) as a package manager. It includes th
|
||||||
|
|
||||||
### Apps and Packages
|
### Apps and Packages
|
||||||
|
|
||||||
- `docs`: a [Next.js](https://nextjs.org/) app
|
- `apps/docs`: a [Next.js](https://nextjs.org/) app
|
||||||
- `web`: another [Next.js](https://nextjs.org/) app
|
- `apps/web`: another [Next.js](https://nextjs.org/) app
|
||||||
- `ui`: a stub React component library shared by both `web` and `docs` applications
|
- `packages/ui`: a stub React component library shared by both `web` and `docs` applications
|
||||||
- `eslint-config-custom`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
|
- `packages/eslint-config-custom`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
|
||||||
- `tsconfig`: `tsconfig.json`s used throughout the monorepo
|
- `packages/tsconfig`: `tsconfig.json`s used throughout the monorepo
|
||||||
|
|
||||||
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
|
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,41 @@ import SantaMonicaHigh from "@public/assets/santa-monica-high.jpg";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { Section, SectionHeader, UL } from "@components/index";
|
import { Section, SectionHeader, UL } from "@components/index";
|
||||||
|
|
||||||
|
type Theme = "dark" | "light";
|
||||||
|
|
||||||
|
const ThemeContext = React.createContext<{
|
||||||
|
theme: Theme,
|
||||||
|
systemTheme: Theme,
|
||||||
|
setThemePreference(theme: Theme): void,
|
||||||
|
} | undefined>(undefined);
|
||||||
|
const ThemeProvider: React.FC<{ children: React.ReactNode, prefer?: "light" | "dark" }> = ({ children, prefer }) => {
|
||||||
|
const pref = prefer ?? "light";
|
||||||
|
const [systemTheme, setSystemTheme] = useState<Theme>(pref);
|
||||||
|
const [themePref, setThemePref] = useState<Theme | undefined>(undefined);
|
||||||
|
// add a listener to system preference on which theme
|
||||||
|
// TODO: is there another way that remove event listener faster?
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("useEffect called");
|
||||||
|
type EventType = { matches: boolean };
|
||||||
|
const onThemeChange = (event: EventType) => {
|
||||||
|
const newColorScheme = event.matches ? "dark" : "light";
|
||||||
|
setSystemTheme(newColorScheme);
|
||||||
|
};
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', onThemeChange);
|
||||||
|
return () => {
|
||||||
|
console.log("useEffect unmount");
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener("change", onThemeChange)
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
return <ThemeContext.Provider value={{
|
||||||
|
theme: themePref ?? systemTheme,
|
||||||
|
systemTheme: systemTheme,
|
||||||
|
setThemePreference: setThemePref,
|
||||||
|
}}>{children}</ThemeContext.Provider>
|
||||||
|
}
|
||||||
|
const useTheme = () => useContext(ThemeContext);
|
||||||
|
|
||||||
|
|
||||||
const circle = cva("absolute rounded-full border -z-10", {
|
const circle = cva("absolute rounded-full border -z-10", {
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
|
@ -237,7 +272,9 @@ const ImageDisplay: React.FC<{ imageDisplayHook: ImageDisplayHook }> = ({ imageD
|
||||||
// setImageRepo,
|
// setImageRepo,
|
||||||
imgIndex,
|
imgIndex,
|
||||||
image
|
image
|
||||||
} }) => <div className="relative bg-gray-100/30 rounded-md overflow-hidden">
|
} }) => {
|
||||||
|
const {theme} = useTheme()!;
|
||||||
|
return <div className="relative h-fit w-fit bg-gray-100/30 rounded-md">
|
||||||
<button className="group left-0 absolute w-5/12 h-full flex flex-row justify-start items-center" onClick={prev}>
|
<button className="group left-0 absolute w-5/12 h-full flex flex-row justify-start items-center" onClick={prev}>
|
||||||
<span className={imageButton()}>{"<"}</span>
|
<span className={imageButton()}>{"<"}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -248,13 +285,15 @@ const ImageDisplay: React.FC<{ imageDisplayHook: ImageDisplayHook }> = ({ imageD
|
||||||
<div className="flex flex-row justify-between items-center gap-2">
|
<div className="flex flex-row justify-between items-center gap-2">
|
||||||
{Array.from(Array(imageRepo.length).keys()).map(i => <div className={imageIndex({
|
{Array.from(Array(imageRepo.length).keys()).map(i => <div className={imageIndex({
|
||||||
active: (i == imgIndex ? "isActive" : "isInactive"),
|
active: (i == imgIndex ? "isActive" : "isInactive"),
|
||||||
theme: "light"
|
theme: theme
|
||||||
})}
|
})}
|
||||||
key={`img-index-${i}`} />)}
|
key={`img-index-${i}`} />)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{image && <Image placeholder="blur" src={image.src} alt={image.alt} className="w-96 h-96 object-contain rounded-md overflow-hidden transition-all" />}
|
{image && <Image placeholder="blur" src={image.src} alt={image.alt}
|
||||||
|
className="w-96 h-96 object-contain rounded-md" />}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
const MyLink: React.FC<{ href: string, children?: ReactNode }> = ({ href, children }) => <Link
|
const MyLink: React.FC<{ href: string, children?: ReactNode }> = ({ href, children }) => <Link
|
||||||
className="text-amber-400/70 visited:text-purple-400/70 hover:text-amber-400 visited:hover:text-purple-400" href={href}>
|
className="text-amber-400/70 visited:text-purple-400/70 hover:text-amber-400 visited:hover:text-purple-400" href={href}>
|
||||||
|
@ -266,7 +305,7 @@ const About = () => {
|
||||||
|
|
||||||
return <Article>
|
return <Article>
|
||||||
<SectionHeader>About</SectionHeader>
|
<SectionHeader>About</SectionHeader>
|
||||||
<div className="flex flex-row gap-4">
|
<div className="flex flex-row w-11/12 justify-evenly max-w-screen-lg gap-8">
|
||||||
<ImageDisplay imageDisplayHook={imgDisplay} />
|
<ImageDisplay imageDisplayHook={imgDisplay} />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<Section>
|
<Section>
|
||||||
|
@ -328,7 +367,7 @@ const NavbarItem: React.FC<{
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
|
||||||
const iconCva = cva("transition-colors", {
|
const iconCva = cva("transition-colors group-hover:animate-bounce", {
|
||||||
variants: {
|
variants: {
|
||||||
theme: {
|
theme: {
|
||||||
light: "group-hover:fill-gray-700 fill-gray-400"
|
light: "group-hover:fill-gray-700 fill-gray-400"
|
||||||
|
@ -339,51 +378,22 @@ const iconCva = cva("transition-colors", {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Navbar = () => <div className="flex justify-center items-center w-full z-50 sticky top-0 left-0 backdrop-blur-md h-8">
|
const Navbar = () => {
|
||||||
|
const _theme = useTheme();
|
||||||
|
if (!_theme) { throw new Error("no theme provider"); }
|
||||||
|
const { theme, setThemePreference } = _theme;
|
||||||
|
return <div className="flex justify-center items-center w-full z-50 sticky top-0 left-0 backdrop-blur-md h-8">
|
||||||
<div className="w-11/12 max-w-screen-lg flex items-center gap-4">
|
<div className="w-11/12 max-w-screen-lg flex items-center gap-4">
|
||||||
<div className="flex flex-row justify-center items-center gap-1">
|
<div className="flex flex-row justify-center items-center gap-1">
|
||||||
<NavbarItem text="GitHub" link="https://github.com/pegasust"><FaGithub className={iconCva()} /></NavbarItem>
|
<NavbarItem text="GitHub" link="https://github.com/pegasust"><FaGithub className={iconCva()} /></NavbarItem>
|
||||||
<NavbarItem text="LinkedIn" link="https://linkedin.com/in/hung-tran-1963bb205/"><FaLinkedin className={iconCva()} /></NavbarItem>
|
<NavbarItem text="LinkedIn" link="https://linkedin.com/in/hung-tran-1963bb205/"><FaLinkedin className={iconCva()} /></NavbarItem>
|
||||||
</div>
|
</div>
|
||||||
<Link href="#brief">
|
<Link href="#brief">
|
||||||
<span className="transition-colors hover:text-amber-400 text-amber-400/50 animate-pulse">Hung Tran</span>
|
<span className="transition-colors hover:text-amber-400 text-amber-400/50 hover:animate-pulse">Hung Tran</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
type Theme = "dark" | "light";
|
|
||||||
|
|
||||||
const ThemeContext = React.createContext<{
|
|
||||||
theme: Theme,
|
|
||||||
systemTheme: Theme,
|
|
||||||
setThemePreference(theme: Theme): void,
|
|
||||||
} | undefined>(undefined);
|
|
||||||
const ThemeProvider: React.FC<{ children: React.ReactNode, prefer?: "light" | "dark" }> = ({ children, prefer }) => {
|
|
||||||
const pref = prefer ?? "light";
|
|
||||||
const [systemTheme, setSystemTheme] = useState<Theme>(pref);
|
|
||||||
const [themePref, setThemePref] = useState<Theme | undefined>(undefined);
|
|
||||||
// add a listener to system preference on which theme
|
|
||||||
// TODO: is there another way that remove event listener faster?
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("useEffect called");
|
|
||||||
type EventType = { matches: boolean };
|
|
||||||
const onThemeChange = (event: EventType) => {
|
|
||||||
const newColorScheme = event.matches ? "dark" : "light";
|
|
||||||
setSystemTheme(newColorScheme);
|
|
||||||
};
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', onThemeChange);
|
|
||||||
return () => {
|
|
||||||
console.log("useEffect unmount");
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener("change", onThemeChange)
|
|
||||||
}
|
}
|
||||||
}, []);
|
|
||||||
return <ThemeContext.Provider value={{
|
|
||||||
theme: themePref ?? systemTheme,
|
|
||||||
systemTheme: systemTheme,
|
|
||||||
setThemePreference: setThemePref,
|
|
||||||
}}>{children}</ThemeContext.Provider>
|
|
||||||
}
|
|
||||||
const useTheme = () => useContext(ThemeContext);
|
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
return (
|
return (
|
||||||
|
|
Loading…
Reference in New Issue