Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
.react-router
*.local

# Editor directories and files
Expand Down
322 changes: 298 additions & 24 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import tseslint from "typescript-eslint";
import importPlugin from "eslint-plugin-import";

export default tseslint.config(
{ ignores: ["dist", "styled-system", "coverage"] },
{ ignores: ["dist", "styled-system", "coverage", ".react-router"] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
Expand Down
13 changes: 0 additions & 13 deletions index.html

This file was deleted.

14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,37 @@
"license": "MIT",
"homepage": "https://www.daleui.com",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"dev": "react-router dev",
"build": "tsc -b && react-router build",
"format": "prettier --check .",
"lint": "eslint .",
"test": "vitest",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"test": "vitest",
"test": "vitest",
"prepare": "react-router typegen",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의견 감사합니다. typegen은 prepare로 분리하였습니다.

"coverage": "vitest run --coverage",
"preview": "vite preview",
"deploy": "bun run build && wrangler deploy",
"prepare": "panda codegen"
"prepare": "panda codegen && react-router typegen"
},
"dependencies": {
"@fontsource-variable/jetbrains-mono": "^5.2.8",
"@mdx-js/react": "^3.1.1",
"@react-router/node": "^7.15.1",
"browserslist": "^4.28.2",
"daleui": "^1.0.0",
"isbot": "^5",
Copy link
Copy Markdown
Collaborator Author

@hyoseong1994 hyoseong1994 May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isbot은 react-router v7 빌드시 자동추가 (prerendering 하는 daleui.com에서는 필요없지만 의존성으로인하여 설치)

"mermaid": "^11.15.0",
"pretendard": "^1.3.9",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-router": "^7.15.1",
"zod": "^4.4.3"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.36.4",
"@cloudflare/vite-plugin": "^1.37.1",
"@codecov/vite-plugin": "^1.9.1",
"@eslint/js": "^10.0.1",
"@pandacss/dev": "^1.11.1",
"@mdx-js/rollup": "^3.1.1",
"@pandacss/dev": "^1.11.1",
"@react-router/dev": "^7.15.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
Expand Down
17 changes: 17 additions & 0 deletions react-router.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { readdirSync } from "node:fs";
import { join } from "node:path";
import type { Config } from "@react-router/dev/config";

const blogDir = join(import.meta.dirname, "src/content/blog");
const blogSlugs = readdirSync(blogDir)
.filter((name) => name.endsWith(".mdx"))
.map((name) => name.replace(/\.mdx$/, ""));

export default {
appDirectory: "src",
buildDirectory: "dist",
ssr: false,
async prerender({ getStaticPaths }) {
return [...getStaticPaths(), ...blogSlugs.map((slug) => `/blog/${slug}`)];
},
} satisfies Config;
27 changes: 0 additions & 27 deletions src/Router.tsx

This file was deleted.

60 changes: 60 additions & 0 deletions src/hooks/useTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useCallback, useSyncExternalStore } from "react";

export type Theme = "light" | "dark";

const STORAGE_KEY = "theme";
const DARK_CLASS = "dark";

function getResolvedTheme(): Theme {
if (typeof document === "undefined") return "light";
return document.documentElement.classList.contains(DARK_CLASS)
? "dark"
: "light";
}

function getServerTheme(): Theme {
return "light";
}

function applyTheme(theme: Theme) {
document.documentElement.classList.toggle(DARK_CLASS, theme === "dark");
}

function subscribe(notify: () => void): () => void {
const root = document.documentElement;
const classObserver = new MutationObserver(notify);
classObserver.observe(root, { attributes: true, attributeFilter: ["class"] });

const onStorage = (event: StorageEvent) => {
if (event.key !== STORAGE_KEY) return;
if (event.newValue === "dark" || event.newValue === "light") {
applyTheme(event.newValue);
}
};
window.addEventListener("storage", onStorage);

return () => {
classObserver.disconnect();
window.removeEventListener("storage", onStorage);
};
}

export function useTheme() {
const theme = useSyncExternalStore(
subscribe,
getResolvedTheme,
getServerTheme,
);

const toggleTheme = useCallback(() => {
const current = getResolvedTheme();
const next = current === "dark" ? "light" : "dark";
localStorage.setItem(STORAGE_KEY, next);
applyTheme(next);
}, []);

return {
isDark: theme === "dark",
toggleTheme,
};
}
31 changes: 4 additions & 27 deletions src/layouts/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Button, Icon, Link } from "daleui";
import { useEffect, useState } from "react";
import { useState } from "react";
import { css } from "../../styled-system/css";
import { stack } from "../../styled-system/patterns";
import { useTheme } from "../hooks/useTheme";

const LINK_ITEMS = [
{ id: 1, label: "깃허브", href: "https://github.com/DaleStudy/daleui" },
Expand All @@ -19,31 +20,7 @@ const LINK_ITEMS = [

export function Navigation() {
const [isOpenMenu, setIsOpenMenu] = useState(false);
const [isDark, setIsDark] = useState(() => {
const el = document.documentElement;
return el.classList.contains("dark");
});

useEffect(() => {
const el = document.documentElement;

const updateThemeState = () => {
const hasDarkClass = el.classList.contains("dark");
setIsDark(hasDarkClass);
};

const observer = new MutationObserver(updateThemeState);
observer.observe(el, {
attributes: true,
});

return () => observer.disconnect();
}, []);

const handleToggleTheme = () => {
const el = document.documentElement;
el.classList.toggle("dark");
};
const { isDark, toggleTheme } = useTheme();

const handleToggleMenu = () => {
setIsOpenMenu((v) => !v);
Expand Down Expand Up @@ -228,7 +205,7 @@ export function Navigation() {
name={isDark ? "moon" : "sun"}
size="lg"
tone="brand"
onClick={handleToggleTheme}
onClick={toggleTheme}
/>
</li>
<li
Expand Down
17 changes: 0 additions & 17 deletions src/layouts/SiteLayout.tsx

This file was deleted.

11 changes: 0 additions & 11 deletions src/main.tsx

This file was deleted.

44 changes: 44 additions & 0 deletions src/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
import "daleui/styles.css";
import "./index.css";
import { Navigation } from "./layouts/Navigation";
import { Footer } from "./layouts/Footer";

export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
<head>
<meta charSet="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{/* FOUC를 방지 */}
<script>
{`
(function () {
try {
var stored = localStorage.getItem('theme');
var prefersDark = !stored && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (stored === 'dark' || prefersDark) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
})();
`}
</script>
<Meta />
<Links />
</head>
<body>
<Navigation />
{children}
<Footer />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

export default function App() {
return <Outlet />;
}
6 changes: 6 additions & 0 deletions src/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
index("routes/home.tsx"),
route("blog/:slug", "routes/blog.$slug.tsx"),
] satisfies RouteConfig;
18 changes: 12 additions & 6 deletions src/pages/BlogPage.tsx → src/routes/blog.$slug.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Box, Flex, Heading, Text, VStack } from "daleui";
import { useEffect, useRef } from "react";
import { css } from "../../styled-system/css";
import type { BlogFrontmatter } from "../content/blog/schema";
import type { MdxDoc } from "../mdx/types";
import { findBlog } from "../content/blog/loader";

type BlogPageProps = {
post: MdxDoc<BlogFrontmatter>;
};
// eslint-disable-next-line react-refresh/only-export-components
export function meta({ params }: { params: { slug: string } }) {
const post = findBlog(params.slug);
return [{ title: post ? `${post.frontmatter.title} | Dale UI` : "Dale UI" }];
}

export default function BlogSlug({ params }: { params: { slug: string } }) {
const post = findBlog(params.slug);
if (!post) {
throw new Response("Not Found", { status: 404 });
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loader부분에서 MDX 파일 + frontmatter를 가져와야 제대로 Static Rendering이 됩니다.

Copy link
Copy Markdown
Collaborator Author

@hyoseong1994 hyoseong1994 May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분이 이해가 잘 안되는데 자세히 설명가능할까요?


export function BlogPage({ post }: BlogPageProps) {
const { default: Component, frontmatter } = post;
const articleRef = useRef<HTMLElement>(null);

Expand Down
7 changes: 6 additions & 1 deletion src/pages/MarketingPage.tsx → src/routes/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { How } from "../sections/marketing/How";
import { Mission } from "../sections/marketing/Mission";
import { Team } from "../sections/marketing/Team";

export function MarketingPage() {
// eslint-disable-next-line react-refresh/only-export-components
export function meta() {
return [{ title: "Dale UI" }];
}

export default function Home() {
const scrollToSection = useCallback((sectionId: string) => {
const element = document.getElementById(sectionId);
if (!element) {
Expand Down
23 changes: 2 additions & 21 deletions src/sections/marketing/Team.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
Text,
VStack,
} from "daleui";
import { useEffect, useState } from "react";
import { css } from "../../../styled-system/css";
import { useTheme } from "../../hooks/useTheme";

export interface TeamMember {
/** 팀원 이름 */
Expand Down Expand Up @@ -258,26 +258,7 @@ const Members: TeamMember[] = [
];

export function Team({ members = Members }: TeamProps) {
const [isDark, setIsDark] = useState(() => {
const el = document.documentElement;
return el.classList.contains("dark");
});

useEffect(() => {
const el = document.documentElement;

const updateThemeState = () => {
const hasDarkClass = el.classList.contains("dark");
setIsDark(hasDarkClass);
};

const observer = new MutationObserver(updateThemeState);
observer.observe(el, {
attributes: true,
});

return () => observer.disconnect();
}, []);
const { isDark } = useTheme();

return (
<section
Expand Down
Loading