shindonghyeo-ui 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -2
- package/dist/components/Banner/Banner.d.ts +15 -0
- package/dist/components/Banner/Banner.js +32 -0
- package/dist/components/Banner/Banner.module.js +10 -0
- package/dist/components/Button/Button.d.ts +14 -0
- package/dist/components/Button/Button.js +16 -0
- package/dist/components/Button/Button.module.js +6 -0
- package/dist/components/Chip/Chip.d.ts +6 -0
- package/dist/components/Chip/Chip.js +12 -0
- package/dist/components/Chip/Chip.module.js +7 -0
- package/dist/components/Container/Container.d.ts +6 -0
- package/dist/components/Container/Container.js +11 -0
- package/dist/components/Container/Container.module.js +6 -0
- package/dist/components/Footer/Footer.d.ts +17 -0
- package/dist/components/Footer/Footer.js +41 -0
- package/dist/components/Footer/Footer.module.js +10 -0
- package/dist/components/GradientText/GradientText.d.ts +2 -0
- package/dist/components/GradientText/GradientText.js +11 -0
- package/dist/components/GradientText/GradientText.module.js +3 -0
- package/dist/components/IconButton/IconButton.d.ts +13 -0
- package/dist/components/IconButton/IconButton.js +16 -0
- package/dist/components/IconButton/IconButton.module.js +3 -0
- package/dist/components/Logo/Logo.d.ts +8 -0
- package/dist/components/Logo/Logo.js +13 -0
- package/dist/components/Logo/Logo.module.js +3 -0
- package/dist/components/Navbar/Navbar.d.ts +21 -0
- package/dist/components/Navbar/Navbar.js +36 -0
- package/dist/components/Navbar/Navbar.module.js +12 -0
- package/dist/components/PostList/PostList.d.ts +16 -0
- package/dist/components/PostList/PostList.js +35 -0
- package/dist/components/PostList/PostList.module.js +11 -0
- package/dist/components/Prose/Prose.d.ts +6 -0
- package/dist/components/Prose/Prose.js +11 -0
- package/dist/components/Prose/Prose.module.js +3 -0
- package/dist/components/ReadingProgress/ReadingProgress.d.ts +7 -0
- package/dist/components/ReadingProgress/ReadingProgress.js +28 -0
- package/dist/components/ReadingProgress/ReadingProgress.module.js +7 -0
- package/dist/components/SectionTitle/SectionTitle.d.ts +10 -0
- package/dist/components/SectionTitle/SectionTitle.js +17 -0
- package/dist/components/SectionTitle/SectionTitle.module.js +7 -0
- package/dist/components/ThemeToggle/ThemeToggle.d.ts +9 -0
- package/dist/components/ThemeToggle/ThemeToggle.js +24 -0
- package/dist/components/ThemeToggle/ThemeToggle.module.js +6 -0
- package/dist/components/Toc/Toc.d.ts +16 -0
- package/dist/components/Toc/Toc.js +53 -0
- package/dist/components/Toc/Toc.module.js +11 -0
- package/dist/components/icons/icons.d.ts +10 -0
- package/dist/components/icons/icons.js +74 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +21 -0
- package/dist/styles.css +2 -0
- package/dist/utils/cx.d.ts +1 -0
- package/dist/utils/cx.js +5 -0
- package/dist/utils/theme.d.ts +7 -0
- package/dist/utils/theme.js +10 -0
- package/package.json +45 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Donghyeok Shin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,126 @@
|
|
|
1
|
-
#
|
|
1
|
+
# shindonghyeo-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
신동혁의 개인 React 디자인 시스템입니다. [개발 블로그](https://shindonghyeo.github.io)의 디자인을 다른 React 사이트에서도 쓸 수 있게 토큰과 컴포넌트로 정리했습니다.
|
|
4
|
+
|
|
5
|
+
- **디자인 토큰:** 색, 글꼴, 모서리 둥글기, 레이아웃 폭을 CSS 변수(`--sd-*`)로 정의하고, 라이트/다크 모드를 지원합니다.
|
|
6
|
+
- **React 컴포넌트:** CSS Modules로 스타일을 입혀서 런타임 비용이 없고, Next.js·Astro·Vite 어디서나 동작합니다.
|
|
7
|
+
- **서버 렌더링 대응:** 상호작용이 필요한 컴포넌트(`ThemeToggle`, `Toc`, `ReadingProgress`)에만 `'use client'`가 붙어 있습니다.
|
|
8
|
+
|
|
9
|
+
## 설치
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install shindonghyeo-ui
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 사용법
|
|
16
|
+
|
|
17
|
+
스타일 파일은 앱 진입점에서 **한 번만** 불러옵니다.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import 'shindonghyeo-ui/styles.css';
|
|
21
|
+
import { Banner, Button, GradientText } from 'shindonghyeo-ui';
|
|
22
|
+
|
|
23
|
+
export function Hero() {
|
|
24
|
+
return (
|
|
25
|
+
<Banner eyebrow="Dev Log" title={<>안녕하세요, <GradientText>신동혁</GradientText>입니다.</>}>
|
|
26
|
+
<Button variant="primary" href="/blog">글 둘러보기 →</Button>
|
|
27
|
+
</Banner>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
글꼴은 [Pretendard](https://github.com/orioncactus/pretendard)를 쓰도록 지정되어 있습니다. 사이트의 `<head>`에 Pretendard를 불러와 주세요.
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<link
|
|
36
|
+
rel="stylesheet"
|
|
37
|
+
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css"
|
|
38
|
+
/>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 다크 모드
|
|
42
|
+
|
|
43
|
+
테마는 `<html data-theme="light|dark">`로 바뀝니다. 첫 화면에서 깜빡이지 않도록 `<head>`에 `themeInitScript`를 인라인으로 넣어 주세요.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { themeInitScript } from 'shindonghyeo-ui';
|
|
47
|
+
|
|
48
|
+
<head>
|
|
49
|
+
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
|
|
50
|
+
</head>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Astro에서 쓰기
|
|
54
|
+
|
|
55
|
+
`@astrojs/react`를 추가하면 `.astro` 파일에서 바로 쓸 수 있습니다. 기본적으로 HTML로만 렌더링되고, 클릭이나 스크롤에 반응해야 하는 컴포넌트에만 `client:*` 지시문을 붙이면 됩니다.
|
|
56
|
+
|
|
57
|
+
```astro
|
|
58
|
+
---
|
|
59
|
+
import { Banner, ThemeToggle, Toc } from 'shindonghyeo-ui';
|
|
60
|
+
---
|
|
61
|
+
<Banner title="모든 글" />
|
|
62
|
+
<ThemeToggle client:load />
|
|
63
|
+
<Toc items={items} client:idle />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Next.js에서 쓰기
|
|
67
|
+
|
|
68
|
+
링크를 그리는 컴포넌트(`Navbar`, `PostList`, `Footer`)는 `linkAs`로 `next/link`의 `Link`를 받을 수 있습니다.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import Link from 'next/link';
|
|
72
|
+
|
|
73
|
+
<PostList items={posts} linkAs={Link} />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 컴포넌트
|
|
77
|
+
|
|
78
|
+
| 컴포넌트 | 설명 |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `Container` | 본문 폭(680px) 또는 넓은 폭(`wide`, 980px)으로 내용을 가운데 정렬 |
|
|
81
|
+
| `Navbar` | 화면 위에 붙어 있는 반투명 메뉴바. 현재 메뉴는 알약 모양으로 강조 |
|
|
82
|
+
| `Banner` | 그라데이션과 점 패턴 배경의 페이지 상단 배너 |
|
|
83
|
+
| `Logo` | 그라데이션 정사각형 로고 마크 |
|
|
84
|
+
| `Button` | `primary`(그라데이션) / `secondary` 버튼. `href`가 있으면 링크로 렌더링 |
|
|
85
|
+
| `IconButton` | 아이콘만 있는 버튼 |
|
|
86
|
+
| `Chip` | 날짜, 읽는 시간 같은 작은 정보 표시 |
|
|
87
|
+
| `GradientText` | 그라데이션 글자 |
|
|
88
|
+
| `SectionTitle` | 왼쪽에 그라데이션 막대가 붙은 섹션 제목 |
|
|
89
|
+
| `PostList` | 마우스를 올리면 카드처럼 떠오르는 글 목록 |
|
|
90
|
+
| `Prose` | 마크다운에서 나온 HTML(제목, 목록, 표, 인용문, 코드)을 꾸미는 본문 영역 |
|
|
91
|
+
| `Toc` | 현재 읽는 제목을 강조하는 목차 (`sidebar` / `inline`) |
|
|
92
|
+
| `ReadingProgress` | 스크롤한 만큼 차오르는 진행 바 |
|
|
93
|
+
| `ThemeToggle` | 라이트/다크 모드 전환 버튼 |
|
|
94
|
+
| `Footer` | 브랜드, 링크, 저작권, "맨 위로"가 있는 푸터 |
|
|
95
|
+
|
|
96
|
+
아이콘(`SunIcon`, `MoonIcon`, `GitHubIcon`, `CalendarIcon`, `ClockIcon`)도 함께 내보냅니다.
|
|
97
|
+
|
|
98
|
+
## 테마 바꾸기
|
|
99
|
+
|
|
100
|
+
포인트 색은 두 값만 덮어쓰면 그라데이션, 강조색, 옅은 배경색까지 모두 따라 바뀝니다.
|
|
101
|
+
|
|
102
|
+
```css
|
|
103
|
+
:root {
|
|
104
|
+
--sd-accent: #7c3aed;
|
|
105
|
+
--sd-accent-2: #db2777;
|
|
106
|
+
}
|
|
107
|
+
:root[data-theme='dark'] {
|
|
108
|
+
--sd-accent: #a78bfa;
|
|
109
|
+
--sd-accent-2: #f472b6;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
전체 토큰 목록은 [`src/styles/tokens.css`](src/styles/tokens.css)에 있습니다.
|
|
114
|
+
|
|
115
|
+
## 개발
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm install
|
|
119
|
+
npm run dev # 플레이그라운드(playground/)에서 컴포넌트 확인
|
|
120
|
+
npm run typecheck
|
|
121
|
+
npm run build # dist/에 JS, 타입, styles.css 생성
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 라이선스
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export interface BannerProps {
|
|
3
|
+
/** 제목 위의 작은 강조 문구. 문자열이나 링크 등 아무 요소나 넣을 수 있다 */
|
|
4
|
+
eyebrow?: ReactNode;
|
|
5
|
+
title?: ReactNode;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
/** 넓은 레이아웃(목차가 있는 글 페이지 등)과 왼쪽 정렬을 맞춘다 */
|
|
8
|
+
wide?: boolean;
|
|
9
|
+
size?: 'md' | 'lg';
|
|
10
|
+
className?: string;
|
|
11
|
+
/** 제목 아래에 들어갈 내용 (버튼, 메타 정보 등) */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/** 그라데이션과 점 패턴 배경이 깔린 페이지 상단 배너 */
|
|
15
|
+
export declare function Banner({ eyebrow, title, description, wide, size, className, children }: BannerProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import { Container as t } from "../Container/Container.js";
|
|
3
|
+
import n from "./Banner.module.js";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
// src/components/Banner/Banner.tsx
|
|
6
|
+
function a({ eyebrow: a, title: o, description: s, wide: c = !1, size: l = "md", className: u, children: d }) {
|
|
7
|
+
return /* @__PURE__ */ r("section", {
|
|
8
|
+
className: e(n.banner, u),
|
|
9
|
+
children: /* @__PURE__ */ r(t, {
|
|
10
|
+
wide: c,
|
|
11
|
+
children: /* @__PURE__ */ i("div", {
|
|
12
|
+
className: e(n.inner, n[l]),
|
|
13
|
+
children: [
|
|
14
|
+
a && /* @__PURE__ */ r("p", {
|
|
15
|
+
className: n.eyebrow,
|
|
16
|
+
children: a
|
|
17
|
+
}),
|
|
18
|
+
o && /* @__PURE__ */ r("h1", {
|
|
19
|
+
className: n.title,
|
|
20
|
+
children: o
|
|
21
|
+
}),
|
|
22
|
+
s && /* @__PURE__ */ r("p", {
|
|
23
|
+
className: n.description,
|
|
24
|
+
children: s
|
|
25
|
+
}),
|
|
26
|
+
d
|
|
27
|
+
]
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export { a as Banner };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/components/Banner/Banner.module.css
|
|
2
|
+
var e = "sd-banner-oyoWE", t = "sd-inner-G1vOq", n = "sd-lg-j2ruH", r = "sd-eyebrow-Rf8QJ", i = "sd-title-AeTHT", a = "sd-description-nbyf-", o = {
|
|
3
|
+
banner: e,
|
|
4
|
+
inner: t,
|
|
5
|
+
lg: n,
|
|
6
|
+
eyebrow: r,
|
|
7
|
+
title: i,
|
|
8
|
+
description: a
|
|
9
|
+
};
|
|
10
|
+
export { e as banner, o as default, a as description, r as eyebrow, t as inner, n as lg, i as title };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
type ButtonBaseProps = {
|
|
3
|
+
variant?: 'primary' | 'secondary';
|
|
4
|
+
};
|
|
5
|
+
type ButtonAsLink = ButtonBaseProps & ComponentPropsWithoutRef<'a'> & {
|
|
6
|
+
href: string;
|
|
7
|
+
};
|
|
8
|
+
type ButtonAsButton = ButtonBaseProps & ComponentPropsWithoutRef<'button'> & {
|
|
9
|
+
href?: undefined;
|
|
10
|
+
};
|
|
11
|
+
/** href가 있으면 <a>, 없으면 <button>으로 렌더링한다 */
|
|
12
|
+
export type ButtonProps = ButtonAsLink | ButtonAsButton;
|
|
13
|
+
export declare function Button({ variant, className, ...props }: ButtonProps): import('react').JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./Button.module.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
// src/components/Button/Button.tsx
|
|
5
|
+
function r({ variant: r = "secondary", className: i, ...a }) {
|
|
6
|
+
let o = e(t.button, t[r], i);
|
|
7
|
+
return a.href === void 0 ? /* @__PURE__ */ n("button", {
|
|
8
|
+
type: "button",
|
|
9
|
+
className: o,
|
|
10
|
+
...a
|
|
11
|
+
}) : /* @__PURE__ */ n("a", {
|
|
12
|
+
className: o,
|
|
13
|
+
...a
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export { r as Button };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
export interface ChipProps extends ComponentPropsWithoutRef<'span'> {
|
|
3
|
+
icon?: ReactNode;
|
|
4
|
+
variant?: 'outline' | 'accent';
|
|
5
|
+
}
|
|
6
|
+
export declare function Chip({ icon, variant, className, children, ...props }: ChipProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./Chip.module.js";
|
|
3
|
+
import { jsxs as n } from "react/jsx-runtime";
|
|
4
|
+
// src/components/Chip/Chip.tsx
|
|
5
|
+
function r({ icon: r, variant: i = "outline", className: a, children: o, ...s }) {
|
|
6
|
+
return /* @__PURE__ */ n("span", {
|
|
7
|
+
className: e(t.chip, t[i], a),
|
|
8
|
+
...s,
|
|
9
|
+
children: [r, o]
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export { r as Chip };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
export interface ContainerProps extends ComponentPropsWithoutRef<'div'> {
|
|
3
|
+
/** 1024px 이상에서 넓은 폭(--sd-wide-width)을 쓴다 */
|
|
4
|
+
wide?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function Container({ wide, className, ...props }: ContainerProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./Container.module.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
// src/components/Container/Container.tsx
|
|
5
|
+
function r({ wide: r = !1, className: i, ...a }) {
|
|
6
|
+
return /* @__PURE__ */ n("div", {
|
|
7
|
+
className: e(t.container, r && t.wide, i),
|
|
8
|
+
...a
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export { r as Container };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ElementType, ReactNode } from 'react';
|
|
2
|
+
export interface FooterLink {
|
|
3
|
+
href: string;
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
external?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface FooterProps {
|
|
8
|
+
brand?: ReactNode;
|
|
9
|
+
description?: ReactNode;
|
|
10
|
+
links?: FooterLink[];
|
|
11
|
+
copyright?: ReactNode;
|
|
12
|
+
/** "맨 위로" 링크를 보여 줄지 여부 */
|
|
13
|
+
backToTop?: boolean;
|
|
14
|
+
linkAs?: ElementType;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function Footer({ brand, description, links, copyright, backToTop, linkAs: Link, className, }: FooterProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import { Container as t } from "../Container/Container.js";
|
|
3
|
+
import n from "./Footer.module.js";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
// src/components/Footer/Footer.tsx
|
|
6
|
+
function a({ brand: a, description: o, links: s = [], copyright: c, backToTop: l = !0, linkAs: u = "a", className: d }) {
|
|
7
|
+
return /* @__PURE__ */ r("footer", {
|
|
8
|
+
className: e(n.footer, d),
|
|
9
|
+
children: /* @__PURE__ */ i(t, {
|
|
10
|
+
wide: !0,
|
|
11
|
+
children: [(a || o || s.length > 0) && /* @__PURE__ */ i("div", {
|
|
12
|
+
className: n.top,
|
|
13
|
+
children: [/* @__PURE__ */ i("div", { children: [a && /* @__PURE__ */ r("div", {
|
|
14
|
+
className: n.brand,
|
|
15
|
+
children: a
|
|
16
|
+
}), o && /* @__PURE__ */ r("p", {
|
|
17
|
+
className: n.description,
|
|
18
|
+
children: o
|
|
19
|
+
})] }), s.length > 0 && /* @__PURE__ */ r("nav", {
|
|
20
|
+
className: n.links,
|
|
21
|
+
children: s.map((e) => e.external ? /* @__PURE__ */ r("a", {
|
|
22
|
+
href: e.href,
|
|
23
|
+
target: "_blank",
|
|
24
|
+
rel: "noopener",
|
|
25
|
+
children: e.label
|
|
26
|
+
}, e.href) : /* @__PURE__ */ r(u, {
|
|
27
|
+
href: e.href,
|
|
28
|
+
children: e.label
|
|
29
|
+
}, e.href))
|
|
30
|
+
})]
|
|
31
|
+
}), /* @__PURE__ */ i("div", {
|
|
32
|
+
className: n.bottom,
|
|
33
|
+
children: [/* @__PURE__ */ r("span", { children: c }), l && /* @__PURE__ */ r("a", {
|
|
34
|
+
href: "#",
|
|
35
|
+
children: "맨 위로 ↑"
|
|
36
|
+
})]
|
|
37
|
+
})]
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export { a as Footer };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/components/Footer/Footer.module.css
|
|
2
|
+
var e = "sd-footer-A76NF", t = "sd-top-oMBPr", n = "sd-brand-EmGiM", r = "sd-description-gSSA8", i = "sd-links-yNuwL", a = "sd-bottom-np3Xe", o = {
|
|
3
|
+
footer: e,
|
|
4
|
+
top: t,
|
|
5
|
+
brand: n,
|
|
6
|
+
description: r,
|
|
7
|
+
links: i,
|
|
8
|
+
bottom: a
|
|
9
|
+
};
|
|
10
|
+
export { a as bottom, n as brand, o as default, r as description, e as footer, i as links, t as top };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./GradientText.module.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
// src/components/GradientText/GradientText.tsx
|
|
5
|
+
function r({ className: r, ...i }) {
|
|
6
|
+
return /* @__PURE__ */ n("span", {
|
|
7
|
+
className: e(t.gradientText, r),
|
|
8
|
+
...i
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export { r as GradientText };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
type IconButtonAsLink = ComponentPropsWithoutRef<'a'> & {
|
|
3
|
+
href: string;
|
|
4
|
+
'aria-label': string;
|
|
5
|
+
};
|
|
6
|
+
type IconButtonAsButton = ComponentPropsWithoutRef<'button'> & {
|
|
7
|
+
href?: undefined;
|
|
8
|
+
'aria-label': string;
|
|
9
|
+
};
|
|
10
|
+
/** 아이콘만 있는 정사각형 버튼. 스크린 리더를 위해 aria-label이 필수다 */
|
|
11
|
+
export type IconButtonProps = IconButtonAsLink | IconButtonAsButton;
|
|
12
|
+
export declare function IconButton({ className, ...props }: IconButtonProps): import('react').JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./IconButton.module.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
// src/components/IconButton/IconButton.tsx
|
|
5
|
+
function r({ className: r, ...i }) {
|
|
6
|
+
let a = e(t.iconButton, r);
|
|
7
|
+
return i.href === void 0 ? /* @__PURE__ */ n("button", {
|
|
8
|
+
type: "button",
|
|
9
|
+
className: a,
|
|
10
|
+
...i
|
|
11
|
+
}) : /* @__PURE__ */ n("a", {
|
|
12
|
+
className: a,
|
|
13
|
+
...i
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export { r as IconButton };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./Logo.module.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
// src/components/Logo/Logo.tsx
|
|
5
|
+
function r({ size: r = 30, children: i = "신", className: a }) {
|
|
6
|
+
return /* @__PURE__ */ n("span", {
|
|
7
|
+
className: e(t.logo, a),
|
|
8
|
+
style: { "--size": `${r}px` },
|
|
9
|
+
"aria-hidden": "true",
|
|
10
|
+
children: i
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export { r as Logo };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ElementType, ReactNode } from 'react';
|
|
2
|
+
export interface NavItem {
|
|
3
|
+
href: string;
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
active?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface NavbarProps {
|
|
8
|
+
/** 왼쪽 브랜드 영역 (로고와 사이트 이름) */
|
|
9
|
+
brand: ReactNode;
|
|
10
|
+
brandHref?: string;
|
|
11
|
+
links?: NavItem[];
|
|
12
|
+
/** 오른쪽 끝 아이콘 버튼들 (GitHub, ThemeToggle 등) */
|
|
13
|
+
actions?: ReactNode;
|
|
14
|
+
/** 링크에 쓸 컴포넌트. Next.js라면 next/link의 Link를 넘긴다 */
|
|
15
|
+
linkAs?: ElementType;
|
|
16
|
+
className?: string;
|
|
17
|
+
/** 메뉴바 아래쪽에 붙일 요소 (예: <ReadingProgress />) */
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
/** 화면 위에 붙어 있는 반투명 메뉴바 */
|
|
21
|
+
export declare function Navbar({ brand, brandHref, links, actions, linkAs: Link, className, children }: NavbarProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import { Container as t } from "../Container/Container.js";
|
|
3
|
+
import n from "./Navbar.module.js";
|
|
4
|
+
import { Fragment as r, jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
// src/components/Navbar/Navbar.tsx
|
|
6
|
+
function o({ brand: o, brandHref: s = "/", links: c = [], actions: l, linkAs: u = "a", className: d, children: f }) {
|
|
7
|
+
return /* @__PURE__ */ a("header", {
|
|
8
|
+
className: e(n.header, d),
|
|
9
|
+
children: [/* @__PURE__ */ i(t, {
|
|
10
|
+
wide: !0,
|
|
11
|
+
children: /* @__PURE__ */ a("nav", {
|
|
12
|
+
className: n.nav,
|
|
13
|
+
children: [/* @__PURE__ */ i(u, {
|
|
14
|
+
className: n.brand,
|
|
15
|
+
href: s,
|
|
16
|
+
children: o
|
|
17
|
+
}), /* @__PURE__ */ a("div", {
|
|
18
|
+
className: n.right,
|
|
19
|
+
children: [c.map((t) => /* @__PURE__ */ i(u, {
|
|
20
|
+
href: t.href,
|
|
21
|
+
className: e(n.link, t.active && n.active),
|
|
22
|
+
"aria-current": t.active ? "page" : void 0,
|
|
23
|
+
children: t.label
|
|
24
|
+
}, t.href)), l && /* @__PURE__ */ a(r, { children: [c.length > 0 && /* @__PURE__ */ i("span", {
|
|
25
|
+
className: n.divider,
|
|
26
|
+
"aria-hidden": "true"
|
|
27
|
+
}), /* @__PURE__ */ i("div", {
|
|
28
|
+
className: n.actions,
|
|
29
|
+
children: l
|
|
30
|
+
})] })]
|
|
31
|
+
})]
|
|
32
|
+
})
|
|
33
|
+
}), f]
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export { o as Navbar };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/components/Navbar/Navbar.module.css
|
|
2
|
+
var e = "sd-header-aqSzZ", t = "sd-nav-0oisE", n = "sd-brand-P5uLt", r = "sd-right-64OWF", i = "sd-link-UwJZv", a = "sd-active-Rf9bv", o = "sd-divider-ZjuJB", s = "sd-actions-8BNWJ", c = {
|
|
3
|
+
header: e,
|
|
4
|
+
nav: t,
|
|
5
|
+
brand: n,
|
|
6
|
+
right: r,
|
|
7
|
+
link: i,
|
|
8
|
+
active: a,
|
|
9
|
+
divider: o,
|
|
10
|
+
actions: s
|
|
11
|
+
};
|
|
12
|
+
export { s as actions, a as active, n as brand, c as default, o as divider, e as header, i as link, t as nav, r as right };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ElementType, ReactNode } from 'react';
|
|
2
|
+
export interface PostListItem {
|
|
3
|
+
href: string;
|
|
4
|
+
title: ReactNode;
|
|
5
|
+
description?: ReactNode;
|
|
6
|
+
/** 날짜, 읽는 시간처럼 아래에 가운뎃점으로 이어 보여 줄 정보 */
|
|
7
|
+
meta?: ReactNode[];
|
|
8
|
+
}
|
|
9
|
+
export interface PostListProps {
|
|
10
|
+
items: PostListItem[];
|
|
11
|
+
/** 링크에 쓸 컴포넌트. Next.js라면 next/link의 Link를 넘긴다 */
|
|
12
|
+
linkAs?: ElementType;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
/** 마우스를 올리면 카드처럼 떠오르는 글 목록 */
|
|
16
|
+
export declare function PostList({ items, linkAs: Link, className }: PostListProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./PostList.module.js";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
// src/components/PostList/PostList.tsx
|
|
5
|
+
function i({ items: i, linkAs: a = "a", className: o }) {
|
|
6
|
+
return /* @__PURE__ */ n("ul", {
|
|
7
|
+
className: e(t.list, o),
|
|
8
|
+
children: i.map((e) => /* @__PURE__ */ n("li", { children: /* @__PURE__ */ r(a, {
|
|
9
|
+
className: t.item,
|
|
10
|
+
href: e.href,
|
|
11
|
+
children: [/* @__PURE__ */ r("div", {
|
|
12
|
+
className: t.body,
|
|
13
|
+
children: [
|
|
14
|
+
/* @__PURE__ */ n("h3", {
|
|
15
|
+
className: t.title,
|
|
16
|
+
children: e.title
|
|
17
|
+
}),
|
|
18
|
+
e.description && /* @__PURE__ */ n("p", {
|
|
19
|
+
className: t.description,
|
|
20
|
+
children: e.description
|
|
21
|
+
}),
|
|
22
|
+
e.meta && e.meta.length > 0 && /* @__PURE__ */ n("div", {
|
|
23
|
+
className: t.meta,
|
|
24
|
+
children: e.meta.map((e, t) => /* @__PURE__ */ n("span", { children: e }, t))
|
|
25
|
+
})
|
|
26
|
+
]
|
|
27
|
+
}), /* @__PURE__ */ n("span", {
|
|
28
|
+
className: t.arrow,
|
|
29
|
+
"aria-hidden": "true",
|
|
30
|
+
children: "→"
|
|
31
|
+
})]
|
|
32
|
+
}) }, e.href))
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export { i as PostList };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/components/PostList/PostList.module.css
|
|
2
|
+
var e = "sd-list-6ceM4", t = "sd-item-OoBbo", n = "sd-body-WN59Q", r = "sd-title-wv5Et", i = "sd-description--Ndtf", a = "sd-meta-91tNg", o = "sd-arrow-cVFi9", s = {
|
|
3
|
+
list: e,
|
|
4
|
+
item: t,
|
|
5
|
+
body: n,
|
|
6
|
+
title: r,
|
|
7
|
+
description: i,
|
|
8
|
+
meta: a,
|
|
9
|
+
arrow: o
|
|
10
|
+
};
|
|
11
|
+
export { o as arrow, n as body, s as default, i as description, t as item, e as list, a as meta, r as title };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, ElementType } from 'react';
|
|
2
|
+
export interface ProseProps extends ComponentPropsWithoutRef<'div'> {
|
|
3
|
+
as?: ElementType;
|
|
4
|
+
}
|
|
5
|
+
/** 마크다운에서 나온 HTML(제목, 목록, 표, 인용문, 코드)을 꾸며 주는 본문 영역 */
|
|
6
|
+
export declare function Prose({ as: Tag, className, ...props }: ProseProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./Prose.module.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
// src/components/Prose/Prose.tsx
|
|
5
|
+
function r({ as: r = "div", className: i, ...a }) {
|
|
6
|
+
return /* @__PURE__ */ n(r, {
|
|
7
|
+
className: e(t.prose, i),
|
|
8
|
+
...a
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export { r as Prose };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ReadingProgressProps {
|
|
2
|
+
/** 'attached'는 Navbar 아래쪽에, 'fixed'는 화면 맨 위에 붙는다 */
|
|
3
|
+
position?: 'attached' | 'fixed';
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
/** 스크롤한 만큼 차오르는 읽기 진행 바 */
|
|
7
|
+
export declare function ReadingProgress({ position, className }: ReadingProgressProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../../utils/cx.js";
|
|
3
|
+
import t from "./ReadingProgress.module.js";
|
|
4
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
5
|
+
import { useEffect as r, useRef as i } from "react";
|
|
6
|
+
// src/components/ReadingProgress/ReadingProgress.tsx
|
|
7
|
+
function a({ position: a = "attached", className: o }) {
|
|
8
|
+
let s = i(null);
|
|
9
|
+
return r(() => {
|
|
10
|
+
let e = 0;
|
|
11
|
+
function t() {
|
|
12
|
+
e = 0;
|
|
13
|
+
let t = document.documentElement, n = t.scrollHeight - t.clientHeight;
|
|
14
|
+
s.current?.style.setProperty("--progress", String(n > 0 ? t.scrollTop / n : 0));
|
|
15
|
+
}
|
|
16
|
+
function n() {
|
|
17
|
+
e ||= requestAnimationFrame(t);
|
|
18
|
+
}
|
|
19
|
+
return t(), addEventListener("scroll", n, { passive: !0 }), addEventListener("resize", n), () => {
|
|
20
|
+
removeEventListener("scroll", n), removeEventListener("resize", n), cancelAnimationFrame(e);
|
|
21
|
+
};
|
|
22
|
+
}, []), /* @__PURE__ */ n("div", {
|
|
23
|
+
ref: s,
|
|
24
|
+
className: e(t.progress, t[a], o),
|
|
25
|
+
"aria-hidden": "true"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export { a as ReadingProgress };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ElementType, ReactNode } from 'react';
|
|
2
|
+
export interface SectionTitleProps {
|
|
3
|
+
as?: ElementType;
|
|
4
|
+
/** 제목 오른쪽 끝에 놓을 요소 (예: "전체 보기 →" 링크) */
|
|
5
|
+
action?: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/** 왼쪽에 그라데이션 막대가 붙은 섹션 제목 */
|
|
10
|
+
export declare function SectionTitle({ as: Heading, action, className, children }: SectionTitleProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { cx as e } from "../../utils/cx.js";
|
|
2
|
+
import t from "./SectionTitle.module.js";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
// src/components/SectionTitle/SectionTitle.tsx
|
|
5
|
+
function i({ as: i = "h2", action: a, className: o, children: s }) {
|
|
6
|
+
return /* @__PURE__ */ r("div", {
|
|
7
|
+
className: e(t.head, o),
|
|
8
|
+
children: [/* @__PURE__ */ n(i, {
|
|
9
|
+
className: t.title,
|
|
10
|
+
children: s
|
|
11
|
+
}), a && /* @__PURE__ */ n("div", {
|
|
12
|
+
className: t.action,
|
|
13
|
+
children: a
|
|
14
|
+
})]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export { i as SectionTitle };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ThemeToggleProps {
|
|
2
|
+
className?: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* 라이트/다크 모드 전환 버튼.
|
|
6
|
+
* 어떤 아이콘을 보일지는 CSS가 <html data-theme>을 보고 정하므로 첫 화면부터 맞는 아이콘이 나온다.
|
|
7
|
+
* 깜빡임을 막으려면 <head>에 themeInitScript도 함께 넣는다.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ThemeToggle({ className }: ThemeToggleProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../../utils/cx.js";
|
|
3
|
+
import { IconButton as t } from "../IconButton/IconButton.js";
|
|
4
|
+
import { MoonIcon as n, SunIcon as r } from "../icons/icons.js";
|
|
5
|
+
import { THEME_STORAGE_KEY as i } from "../../utils/theme.js";
|
|
6
|
+
import a from "./ThemeToggle.module.js";
|
|
7
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
8
|
+
// src/components/ThemeToggle/ThemeToggle.tsx
|
|
9
|
+
function c({ className: c }) {
|
|
10
|
+
function l() {
|
|
11
|
+
let e = document.documentElement, t = e.dataset.theme === "dark" ? "light" : "dark";
|
|
12
|
+
e.dataset.theme = t;
|
|
13
|
+
try {
|
|
14
|
+
localStorage.setItem(i, t);
|
|
15
|
+
} catch {}
|
|
16
|
+
}
|
|
17
|
+
return /* @__PURE__ */ s(t, {
|
|
18
|
+
"aria-label": "다크 모드 전환",
|
|
19
|
+
onClick: l,
|
|
20
|
+
className: e(a.toggle, c),
|
|
21
|
+
children: [/* @__PURE__ */ o(r, { className: a.sun }), /* @__PURE__ */ o(n, { className: a.moon })]
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export { c as ThemeToggle };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TocItem {
|
|
2
|
+
/** 제목 요소의 id */
|
|
3
|
+
id: string;
|
|
4
|
+
text: string;
|
|
5
|
+
/** 2 = h2, 3 = h3 */
|
|
6
|
+
depth: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TocProps {
|
|
9
|
+
items: TocItem[];
|
|
10
|
+
title?: string;
|
|
11
|
+
/** 'sidebar'는 옆에 따라다니는 목차, 'inline'은 본문 위에 접었다 펴는 목차 */
|
|
12
|
+
variant?: 'sidebar' | 'inline';
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
/** 지금 읽고 있는 제목을 강조해 주는 목차 */
|
|
16
|
+
export declare function Toc({ items, title, variant, className }: TocProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../../utils/cx.js";
|
|
3
|
+
import t from "./Toc.module.js";
|
|
4
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
5
|
+
import { useEffect as i, useState as a } from "react";
|
|
6
|
+
// src/components/Toc/Toc.tsx
|
|
7
|
+
function o({ items: i, title: a = "목차", variant: o = "sidebar", className: c }) {
|
|
8
|
+
let l = s(o === "sidebar" ? i : []), u = /* @__PURE__ */ n("ol", {
|
|
9
|
+
className: t.list,
|
|
10
|
+
children: i.map((r) => /* @__PURE__ */ n("li", {
|
|
11
|
+
className: r.depth >= 3 ? t.sub : void 0,
|
|
12
|
+
children: /* @__PURE__ */ n("a", {
|
|
13
|
+
href: `#${r.id}`,
|
|
14
|
+
className: e(t.link, r.id === l && t.active),
|
|
15
|
+
children: r.text
|
|
16
|
+
})
|
|
17
|
+
}, r.id))
|
|
18
|
+
});
|
|
19
|
+
return o === "inline" ? /* @__PURE__ */ r("details", {
|
|
20
|
+
className: e(t.inline, c),
|
|
21
|
+
children: [/* @__PURE__ */ n("summary", { children: a }), u]
|
|
22
|
+
}) : /* @__PURE__ */ r("nav", {
|
|
23
|
+
className: e(t.sidebar, c),
|
|
24
|
+
"aria-label": a,
|
|
25
|
+
children: [/* @__PURE__ */ n("p", {
|
|
26
|
+
className: t.title,
|
|
27
|
+
children: a
|
|
28
|
+
}), u]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function s(e, t = 140) {
|
|
32
|
+
let [n, r] = a();
|
|
33
|
+
return i(() => {
|
|
34
|
+
if (e.length === 0) return;
|
|
35
|
+
let n = 0;
|
|
36
|
+
function i() {
|
|
37
|
+
n = 0;
|
|
38
|
+
let i;
|
|
39
|
+
for (let n of e) {
|
|
40
|
+
let e = document.getElementById(n.id);
|
|
41
|
+
e && e.getBoundingClientRect().top < t && (i = n.id);
|
|
42
|
+
}
|
|
43
|
+
r(i);
|
|
44
|
+
}
|
|
45
|
+
function a() {
|
|
46
|
+
n ||= requestAnimationFrame(i);
|
|
47
|
+
}
|
|
48
|
+
return i(), addEventListener("scroll", a, { passive: !0 }), () => {
|
|
49
|
+
removeEventListener("scroll", a), cancelAnimationFrame(n);
|
|
50
|
+
};
|
|
51
|
+
}, [e, t]), n;
|
|
52
|
+
}
|
|
53
|
+
export { o as Toc };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/components/Toc/Toc.module.css
|
|
2
|
+
var e = "sd-list-ntSgl", t = "sd-sidebar-Oo6GJ", n = "sd-title-sn1aR", r = "sd-link-1iE3P", i = "sd-sub-zhWRq", a = "sd-active-2X5eC", o = "sd-inline-oyv0J", s = {
|
|
3
|
+
list: e,
|
|
4
|
+
sidebar: t,
|
|
5
|
+
title: n,
|
|
6
|
+
link: r,
|
|
7
|
+
sub: i,
|
|
8
|
+
active: a,
|
|
9
|
+
inline: o
|
|
10
|
+
};
|
|
11
|
+
export { a as active, s as default, o as inline, r as link, e as list, t as sidebar, i as sub, n as title };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SVGProps } from 'react';
|
|
2
|
+
type IconProps = SVGProps<SVGSVGElement> & {
|
|
3
|
+
size?: number;
|
|
4
|
+
};
|
|
5
|
+
export declare function SunIcon(props: IconProps): import('react').JSX.Element;
|
|
6
|
+
export declare function MoonIcon(props: IconProps): import('react').JSX.Element;
|
|
7
|
+
export declare function CalendarIcon(props: IconProps): import('react').JSX.Element;
|
|
8
|
+
export declare function ClockIcon(props: IconProps): import('react').JSX.Element;
|
|
9
|
+
export declare function GitHubIcon({ size, ...props }: IconProps): import('react').JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as e, jsxs as t } from "react/jsx-runtime";
|
|
2
|
+
// src/components/icons/icons.tsx
|
|
3
|
+
function n({ size: e = 18, ...t }) {
|
|
4
|
+
return {
|
|
5
|
+
width: e,
|
|
6
|
+
height: e,
|
|
7
|
+
viewBox: "0 0 24 24",
|
|
8
|
+
fill: "none",
|
|
9
|
+
stroke: "currentColor",
|
|
10
|
+
strokeWidth: 1.8,
|
|
11
|
+
strokeLinecap: "round",
|
|
12
|
+
strokeLinejoin: "round",
|
|
13
|
+
"aria-hidden": !0,
|
|
14
|
+
...t
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function r(r) {
|
|
18
|
+
return /* @__PURE__ */ t("svg", {
|
|
19
|
+
...n(r),
|
|
20
|
+
children: [/* @__PURE__ */ e("circle", {
|
|
21
|
+
cx: "12",
|
|
22
|
+
cy: "12",
|
|
23
|
+
r: "4.5"
|
|
24
|
+
}), /* @__PURE__ */ e("path", { d: "M12 2v2.5M12 19.5V22M4.2 4.2l1.8 1.8M18 18l1.8 1.8M2 12h2.5M19.5 12H22M4.2 19.8 6 18M18 6l1.8-1.8" })]
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function i(t) {
|
|
28
|
+
return /* @__PURE__ */ e("svg", {
|
|
29
|
+
...n(t),
|
|
30
|
+
children: /* @__PURE__ */ e("path", { d: "M20.5 14.5A8.5 8.5 0 0 1 9.5 3.5a8.5 8.5 0 1 0 11 11Z" })
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function a(r) {
|
|
34
|
+
return /* @__PURE__ */ t("svg", {
|
|
35
|
+
...n({
|
|
36
|
+
size: 15,
|
|
37
|
+
...r
|
|
38
|
+
}),
|
|
39
|
+
children: [/* @__PURE__ */ e("rect", {
|
|
40
|
+
x: "3.5",
|
|
41
|
+
y: "5",
|
|
42
|
+
width: "17",
|
|
43
|
+
height: "15",
|
|
44
|
+
rx: "2.5"
|
|
45
|
+
}), /* @__PURE__ */ e("path", { d: "M3.5 10h17M8 3v4M16 3v4" })]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function o(r) {
|
|
49
|
+
return /* @__PURE__ */ t("svg", {
|
|
50
|
+
...n({
|
|
51
|
+
size: 15,
|
|
52
|
+
...r
|
|
53
|
+
}),
|
|
54
|
+
children: [/* @__PURE__ */ e("circle", {
|
|
55
|
+
cx: "12",
|
|
56
|
+
cy: "12",
|
|
57
|
+
r: "8.5"
|
|
58
|
+
}), /* @__PURE__ */ e("path", { d: "M12 7.5V12l3 2" })]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function s({ size: t = 18, ...n }) {
|
|
62
|
+
return /* @__PURE__ */ e("svg", {
|
|
63
|
+
width: t,
|
|
64
|
+
height: t,
|
|
65
|
+
viewBox: "0 0 16 16",
|
|
66
|
+
"aria-hidden": "true",
|
|
67
|
+
...n,
|
|
68
|
+
children: /* @__PURE__ */ e("path", {
|
|
69
|
+
fill: "currentColor",
|
|
70
|
+
d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export { a as CalendarIcon, o as ClockIcon, s as GitHubIcon, i as MoonIcon, r as SunIcon };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { Banner, type BannerProps } from './components/Banner/Banner';
|
|
2
|
+
export { Button, type ButtonProps } from './components/Button/Button';
|
|
3
|
+
export { Chip, type ChipProps } from './components/Chip/Chip';
|
|
4
|
+
export { Container, type ContainerProps } from './components/Container/Container';
|
|
5
|
+
export { Footer, type FooterLink, type FooterProps } from './components/Footer/Footer';
|
|
6
|
+
export { GradientText } from './components/GradientText/GradientText';
|
|
7
|
+
export { IconButton, type IconButtonProps } from './components/IconButton/IconButton';
|
|
8
|
+
export { CalendarIcon, ClockIcon, GitHubIcon, MoonIcon, SunIcon } from './components/icons/icons';
|
|
9
|
+
export { Logo, type LogoProps } from './components/Logo/Logo';
|
|
10
|
+
export { Navbar, type NavItem, type NavbarProps } from './components/Navbar/Navbar';
|
|
11
|
+
export { PostList, type PostListItem, type PostListProps } from './components/PostList/PostList';
|
|
12
|
+
export { Prose, type ProseProps } from './components/Prose/Prose';
|
|
13
|
+
export { ReadingProgress, type ReadingProgressProps } from './components/ReadingProgress/ReadingProgress';
|
|
14
|
+
export { SectionTitle, type SectionTitleProps } from './components/SectionTitle/SectionTitle';
|
|
15
|
+
export { ThemeToggle, type ThemeToggleProps } from './components/ThemeToggle/ThemeToggle';
|
|
16
|
+
export { Toc, type TocItem, type TocProps } from './components/Toc/Toc';
|
|
17
|
+
export { cx } from './utils/cx';
|
|
18
|
+
export { THEME_STORAGE_KEY, themeInitScript, type Theme } from './utils/theme';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* empty css */
|
|
2
|
+
/* empty css */
|
|
3
|
+
import { cx as e } from "./utils/cx.js";
|
|
4
|
+
import { Container as t } from "./components/Container/Container.js";
|
|
5
|
+
import { Banner as n } from "./components/Banner/Banner.js";
|
|
6
|
+
import { Button as r } from "./components/Button/Button.js";
|
|
7
|
+
import { Chip as i } from "./components/Chip/Chip.js";
|
|
8
|
+
import { Footer as a } from "./components/Footer/Footer.js";
|
|
9
|
+
import { GradientText as o } from "./components/GradientText/GradientText.js";
|
|
10
|
+
import { IconButton as s } from "./components/IconButton/IconButton.js";
|
|
11
|
+
import { CalendarIcon as c, ClockIcon as l, GitHubIcon as u, MoonIcon as d, SunIcon as f } from "./components/icons/icons.js";
|
|
12
|
+
import { Logo as p } from "./components/Logo/Logo.js";
|
|
13
|
+
import { Navbar as m } from "./components/Navbar/Navbar.js";
|
|
14
|
+
import { PostList as h } from "./components/PostList/PostList.js";
|
|
15
|
+
import { Prose as g } from "./components/Prose/Prose.js";
|
|
16
|
+
import { ReadingProgress as _ } from "./components/ReadingProgress/ReadingProgress.js";
|
|
17
|
+
import { SectionTitle as v } from "./components/SectionTitle/SectionTitle.js";
|
|
18
|
+
import { THEME_STORAGE_KEY as y, themeInitScript as b } from "./utils/theme.js";
|
|
19
|
+
import { ThemeToggle as x } from "./components/ThemeToggle/ThemeToggle.js";
|
|
20
|
+
import { Toc as S } from "./components/Toc/Toc.js";
|
|
21
|
+
export { n as Banner, r as Button, c as CalendarIcon, i as Chip, l as ClockIcon, t as Container, a as Footer, u as GitHubIcon, o as GradientText, s as IconButton, p as Logo, d as MoonIcon, m as Navbar, h as PostList, g as Prose, _ as ReadingProgress, v as SectionTitle, f as SunIcon, y as THEME_STORAGE_KEY, x as ThemeToggle, S as Toc, e as cx, b as themeInitScript };
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
:root{--sd-bg:#fff;--sd-bg-subtle:#f6f8fa;--sd-bg-elevated:#fff;--sd-text:#2b3138;--sd-text-strong:#0d1117;--sd-text-muted:#6b7280;--sd-border:#e6e8eb;--sd-accent:#0d9488;--sd-accent-2:#2563eb;--sd-accent-soft:color-mix(in srgb, var(--sd-accent) 10%, transparent);--sd-accent-2-soft:color-mix(in srgb, var(--sd-accent-2) 8%, transparent);--sd-gradient:linear-gradient(120deg, var(--sd-accent), var(--sd-accent-2));--sd-code-bg:#f1f3f5;--sd-dot:#0d111714;--sd-shadow:0 1px 2px #0d11170a, 0 8px 24px #0d11170f;--sd-selection:color-mix(in srgb, var(--sd-accent) 18%, transparent);--sd-font-sans:"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif;--sd-font-mono:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;--sd-radius-sm:8px;--sd-radius-md:12px;--sd-radius-lg:14px;--sd-radius-full:999px;--sd-content-width:680px;--sd-wide-width:980px;--sd-header-height:64px;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root[data-theme=dark]{--sd-bg:#0e1013;--sd-bg-subtle:#15181c;--sd-bg-elevated:#1a1e23;--sd-text:#cfd4da;--sd-text-strong:#f3f4f6;--sd-text-muted:#8b929b;--sd-border:#262a30;--sd-accent:#2dd4bf;--sd-accent-2:#60a5fa;--sd-accent-soft:color-mix(in srgb, var(--sd-accent) 12%, transparent);--sd-accent-2-soft:color-mix(in srgb, var(--sd-accent-2) 10%, transparent);--sd-code-bg:#1d2126;--sd-dot:#ffffff0f;--sd-shadow:0 1px 2px #0000004d, 0 8px 24px #00000059;--sd-selection:color-mix(in srgb, var(--sd-accent) 25%, transparent);--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;scroll-padding-top:calc(var(--sd-header-height) + 24px)}@media (prefers-reduced-motion:no-preference){html{scroll-behavior:smooth}}body{background:var(--sd-bg);color:var(--sd-text);font-family:var(--sd-font-sans);letter-spacing:-.01em;word-break:keep-all;overflow-wrap:break-word;-webkit-font-smoothing:antialiased;margin:0;font-size:17px;line-height:1.8}::selection{background:var(--sd-selection)}h1,h2,h3,h4,h5,h6{color:var(--sd-text-strong);letter-spacing:-.025em;margin:0;font-weight:700;line-height:1.35}a{color:inherit;text-decoration:none}img{max-width:100%;height:auto}code,pre{font-family:var(--sd-font-mono)}@media (width<=640px){body{font-size:16px}}.sd-container-JMoiT{width:100%;max-width:calc(var(--sd-content-width) + 48px);margin:0 auto;padding-left:24px;padding-right:24px}@media (width>=1024px){.sd-wide-LxoRm{max-width:calc(var(--sd-wide-width) + 48px)}}@media (width<=640px){.sd-container-JMoiT{padding-left:20px;padding-right:20px}}.sd-banner-oyoWE{background:radial-gradient(ellipse 60% 120% at 10% 0%, var(--sd-accent-soft), transparent 70%), radial-gradient(ellipse 50% 100% at 95% 10%, var(--sd-accent-2-soft), transparent 70%), var(--sd-bg-subtle);border-bottom:1px solid var(--sd-border);position:relative;overflow:hidden}.sd-banner-oyoWE:before{content:"";background-image:radial-gradient(var(--sd-dot) 1px, transparent 1px);pointer-events:none;background-size:22px 22px;position:absolute;inset:0;-webkit-mask-image:linear-gradient(#000,#0000 90%);mask-image:linear-gradient(#000,#0000 90%)}.sd-banner-oyoWE>*{position:relative}.sd-inner-G1vOq{max-width:var(--sd-content-width);padding:64px 0 52px}.sd-lg-j2ruH{padding:72px 0 60px}.sd-eyebrow-Rf8QJ{color:var(--sd-accent);letter-spacing:.08em;text-transform:uppercase;align-items:center;gap:6px;margin:0 0 14px;font-size:.8rem;font-weight:700;display:flex}.sd-eyebrow-Rf8QJ a{transition:opacity .15s}.sd-eyebrow-Rf8QJ a:hover{opacity:.75}.sd-title-AeTHT{letter-spacing:-.04em;font-size:2.4rem;line-height:1.28}.sd-lg-j2ruH .sd-title-AeTHT{letter-spacing:-.045em;font-size:2.6rem;line-height:1.25}.sd-description-nbyf-{color:var(--sd-text-muted);margin:16px 0 0;font-size:1.08rem;line-height:1.65}@media (width<=640px){.sd-inner-G1vOq,.sd-lg-j2ruH{padding:44px 0 36px}.sd-title-AeTHT,.sd-lg-j2ruH .sd-title-AeTHT{font-size:1.75rem}.sd-description-nbyf-{font-size:1rem}}.sd-button-2ZuB7{border:1px solid var(--sd-border);background:var(--sd-bg-elevated);height:42px;color:var(--sd-text-strong);font:inherit;cursor:pointer;border-radius:10px;align-items:center;gap:8px;padding:0 18px;font-size:.92rem;font-weight:600;text-decoration:none;transition:transform .15s,box-shadow .15s,border-color .15s;display:inline-flex}.sd-button-2ZuB7:hover{box-shadow:var(--sd-shadow);transform:translateY(-1px)}.sd-button-2ZuB7:focus-visible{outline:2px solid var(--sd-accent);outline-offset:2px}.sd-primary-s1sM6{background:var(--sd-gradient);color:#fff;border-color:#0000}.sd-chip-0nys-{border-radius:var(--sd-radius-full);align-items:center;gap:6px;padding:5px 12px;font-size:.84rem;line-height:1.4;display:inline-flex}.sd-outline-s240C{border:1px solid var(--sd-border);background:var(--sd-bg-elevated);color:var(--sd-text-muted)}.sd-accent-wWCCB{background:var(--sd-accent-soft);color:var(--sd-accent);padding:1px 9px;font-size:.78rem;font-weight:700}.sd-footer-A76NF{border-top:1px solid var(--sd-border);background:var(--sd-bg-subtle);color:var(--sd-text-muted);font-size:.875rem}.sd-top-oMBPr{border-bottom:1px solid var(--sd-border);justify-content:space-between;gap:32px;padding:40px 0 28px;display:flex}.sd-brand-EmGiM{color:var(--sd-text-strong);letter-spacing:-.03em;align-items:center;gap:10px;font-size:1rem;font-weight:800;display:inline-flex}.sd-description-gSSA8{margin:8px 0 0}.sd-links-yNuwL{flex-wrap:wrap;align-content:flex-start;gap:8px 24px;display:flex}.sd-footer-A76NF a{transition:color .15s}.sd-footer-A76NF a:hover{color:var(--sd-accent)}.sd-bottom-np3Xe{justify-content:space-between;gap:16px;padding:20px 0 40px;font-size:.82rem;display:flex}@media (width<=640px){.sd-top-oMBPr{flex-direction:column;gap:20px}}.sd-gradientText-CxkVn{background:var(--sd-gradient);color:#0000;-webkit-background-clip:text;background-clip:text}.sd-iconButton-JAF-a{width:36px;height:36px;color:var(--sd-text-muted);cursor:pointer;background:0 0;border:none;border-radius:10px;justify-content:center;align-items:center;padding:0;transition:background-color .15s,color .15s;display:inline-flex}.sd-iconButton-JAF-a:hover{background:var(--sd-bg-subtle);color:var(--sd-text-strong)}.sd-iconButton-JAF-a:focus-visible{outline:2px solid var(--sd-accent);outline-offset:2px}.sd-logo-p5tMH{width:var(--size);height:var(--size);border-radius:calc(var(--size) * .3);background:var(--sd-gradient);color:#fff;font-size:calc(var(--size) * .5);letter-spacing:0;box-shadow:0 2px 8px var(--sd-accent-soft);flex-shrink:0;justify-content:center;align-items:center;font-weight:800;line-height:1;display:inline-flex}.sd-header-aqSzZ{z-index:20;background:color-mix(in srgb, var(--sd-bg) 80%, transparent);-webkit-backdrop-filter:saturate(180%)blur(14px);border-bottom:1px solid var(--sd-border);position:sticky;top:0}.sd-nav-0oisE{height:var(--sd-header-height);justify-content:space-between;align-items:center;gap:16px;display:flex}.sd-brand-P5uLt{color:var(--sd-text-strong);letter-spacing:-.035em;white-space:nowrap;align-items:center;gap:10px;font-size:1.05rem;font-weight:800;display:flex}.sd-right-64OWF{align-items:center;gap:2px;display:flex}.sd-link-UwJZv{border-radius:var(--sd-radius-full);color:var(--sd-text-muted);padding:6px 14px;font-size:.93rem;font-weight:500;line-height:1.6;transition:background-color .15s,color .15s;display:inline-block}.sd-link-UwJZv:hover{color:var(--sd-text-strong)}.sd-active-Rf9bv,.sd-active-Rf9bv:hover{background:var(--sd-accent-soft);color:var(--sd-accent);font-weight:700}.sd-divider-ZjuJB{background:var(--sd-border);width:1px;height:18px;margin:0 8px}.sd-actions-8BNWJ{align-items:center;gap:2px;display:flex}@media (width<=640px){.sd-link-UwJZv{padding:6px 11px}.sd-divider-ZjuJB{display:none}}.sd-list-6ceM4{flex-direction:column;gap:4px;margin:0 -20px;padding:0;list-style:none;display:flex}.sd-item-OoBbo{border-radius:var(--sd-radius-lg);border:1px solid #0000;align-items:center;gap:24px;padding:22px 20px;transition:background-color .2s,border-color .2s,box-shadow .2s;display:flex}.sd-item-OoBbo:hover{border-color:var(--sd-border);background:var(--sd-bg-elevated);box-shadow:var(--sd-shadow)}.sd-body-WN59Q{flex:1;min-width:0}.sd-title-wv5Et{font-size:1.22rem;transition:color .15s}.sd-item-OoBbo:hover .sd-title-wv5Et{color:var(--sd-accent)}.sd-description--Ndtf{color:var(--sd-text);margin:6px 0 0;font-size:.97rem;line-height:1.65}.sd-meta-91tNg{color:var(--sd-text-muted);flex-wrap:wrap;margin-top:12px;font-size:.84rem;display:flex}.sd-meta-91tNg>:not(:first-child):before{content:"·";margin:0 8px}.sd-arrow-cVFi9{color:var(--sd-accent);opacity:0;font-size:1.2rem;transition:opacity .2s,transform .2s;transform:translate(-8px)}.sd-item-OoBbo:hover .sd-arrow-cVFi9{opacity:1;transform:none}@media (width<=640px){.sd-list-6ceM4{margin:0 -12px}.sd-item-OoBbo{padding:18px 12px}.sd-arrow-cVFi9{display:none}}.sd-prose-Obc4t{min-width:0;color:var(--sd-text)}.sd-prose-Obc4t>:first-child{margin-top:0}.sd-prose-Obc4t p{margin:0 0 1.4em}.sd-prose-Obc4t h2{margin:2.8em 0 .9em;padding-left:16px;font-size:1.5rem;position:relative}.sd-prose-Obc4t h2:before{content:"";background:var(--sd-gradient);border-radius:4px;width:4px;position:absolute;top:.2em;bottom:.2em;left:0}.sd-prose-Obc4t h3{margin:2em 0 .7em;font-size:1.22rem}.sd-prose-Obc4t h4{margin:1.6em 0 .6em;font-size:1.05rem}.sd-prose-Obc4t a{color:var(--sd-accent);text-decoration:underline;-webkit-text-decoration-color:color-mix(in srgb, var(--sd-accent) 40%, transparent);text-decoration-color:color-mix(in srgb, var(--sd-accent) 40%, transparent);text-underline-offset:3px;transition:text-decoration-color .15s}.sd-prose-Obc4t a:hover{-webkit-text-decoration-color:var(--sd-accent);text-decoration-color:var(--sd-accent)}.sd-prose-Obc4t strong{color:var(--sd-text-strong);font-weight:700}.sd-prose-Obc4t ul,.sd-prose-Obc4t ol{margin:0 0 1.4em;padding-left:1.4em}.sd-prose-Obc4t li{margin:.35em 0;padding-left:.2em}.sd-prose-Obc4t ul>li::marker{color:var(--sd-accent)}.sd-prose-Obc4t ol>li::marker{color:var(--sd-accent);font-variant-numeric:tabular-nums;font-weight:700}.sd-prose-Obc4t img{border-radius:var(--sd-radius-md);margin:2em auto;display:block}.sd-prose-Obc4t blockquote{border-left:4px solid var(--sd-accent);background:var(--sd-accent-soft);color:var(--sd-text);border-radius:0 10px 10px 0;margin:1.8em 0;padding:1em 1.25em}.sd-prose-Obc4t blockquote>:last-child{margin-bottom:0}.sd-prose-Obc4t hr{border:none;border-top:1px solid var(--sd-border);margin:2.5em 0}.sd-prose-Obc4t table{border:1px solid var(--sd-border);border-collapse:separate;border-spacing:0;border-radius:10px;width:100%;max-width:100%;margin:1.8em 0;font-size:.92em;line-height:1.6;display:block;overflow-x:auto}.sd-prose-Obc4t th,.sd-prose-Obc4t td{border-bottom:1px solid var(--sd-border);text-align:left;vertical-align:top;padding:.7em 1em}.sd-prose-Obc4t tr:last-child td{border-bottom:none}.sd-prose-Obc4t th{background:var(--sd-bg-subtle);color:var(--sd-text-strong);white-space:nowrap;font-weight:600}.sd-prose-Obc4t td:first-child{white-space:nowrap;font-weight:500}.sd-prose-Obc4t :not(pre)>code{background:var(--sd-code-bg);color:var(--sd-text-strong);word-break:break-word;border-radius:5px;padding:.15em .4em;font-size:.86em}.sd-prose-Obc4t pre{border:1px solid var(--sd-border);border-radius:var(--sd-radius-md);background:var(--sd-bg-subtle);margin:1.8em 0;padding:1.1em 1.3em;font-size:.86rem;line-height:1.65;overflow-x:auto}:root:not([data-theme=dark]) .sd-prose-Obc4t :is(.shiki,.astro-code){background-color:var(--sd-bg-subtle)!important}:root[data-theme=dark] .sd-prose-Obc4t :is(.shiki,.astro-code),:root[data-theme=dark] .sd-prose-Obc4t :is(.shiki,.astro-code) span{color:var(--shiki-dark)!important;background-color:var(--shiki-dark-bg)!important}@media (width<=640px){.sd-prose-Obc4t h2{font-size:1.32rem}}.sd-progress-52KzL{background:var(--sd-gradient);width:100%;height:3px;transform:scaleX(var(--progress,0));transform-origin:0;pointer-events:none;left:0}.sd-attached-n7Ovo{position:absolute;bottom:-1px}.sd-fixed-GqraP{z-index:30;position:fixed;top:0}.sd-head-tgU0N{justify-content:space-between;align-items:center;gap:16px;margin-bottom:16px;display:flex}.sd-title-Uxtii{font-variant-numeric:tabular-nums;align-items:center;gap:10px;font-size:1.3rem;display:flex}.sd-title-Uxtii:before{content:"";background:var(--sd-gradient);border-radius:4px;width:4px;height:1.1em}.sd-action-Vs6N5{color:var(--sd-text-muted);font-size:.88rem;font-weight:500}.sd-action-Vs6N5 a{transition:color .15s}.sd-action-Vs6N5 a:hover{color:var(--sd-accent)}:root[data-theme=dark] .sd-sun-7vHKu,:root:not([data-theme=dark]) .sd-moon-sqz-D{display:none}.sd-list-ntSgl{margin:0;padding:0;list-style:none}.sd-sidebar-Oo6GJ{top:calc(var(--sd-header-height) + 32px);max-height:calc(100vh - var(--sd-header-height) - 64px);align-self:start;font-size:.86rem;line-height:1.5;position:sticky;overflow-y:auto}.sd-title-sn1aR{color:var(--sd-text-strong);letter-spacing:.08em;margin:0 0 12px;font-size:.78rem;font-weight:700}.sd-sidebar-Oo6GJ .sd-list-ntSgl{border-left:2px solid var(--sd-border)}.sd-sidebar-Oo6GJ .sd-link-1iE3P{color:var(--sd-text-muted);border-left:2px solid #0000;margin-left:-2px;padding:6px 0 6px 14px;transition:color .15s,border-color .15s;display:block}.sd-sidebar-Oo6GJ .sd-sub-zhWRq .sd-link-1iE3P{padding-left:28px;font-size:.82rem}.sd-sidebar-Oo6GJ .sd-link-1iE3P:hover{color:var(--sd-text-strong)}.sd-sidebar-Oo6GJ .sd-active-2X5eC,.sd-sidebar-Oo6GJ .sd-active-2X5eC:hover{border-left-color:var(--sd-accent);color:var(--sd-accent);font-weight:600}.sd-inline-oyv0J{border:1px solid var(--sd-border);border-radius:var(--sd-radius-md);background:var(--sd-bg-subtle);font-size:.92rem}.sd-inline-oyv0J summary{color:var(--sd-text-strong);cursor:pointer;padding:12px 18px;font-weight:700}.sd-inline-oyv0J .sd-list-ntSgl{padding:0 18px 14px}.sd-inline-oyv0J li{padding:4px 0}.sd-inline-oyv0J .sd-sub-zhWRq{padding-left:16px}.sd-inline-oyv0J .sd-link-1iE3P{color:var(--sd-text-muted)}.sd-inline-oyv0J .sd-link-1iE3P:hover{color:var(--sd-accent)}
|
|
2
|
+
/*$vite$:1*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cx(...classes: (string | false | null | undefined)[]): string;
|
package/dist/utils/cx.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type Theme = 'light' | 'dark';
|
|
2
|
+
export declare const THEME_STORAGE_KEY = "theme";
|
|
3
|
+
/**
|
|
4
|
+
* 첫 렌더 전에 저장된 테마(없으면 시스템 설정)를 <html data-theme>에 적용하는 스크립트.
|
|
5
|
+
* <head> 안에 인라인으로 넣어야 화면이 깜빡이지 않는다.
|
|
6
|
+
*/
|
|
7
|
+
export declare const themeInitScript = "(() => {\n\tlet theme;\n\ttry { theme = localStorage.getItem('theme'); } catch {}\n\tif (theme !== 'light' && theme !== 'dark') {\n\t\ttheme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n\t}\n\tdocument.documentElement.dataset.theme = theme;\n})();";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/utils/theme.ts
|
|
2
|
+
var e = "theme", t = `(() => {
|
|
3
|
+
let theme;
|
|
4
|
+
try { theme = localStorage.getItem('${e}'); } catch {}
|
|
5
|
+
if (theme !== 'light' && theme !== 'dark') {
|
|
6
|
+
theme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
7
|
+
}
|
|
8
|
+
document.documentElement.dataset.theme = theme;
|
|
9
|
+
})();`;
|
|
10
|
+
export { e as THEME_STORAGE_KEY, t as themeInitScript };
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shindonghyeo-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "신동혁의 개인 React 디자인 시스템",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Donghyeok Shin",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/SHINDongHyeo/shindonghyeo-ui.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./styles.css": "./dist/styles.css"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"*.css"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "vite",
|
|
29
|
+
"build": "vite build",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"prepare": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=18",
|
|
35
|
+
"react-dom": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/react": "^19.3.0",
|
|
39
|
+
"@types/react-dom": "^19.3.0",
|
|
40
|
+
"@vitejs/plugin-react": "^6.1.2",
|
|
41
|
+
"react": "^19.3.1",
|
|
42
|
+
"react-dom": "^19.3.1",
|
|
43
|
+
"typescript": "^6.0.3",
|
|
44
|
+
"vite": "^8.3.5",
|
|
45
|
+
"vite-plugin-dts": "^5.1.2"
|
|
46
|
+
}
|
|
47
|
+
}
|