Issue 97 dark mode (#105)

This commit is contained in:
angbur 2022-12-06 15:29:48 +01:00 committed by GitHub
parent 9e1e153e74
commit 7e15cd3412
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 88 additions and 25 deletions

View file

@ -1 +1 @@
<div class="w-full h-px bg-gray-200"></div> <div class="w-full h-px bg-gray-200 dark:bg-gray-600"></div>

View file

@ -35,6 +35,8 @@ const { icon, href, target, size, ...rest } = Astro.props;
'focus:ring-2', 'focus:ring-2',
'focus:ring-offset-2', 'focus:ring-offset-2',
'focus:ring-primary-500', 'focus:ring-primary-500',
'dark:bg-gray-600',
'dark:text-gray-200',
sizeMap[size], sizeMap[size],
]} ]}
{...rest} {...rest}

View file

@ -8,6 +8,6 @@ const { label, value } = Astro.props;
--- ---
<div class="text-base"> <div class="text-base">
<span class="font-medium text-gray-700">{label}:</span> <span class="font-medium text-gray-700 dark:text-gray-300">{label}:</span>
<span class="font-normal text-gray-500">{value}</span> <span class="font-normal text-gray-500 dark:text-gray-400">{value}</span>
</div> </div>

View file

@ -8,4 +8,4 @@ export interface Props {
const { section } = Astro.props; const { section } = Astro.props;
--- ---
<div id={section} class="p-8 bg-white rounded-2xl shadow-md flex flex-col gap-6"><slot /></div> <div id={section} class="p-8 bg-white dark:bg-gray-800 rounded-2xl shadow-md flex flex-col gap-6"><slot /></div>

View file

@ -24,7 +24,11 @@ const SidebarItem = ({ section, icon, title = '' }: SidebarItemProps) => {
<a <a
href={href} href={href}
className={`inline-flex justify-center items-center h-10 w-10 rounded-lg transition className={`inline-flex justify-center items-center h-10 w-10 rounded-lg transition
${active ? 'bg-primary-600 text-white' : 'bg-white text-gray-400 hover:bg-primary-600 hover:text-white'} ${
active
? 'bg-primary-600 text-white'
: 'bg-white text-gray-400 dark:bg-gray-800 dark:text-gray-200 hover:bg-primary-600 hover:text-white'
}
`} `}
aria-current={active ? 'page' : undefined} aria-current={active ? 'page' : undefined}
aria-label={`${section} section`} aria-label={`${section} section`}

View file

@ -17,7 +17,7 @@ const isFilled = skillLevel >= tileLevel;
'last:rounded-r-sm', 'last:rounded-r-sm',
'first:rounded-l-sm', 'first:rounded-l-sm',
'first:rounded-r-none', 'first:rounded-r-none',
{ 'bg-gray-500': isFilled, 'bg-gray-300': !isFilled }, { 'dark:bg-gray-300': isFilled, 'dark:bg-gray-500': !isFilled, 'bg-gray-200': !isFilled, 'bg-gray-500': isFilled },
]} ]}
> >
</div> </div>

View file

@ -10,7 +10,9 @@ export interface Props {
const { name, color } = Astro.props; const { name, color } = Astro.props;
--- ---
<div class="flex items-center w-fit h-6 px-2.5 rounded text-sm font-medium text-gray-700 bg-gray-100 gap-x-1.5"> <div
class="flex items-center w-fit h-6 px-2.5 rounded text-sm font-medium tracking-wide text-gray-700 dark:text-gray-100 bg-gray-100 dark:bg-gray-700 gap-x-1.5"
>
<Icon client:load name={name} color={color} size={16} /> <Icon client:load name={name} color={color} size={16} />
<slot /> <slot />
</div> </div>

View file

@ -0,0 +1,49 @@
import { useEffect, useState } from 'react';
import Icon from './icon';
const STORAGE_THEME_KEY = 'theme';
const DARK_THEME_KEY = 'dark';
const LIGHT_THEME_KEY = 'light';
type ThemeVariant = typeof DARK_THEME_KEY | typeof LIGHT_THEME_KEY;
const getInitialTheme = (): ThemeVariant => {
if (typeof localStorage !== 'undefined' && localStorage.getItem(STORAGE_THEME_KEY)) {
return localStorage.getItem(STORAGE_THEME_KEY) === LIGHT_THEME_KEY ? LIGHT_THEME_KEY : DARK_THEME_KEY;
}
if (window.matchMedia(`(prefers-color-scheme: ${DARK_THEME_KEY})`).matches) {
return DARK_THEME_KEY;
}
return LIGHT_THEME_KEY;
};
const ThemeToggle = () => {
const [theme, setTheme] = useState<ThemeVariant>(() => getInitialTheme());
const handleClick = () => {
setTheme((prev) => (prev === LIGHT_THEME_KEY ? DARK_THEME_KEY : LIGHT_THEME_KEY));
};
useEffect(() => {
if (theme === DARK_THEME_KEY) {
document.documentElement.classList.add(DARK_THEME_KEY);
}
if (theme === LIGHT_THEME_KEY) {
document.documentElement.classList.remove(DARK_THEME_KEY);
}
localStorage.setItem(STORAGE_THEME_KEY, theme);
}, [theme]);
return (
<button
onClick={handleClick}
type="button"
className="text-gray-400 bg-gray-100 focus:ring-primary-500 dark:bg-gray-600 dark:text-gray-200 inline-flex justify-center items-center h-10 w-10 transition fixed bottom-3 left-3 shadow-xl rounded-lg z-10"
>
<Icon name={theme === DARK_THEME_KEY ? 'ri:moon-fill' : 'ri:sun-line'} size={20} />
</button>
);
};
export default ThemeToggle;

View file

@ -11,7 +11,10 @@ const Tooltip = ({ children, content, placement = 'top' }: TooltipProps) => {
return ( return (
<Tippy <Tippy
render={(attrs) => ( render={(attrs) => (
<div {...attrs} className="bg-gray-700 rounded-lg px-2 py-1.5 text-white max-w-[95%] sm:max-w-xs"> <div
{...attrs}
className="bg-gray-700 dark:bg-gray-100 dark:text-gray-800 rounded-lg px-2 py-1.5 text-white max-w-[95%] sm:max-w-xs"
>
{content} {content}
</div> </div>
)} )}

View file

@ -25,16 +25,16 @@ const variantToElement = {
} as const; } as const;
const variantToClassName = { const variantToClassName = {
'main-title': 'text-3xl sm:text-4xl font-extrabold text-gray-900', 'main-title': 'text-3xl sm:text-4xl font-extrabold text-gray-900 dark:text-gray-100',
'main-subtitle': 'text-md sm:text-lg font-medium text-gray-700', 'main-subtitle': 'text-md sm:text-lg font-medium text-gray-700 dark:text-gray-100',
'section-title': 'text-3xl font-extrabold text-gray-900', 'section-title': 'text-3xl font-extrabold text-gray-900 dark:text-gray-100',
'section-subtitle': 'text-lg font-extrabold text-gray-900', 'section-subtitle': 'text-lg font-extrabold text-gray-900 dark:text-gray-100',
'item-title': 'text-xl font-extrabold text-gray-900', 'item-title': 'text-xl font-extrabold text-gray-900 dark:text-gray-100',
'item-title-suffix': 'text-xl font-medium text-gray-700', 'item-title-suffix': 'text-xl font-medium text-gray-700 dark:text-gray-100',
'item-subtitle': 'text-md font-medium text-gray-700', 'item-subtitle': 'text-md font-medium text-gray-700 dark:text-gray-100',
'tile-title': 'text-sm font-medium text-gray-700', 'tile-title': 'text-sm font-medium text-gray-700 dark:text-gray-200',
'tile-subtitle': 'text-sm font-normal text-gray-500', 'tile-subtitle': 'text-sm font-normal text-gray-500 dark:text-gray-300',
paragraph: 'text-sm sm:text-base leading-relaxed font-normal text-gray-500', paragraph: 'text-sm sm:text-base leading-relaxed font-normal text-gray-500 dark:text-gray-300',
}; };
export interface Props extends Omit<astroHTML.JSX.HTMLAttributes, 'slot'> { export interface Props extends Omit<astroHTML.JSX.HTMLAttributes, 'slot'> {

View file

@ -5,6 +5,6 @@ export interface Props {
const { className } = Astro.props; const { className } = Astro.props;
--- ---
<nav class:list={['flex flex-col w-max h-fit p-2 rounded-lg gap-2 bg-white shadow-md', className]}> <nav class:list={['flex flex-col w-max h-fit p-2 rounded-lg gap-2 bg-white dark:bg-gray-800 shadow-md', className]}>
<slot /> <slot />
</nav> </nav>

View file

@ -22,7 +22,7 @@ const IconWrapper = url ? 'a' : 'div';
> >
<Icon client:load name={icon} color={iconColor} size={20} /> <Icon client:load name={icon} color={iconColor} size={20} />
<Typography variant="tile-subtitle"> <Typography variant="tile-subtitle">
<span class="text-gray-700">{name}</span> <span class:list={['text-gray-700', 'dark:text-gray-300']}>{name}</span>
</Typography> </Typography>
</IconWrapper> </IconWrapper>
{ {

View file

@ -59,8 +59,8 @@ const section: Section = 'main';
details.map(({ label: detailLabel, value }) => ( details.map(({ label: detailLabel, value }) => (
<div class="w-fit"> <div class="w-fit">
<Typography variant="paragraph"> <Typography variant="paragraph">
<span class="text-gray-700">{detailLabel}: </span> <span class:list={['text-gray-700', 'dark:text-gray-300']}>{detailLabel}: </span>
<span class="break-all">{value}</span> <span class:list={['break-all', 'dark:text-gray-400']}>{value}</span>
</Typography> </Typography>
</div> </div>
)) ))

View file

@ -1,5 +1,6 @@
--- ---
import SidebarItem from '@/atoms/sidebar-item'; import SidebarItem from '@/atoms/sidebar-item';
import ThemeToggle from '@/atoms/theme-icon';
import Sidebar from '@/organisms/sidebar.astro'; import Sidebar from '@/organisms/sidebar.astro';
import ExperienceSection from '@/sections/experience-section.astro'; import ExperienceSection from '@/sections/experience-section.astro';
import FavoritesSection from '@/sections/favorites-section.astro'; import FavoritesSection from '@/sections/favorites-section.astro';
@ -28,7 +29,8 @@ const seoImage = seo.image ? seo.image : '/favicon.svg';
<meta property="og:description" content={seo.description} /> <meta property="og:description" content={seo.description} />
<meta property="og:image" content={seoImage} /> <meta property="og:image" content={seoImage} />
</head> </head>
<body class="flex justify-center bg-gray-50"> <body class="flex justify-center bg-gray-50 dark:bg-gray-900">
<ThemeToggle client:only="react" />
<div class="flex relative transform-none gap-8 w-full max-w-5xl px-2 py-3 sm:px-8 sm:py-12 lg:py-20"> <div class="flex relative transform-none gap-8 w-full max-w-5xl px-2 py-3 sm:px-8 sm:py-12 lg:py-20">
<div class="absolute z-40 -right-2"> <div class="absolute z-40 -right-2">
<Sidebar className="hidden xl:flex fixed"> <Sidebar className="hidden xl:flex fixed">

View file

@ -1,3 +1,3 @@
import type { CircleFlags, Fa6Brands, Fa6Solid, Logos, SimpleIcons } from 'iconify-icon-names'; import type { CircleFlags, Fa6Brands, Fa6Solid, Ri, SimpleIcons } from 'iconify-icon-names';
export type IconName = Fa6Brands | Fa6Solid | SimpleIcons | CircleFlags | Logos; export type IconName = Fa6Brands | Fa6Solid | SimpleIcons | CircleFlags | Ri;

View file

@ -3,6 +3,7 @@ const colors = require('tailwindcss/colors');
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
darkMode: ['class'],
theme: { theme: {
colors: { colors: {
transparent: 'transparent', transparent: 'transparent',