Skip to content

Commit f9b1d93

Browse files
authored
Merge pull request #163 from 01-binary/refactor/code-review-improvements-4
refactor(notion-to-jsx): 타입 안전성 강화, Props 전달 컨벤션 통일
2 parents 286f8b9 + add5029 commit f9b1d93

25 files changed

Lines changed: 191 additions & 213 deletions

File tree

.changeset/slow-cars-wait.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"notion-to-jsx": patch
3+
---
4+
5+
타입 안전성 강화, Props 전달 컨벤션 통일

packages/notion-to-jsx/src/components/Cover/styles.css.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { style } from '@vanilla-extract/css';
22
import { recipe } from '@vanilla-extract/recipes';
3+
export { skeletonWrapper } from '../../styles/loadingOverlay.css';
34

45
export const coverContainer = style({
56
position: 'relative',
@@ -18,31 +19,6 @@ export const coverContainer = style({
1819
},
1920
});
2021

21-
export const skeletonWrapper = recipe({
22-
base: {
23-
position: 'absolute',
24-
top: 0,
25-
left: 0,
26-
width: '100%',
27-
height: '100%',
28-
zIndex: 1,
29-
transition: 'opacity 0.3s ease',
30-
},
31-
variants: {
32-
isLoaded: {
33-
true: {
34-
opacity: 0,
35-
},
36-
false: {
37-
opacity: 1,
38-
},
39-
},
40-
},
41-
defaultVariants: {
42-
isLoaded: false,
43-
},
44-
});
45-
4622
export const imageStyle = recipe({
4723
base: {
4824
width: '100%',

packages/notion-to-jsx/src/components/Renderer/components/Block/BlockRenderer.tsx

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ type BlockRendererMap = {
3939
*
4040
* Props 전달 컨벤션:
4141
* - children이 있는 블록(Toggle, Table, ColumnList): block 통째 전달 (children 접근 필요)
42-
* - 그 외: 필요한 데이터만 추출하여 전달
42+
* - 텍스트 블록(paragraph, heading): children으로 RichText 전달
43+
* - 미디어/콘텐츠 블록: block.[type] 콘텐츠 객체를 전달 (컴포넌트가 내부 구조 처리)
4344
*/
4445
const blockRenderers: BlockRendererMap = {
4546
link_preview: (block) => <MemoizedLinkPreview url={block.link_preview.url} />,
@@ -68,48 +69,29 @@ const blockRenderers: BlockRendererMap = {
6869
</Heading3>
6970
),
7071

71-
code: (block) => (
72-
<div>
73-
<CodeBlock
74-
code={block.code.rich_text.map(rt => rt.plain_text).join('')}
75-
language={block.code.language}
76-
caption={block.code.caption}
77-
/>
78-
</div>
79-
),
72+
code: (block) => <CodeBlock code={block.code} />,
8073

8174
image: (block, isColumn) => (
8275
<figure>
83-
<MemoizedImage
84-
src={block.image.file?.url || block.image.external?.url || ''}
85-
alt={block.image.caption?.[0]?.plain_text || 'Notion image'}
86-
caption={block.image.caption}
87-
format={block.image.format}
88-
isColumn={isColumn}
89-
/>
76+
<MemoizedImage image={block.image} isColumn={isColumn} />
9077
</figure>
9178
),
9279

93-
bookmark: (block) => (
94-
<MemoizedBookmark
95-
url={block.bookmark.url}
96-
metadata={block.bookmark.metadata}
97-
/>
98-
),
80+
bookmark: (block) => <MemoizedBookmark bookmark={block.bookmark} />,
9981

10082
column_list: (block) => <ColumnList block={block} />,
10183

10284
column: () => null,
10385

104-
quote: (block) => <Quote richTexts={block.quote.rich_text} />,
86+
quote: (block) => <Quote quote={block.quote} />,
10587

10688
table: (block) => <Table block={block} />,
10789

10890
toggle: (block) => <Toggle block={block} />,
10991

11092
video: (block) => <Video video={block.video} />,
11193

112-
embed: (block) => <Embed url={block.embed.url} caption={block.embed.caption} />,
94+
embed: (block) => <Embed embed={block.embed} />,
11395
};
11496

11597
const BlockRenderer = memo(({ block, isColumn = false }: BlockRendererProps) => {

packages/notion-to-jsx/src/components/Renderer/components/Bookmark/Bookmark.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ import {
1111
favicon,
1212
urlText,
1313
} from './styles.css';
14-
import { OpenGraphData } from './type';
14+
import type { BookmarkBlock } from 'notion-types';
1515
import ExternalLink from '../shared/ExternalLink';
1616

17+
type BookmarkData = BookmarkBlock['bookmark'];
18+
1719
export interface BookmarkProps {
18-
url: string;
19-
metadata?: OpenGraphData;
20+
bookmark: BookmarkData;
2021
}
2122

22-
const Bookmark = ({ url, metadata }: BookmarkProps) => {
23+
const Bookmark = ({ bookmark }: BookmarkProps) => {
24+
const { url, metadata } = bookmark;
2325
const [isImageBroken, setIsImageBroken] = useState(false);
2426

2527
return (

packages/notion-to-jsx/src/components/Renderer/components/Caption/Caption.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { memo } from 'react';
2-
import { RichTextItem } from '../RichText/RichTexts';
2+
import type { RichTextItem } from 'notion-types';
33
import { MemoizedRichText } from '../MemoizedComponents';
44
import { captionStyle } from './styles.css';
55

packages/notion-to-jsx/src/components/Renderer/components/Code/CodeBlock.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ReactNode, useState, useEffect, useMemo, memo } from 'react';
22
import { codeBlock } from './styles.css';
33
import Prism, { Grammar, Token } from 'prismjs';
44
import { Caption } from '../Caption';
5-
import { RichTextItem } from '../RichText/RichTexts';
5+
import type { CodeBlock as CodeBlockType } from 'notion-types';
66

77
import 'prismjs/components/prism-typescript';
88
import 'prismjs/components/prism-jsx';
@@ -31,17 +31,20 @@ const renderToken = (token: string | Token, i: number): ReactNode => {
3131
);
3232
};
3333

34+
type CodeData = CodeBlockType['code'];
35+
3436
export interface CodeBlockProps {
35-
code: string;
36-
language: string;
37-
caption?: RichTextItem[];
37+
code: CodeData;
3838
}
3939

4040
// Prism.js는 브라우저 환경에서 window.Prism을 설정하여 추가 언어 플러그인을 등록합니다.
4141
// 이로 인해 서버(Node.js)와 클라이언트(브라우저)에서 토큰화 결과가 달라져
4242
// SSR 하이드레이션 불일치가 발생합니다. isMounted 패턴으로 클라이언트 마운트 후에만
4343
// 토큰화를 수행하여 이 문제를 방지합니다.
44-
const CodeBlock = memo(({ code, language, caption }: CodeBlockProps) => {
44+
const CodeBlock = memo(({ code }: CodeBlockProps) => {
45+
const { rich_text, language, caption } = code;
46+
const plainCode = rich_text.map(rt => rt.plain_text).join('');
47+
4548
const [isMounted, setIsMounted] = useState(false);
4649

4750
useEffect(() => {
@@ -52,18 +55,18 @@ const CodeBlock = memo(({ code, language, caption }: CodeBlockProps) => {
5255
if (!isMounted) return null;
5356
const prismLanguage =
5457
Prism.languages[language] || Prism.languages.plaintext;
55-
return Prism.tokenize(code, prismLanguage as Grammar);
56-
}, [code, language, isMounted]);
58+
return Prism.tokenize(plainCode, prismLanguage as Grammar);
59+
}, [plainCode, language, isMounted]);
5760

5861
return (
59-
<>
62+
<div>
6063
<pre className={`${codeBlock} language-${language}`} tabIndex={0}>
6164
<code className={`language-${language}`}>
62-
{tokens ? tokens.map((token, i) => renderToken(token, i)) : code}
65+
{tokens ? tokens.map((token, i) => renderToken(token, i)) : plainCode}
6366
</code>
6467
</pre>
6568
<Caption caption={caption} />
66-
</>
69+
</div>
6770
);
6871
});
6972

packages/notion-to-jsx/src/components/Renderer/components/Embed/Embed.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import {
88
isAllowedEmbedDomain,
99
} from '../../utils/embedUrlParser';
1010
import { embedIframe } from './styles.css';
11-
import { RichTextItem } from '../RichText/RichTexts';
11+
import type { EmbedBlock } from 'notion-types';
12+
13+
type EmbedData = EmbedBlock['embed'];
1214

1315
interface EmbedProps {
14-
url: string;
15-
caption?: RichTextItem[];
16+
embed: EmbedData;
1617
}
1718

18-
const Embed = memo(({ url, caption }: EmbedProps) => {
19+
const Embed = memo(({ embed }: EmbedProps) => {
20+
const { url, caption } = embed;
1921

2022
if (!isAllowedEmbedDomain(url)) {
2123
return (
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { style } from '@vanilla-extract/css';
2+
import { vars } from '../../../../styles/theme.css';
23

34
export const embedIframe = style({
45
width: '100%',
56
aspectRatio: '16 / 9',
67
border: 'none',
7-
borderRadius: '8px',
8+
borderRadius: vars.borderRadius.lg,
89
});

packages/notion-to-jsx/src/components/Renderer/components/Image/Image.tsx

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@ import {
55
imageStyle,
66
skeletonWrapper,
77
} from './styles.css';
8-
import { RichTextItem } from '../RichText/RichTexts';
8+
import type { ImageBlock } from 'notion-types';
99
import Skeleton from '../../../Skeleton';
1010
import { useImageLoad } from '../../../../hooks/useImageLoad';
11+
import { CONTENT_MAX_WIDTH_PX } from '../../../../styles/layout';
1112

12-
export interface ImageFormat {
13-
block_width?: number;
14-
block_height?: number;
15-
block_aspect_ratio?: number;
16-
}
17-
18-
const MAX_WIDTH = 720;
13+
type ImageData = ImageBlock['image'];
14+
type ImageFormat = ImageData['format'];
1915

2016
const getWidthStyle = (format?: ImageFormat, isColumn: boolean = false) => {
2117
if (
@@ -27,7 +23,7 @@ const getWidthStyle = (format?: ImageFormat, isColumn: boolean = false) => {
2723
}
2824

2925
if (format?.block_width) {
30-
return format.block_width > MAX_WIDTH
26+
return format.block_width > CONTENT_MAX_WIDTH_PX
3127
? '100%'
3228
: `${format.block_width}px`;
3329
}
@@ -51,21 +47,17 @@ const getImageTagStyle = (format?: ImageFormat) => {
5147
};
5248

5349
export interface ImageProps {
54-
src: string;
55-
alt: string;
56-
caption?: RichTextItem[];
57-
priority?: boolean;
58-
format?: ImageFormat;
50+
image: ImageData;
5951
isColumn?: boolean;
6052
}
6153

6254
const Image = ({
63-
src,
64-
alt,
65-
caption,
66-
format,
55+
image,
6756
isColumn = false,
6857
}: ImageProps) => {
58+
const src = image.file?.url || image.external?.url || '';
59+
const alt = image.caption?.[0]?.plain_text || 'Notion image';
60+
const { caption, format } = image;
6961
const { isLoaded, imgRef, handleLoad } = useImageLoad(src);
7062

7163
return (

packages/notion-to-jsx/src/components/Renderer/components/Image/styles.css.ts

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,4 @@ export const imageStyle = recipe({
5252
},
5353
});
5454

55-
export const skeletonWrapper = recipe({
56-
base: {
57-
position: 'absolute',
58-
top: 0,
59-
left: 0,
60-
width: '100%',
61-
height: '100%',
62-
zIndex: 1,
63-
transition: 'opacity 0.3s ease',
64-
},
65-
variants: {
66-
isLoaded: {
67-
true: {
68-
opacity: 0,
69-
},
70-
false: {
71-
opacity: 1,
72-
},
73-
},
74-
},
75-
defaultVariants: {
76-
isLoaded: false,
77-
},
78-
});
55+
export { skeletonWrapper } from '../../../../styles/loadingOverlay.css';

0 commit comments

Comments
 (0)