feat: Add theme switch
This commit is contained in:
38 files changed
+1724
-218
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { VStack, HStack, Header, Paragraph, Link } from '@ermnvldmr/ui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { IndexLayout } from '../components/layouts/IndexLayout/IndexLayout';
|
||||
import { createPage } from '../lib/core/createPage';
|
||||
import {
|
||||
AUTHOR_EMAIL,
|
||||
@@ -37,34 +38,36 @@ createPage(
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="h-screen w-screen">
|
||||
<VStack align="center" className="w-full h-full" justify="center">
|
||||
<VStack align="start" gap={8}>
|
||||
<Header level={1}>
|
||||
Exceptional Situation
|
||||
{status !== null && (
|
||||
<>
|
||||
{' '}
|
||||
<Link href={getHttpStatusUrl(status)} size="l" type="display">
|
||||
#{status}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</Header>{' '}
|
||||
<Paragraph>
|
||||
This location is unavailable at the moment — static content does not dwell here
|
||||
</Paragraph>
|
||||
<HStack className="w-full" justify="between">
|
||||
<span>
|
||||
<Link href={`mailto:${AUTHOR_EMAIL}`}>{AUTHOR_NAME}</Link>, {getCurrentYear()}
|
||||
</span>
|
||||
<span>
|
||||
<Link href={SITE_URL}>ermnvldmr.com</Link>
|
||||
</span>
|
||||
</HStack>
|
||||
<IndexLayout>
|
||||
<main className="h-screen w-screen">
|
||||
<VStack align="center" className="w-full h-full" justify="center">
|
||||
<VStack align="start" gap={8}>
|
||||
<Header level={1}>
|
||||
Exceptional Situation
|
||||
{status !== null && (
|
||||
<>
|
||||
{' '}
|
||||
<Link href={getHttpStatusUrl(status)} size="l" type="display">
|
||||
#{status}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</Header>{' '}
|
||||
<Paragraph>
|
||||
This location is unavailable at the moment — static content does not dwell here
|
||||
</Paragraph>
|
||||
<HStack className="w-full" justify="between">
|
||||
<span>
|
||||
<Link href={`mailto:${AUTHOR_EMAIL}`}>{AUTHOR_NAME}</Link>, {getCurrentYear()}
|
||||
</span>
|
||||
<span>
|
||||
<Link href={SITE_URL}>ermnvldmr.com</Link>
|
||||
</span>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</main>
|
||||
</main>
|
||||
</IndexLayout>
|
||||
);
|
||||
},
|
||||
{ title: ERROR_TITLE, description: ERROR_DESCRIPTION }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { VStack, HStack, Header, Paragraph, Link } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { IndexLayout } from '../components/layouts/IndexLayout/IndexLayout';
|
||||
import { createPage } from '../lib/core/createPage';
|
||||
import {
|
||||
AUTHOR_EMAIL,
|
||||
@@ -14,24 +15,26 @@ import { getCurrentYear } from '../lib/shared/utils';
|
||||
createPage(
|
||||
function Index() {
|
||||
return (
|
||||
<main className="h-screen w-screen">
|
||||
<VStack align="center" className="w-full h-full" justify="center">
|
||||
<VStack align="start" gap={8}>
|
||||
<Header level={1}>Static Content Dwells Here</Header>
|
||||
<Paragraph>
|
||||
Other pages direct you down specific paths to the content that resides here
|
||||
</Paragraph>
|
||||
<HStack className="w-full" justify="between">
|
||||
<span>
|
||||
<Link href={`mailto:${AUTHOR_EMAIL}`}>{AUTHOR_NAME}</Link>, {getCurrentYear()}
|
||||
</span>
|
||||
<span>
|
||||
<Link href={SITE_URL}>ermnvldmr.com</Link>
|
||||
</span>
|
||||
</HStack>
|
||||
<IndexLayout>
|
||||
<main className="h-screen w-screen">
|
||||
<VStack align="center" className="w-full h-full" justify="center">
|
||||
<VStack align="start" gap={8}>
|
||||
<Header level={1}>Static Content Dwells Here</Header>
|
||||
<Paragraph>
|
||||
Other pages direct you down specific paths to the content that resides here
|
||||
</Paragraph>
|
||||
<HStack className="w-full" justify="between">
|
||||
<span>
|
||||
<Link href={`mailto:${AUTHOR_EMAIL}`}>{AUTHOR_NAME}</Link>, {getCurrentYear()}
|
||||
</span>
|
||||
<span>
|
||||
<Link href={SITE_URL}>ermnvldmr.com</Link>
|
||||
</span>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</main>
|
||||
</main>
|
||||
</IndexLayout>
|
||||
);
|
||||
},
|
||||
{ title: SITE_TITLE, description: SITE_DESCRIPTION }
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Container, HStack, Link, ThemeSwitch, useTheme, VStack, Text, Separator } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Props for the IndexLayout component.
|
||||
*/
|
||||
export interface IndexLayoutProps {
|
||||
/** Main content slot */
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The root layout for the static service.
|
||||
*
|
||||
* @param props - The component props.
|
||||
* @param props.children - Main content slot.
|
||||
* @returns A React element representing the index layout.
|
||||
* @example
|
||||
* ```tsx
|
||||
* <IndexLayout>Content</IndexLayout>
|
||||
* ```
|
||||
*/
|
||||
export function IndexLayout({ children }: IndexLayoutProps): React.JSX.Element {
|
||||
// Initialize theme side-effects
|
||||
useTheme();
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<VStack className="min-h-screen bg-[var(--rb-background)] transition-colors duration-200" justify="between">
|
||||
<VStack as="main" className="min-h-[105vh] w-full flex-1">
|
||||
{children}
|
||||
</VStack>
|
||||
|
||||
<VStack as="footer" className="w-full" gap={0}>
|
||||
<Separator thinned={false} />
|
||||
<Container className="px-6 py-12 md:px-12">
|
||||
<HStack align="center" className="w-full" justify="between">
|
||||
<VStack align="start" gap={1}>
|
||||
<Text color="muted" size="s">
|
||||
© {currentYear} Vladimir Eremin
|
||||
</Text>
|
||||
<Link href="https://creativecommons.org/licenses/by/4.0/" size="s">
|
||||
CC-BY 4.0
|
||||
</Link>
|
||||
</VStack>
|
||||
|
||||
<ThemeSwitch />
|
||||
</HStack>
|
||||
</Container>
|
||||
</VStack>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +1,7 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script>
|
||||
const getThemePreference = () => {
|
||||
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
|
||||
return localStorage.getItem('theme');
|
||||
}
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
};
|
||||
const isDark = getThemePreference() === 'dark';
|
||||
document.documentElement.classList[isDark ? 'add' : 'remove']('dark');
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const observer = new MutationObserver(() => {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
});
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>(function(){try{var k='ermnvldmr/ui/lib/theme';var p=JSON.parse(localStorage.getItem(k)||'"system"');var d=p==='dark'||(p==='system'&&window.matchMedia('(prefers-color-scheme:dark)').matches);document.documentElement.classList.toggle('dark',d)}catch(e){}})();</script>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vladimir Eremin</title>
|
||||
|
||||
Reference in new issue
Block a user