feat: Add theme switch

This commit is contained in:
deytenit committed 2026-02-15 15:36:31 +03:00
1 parent 891027cab0
commit 2d1bc9d119
38 files changed
+1724 -218

No files matched your search

+30 -27
View File
@@ -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 }
+20 -17
View File
@@ -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 -21
View File
@@ -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>