Skip to content

Refactor #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 29, 2025
Merged
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
5 changes: 5 additions & 0 deletions .changeset/few-oranges-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"notion-to-jsx": patch
---

refactor: components to use TypeScript types and remove React imports
4 changes: 2 additions & 2 deletions packages/notion-to-jsx/src/components/Cover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState } from 'react';
import { coverContainer, skeletonWrapper, imageStyle } from './styles.css';
import Skeleton from '../Skeleton';

interface CoverProps {
interface Props {
src: string;
alt: string;
}
Expand All @@ -11,7 +11,7 @@ interface CoverProps {
* 노션 페이지 상단에 표시되는 커버 이미지 컴포넌트
* 이미지 로딩 중에는 스켈레톤 UI를 표시하고, 로딩 완료 시 자연스럽게 이미지로 전환됩니다.
*/
const Cover = ({ src, alt }: CoverProps) => {
const Cover = ({ src, alt }: Props) => {
const [isLoaded, setIsLoaded] = useState(false);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from 'react';

import {
MemoizedRichText,
MemoizedImage,
Expand All @@ -12,76 +10,63 @@ import { ColumnList } from '../Column';
import { Quote } from '../Quote';
import Table from '../Table';
import { Toggle } from '../Toggle';
import { NotionBlock } from '../../../../types';

export interface Props {
block: any;
onFocus?: () => void;
index: number;
block: NotionBlock;
isColumn?: boolean;
}

const BlockRenderer: React.FC<Props> = ({
block,
onFocus,
index,
isColumn = false,
}) => {
const BlockRenderer = ({ block, isColumn = false }: Props) => {
if (!block) return null;

const blockProps = {
tabIndex: 0,
onFocus,
};

switch (block.type) {
case 'link_preview':
return (
<MemoizedLinkPreview url={block.link_preview.url} {...blockProps} />
);
return <MemoizedLinkPreview url={block.link_preview.url} />;
case 'paragraph':
return (
<Paragraph {...blockProps}>
<Paragraph>
<MemoizedRichText richTexts={block.paragraph.rich_text} />
</Paragraph>
);

case 'heading_1':
return (
<Heading1 {...blockProps}>
<Heading1>
<MemoizedRichText richTexts={block.heading_1.rich_text} />
</Heading1>
);

case 'heading_2':
return (
<Heading2 {...blockProps}>
<Heading2>
<MemoizedRichText richTexts={block.heading_2.rich_text} />
</Heading2>
);

case 'heading_3':
return (
<Heading3 {...blockProps}>
<Heading3>
<MemoizedRichText richTexts={block.heading_3.rich_text} />
</Heading3>
);

case 'code':
return (
<div {...blockProps}>
<div>
<CodeBlock
code={block.code.rich_text[0].text.content}
code={block.code.rich_text[0]?.text?.content || ''}
language={block.code.language}
caption={block.code.caption?.[0]?.plain_text}
caption={block.code.caption}
/>
</div>
);

case 'image':
return (
<figure {...blockProps}>
<figure>
<MemoizedImage
src={block.image.file?.url || block.image.external?.url}
src={block.image.file?.url || block.image.external?.url || ''}
alt={block.image.caption?.[0]?.plain_text || ''}
caption={block.image.caption}
format={block.image.format}
Expand All @@ -99,26 +84,20 @@ const BlockRenderer: React.FC<Props> = ({
);

case 'column_list':
return <ColumnList block={block} onFocus={onFocus} />;
return <ColumnList block={block} />;

case 'column':
// 개별 column은 ColumnList에서 처리됩니다
return null;

case 'quote':
return <Quote richTexts={block.quote.rich_text} {...blockProps} />;
return <Quote richTexts={block.quote.rich_text} />;

case 'table':
return <Table block={block} tabIndex={blockProps.tabIndex} />;
return <Table block={block} />;

case 'toggle':
return (
<Toggle
block={block}
tabIndex={blockProps.tabIndex}
onFocus={onFocus}
/>
);
return <Toggle block={block} />;

default:
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import {
link,
card,
Expand All @@ -11,22 +10,14 @@ import {
favicon,
urlText,
} from './styles.css';
import { OpenGraphData } from './type';

interface OpenGraphData {
title: string;
description: string;
image: string;
siteName: string;
url: string;
favicon?: string;
}

export interface BookmarkProps {
export interface Props {
url: string;
metadata?: OpenGraphData;
}

const Bookmark: React.FC<BookmarkProps> = ({ url, metadata }) => {
const Bookmark = ({ url, metadata }: Props) => {
return (
<a href={url} target="_blank" rel="noopener noreferrer" className={link}>
<div className={card}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface OpenGraphData {
title: string;
description: string;
image: string;
siteName: string;
url: string;
favicon?: string;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import { ReactNode, useMemo } from 'react';
import { codeBlock } from './styles.css';
import Prism, { Grammar, Token } from 'prismjs';
import { MemoizedRichText } from '../MemoizedComponents';
Expand All @@ -12,19 +12,13 @@ if (typeof window !== 'undefined') {
window.Prism = Prism;
}

export interface Props {
code: string;
language: string;
caption?: RichTextItem[];
}

const renderToken = (token: string | Token, i: number): React.ReactNode => {
const renderToken = (token: string | Token, i: number): ReactNode => {
if (typeof token === 'string') {
return <span key={i}>{token}</span>;
}

const content = token.content;
let tokenContent: React.ReactNode;
let tokenContent: ReactNode;

if (Array.isArray(content)) {
tokenContent = content.map((subToken, j) => renderToken(subToken, j));
Expand All @@ -41,7 +35,13 @@ const renderToken = (token: string | Token, i: number): React.ReactNode => {
);
};

const CodeBlock: React.FC<Props> = ({ code, language, caption }) => {
export interface Props {
code: string;
language: string;
caption?: RichTextItem[];
}

const CodeBlock = ({ code, language, caption }: Props) => {
const tokens = useMemo(() => {
const prismLanguage =
Prism.languages[language] || Prism.languages.plaintext;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import React from 'react';
import { ColumnBlock } from '../../../../types';
import BlockRenderer from '../Block/BlockRenderer';
import { columnContainer } from './styles.css';

export interface ColumnProps {
block: any;
onFocus?: () => void;
block: ColumnBlock;
}

const Column: React.FC<ColumnProps> = ({ block, onFocus }) => {
const Column = ({ block }: ColumnProps) => {
if (!block || !block.children) return null;

return (
<div className={columnContainer}>
{block.children.map((childBlock: any, index: number) => (
<BlockRenderer
key={childBlock.id}
block={childBlock}
onFocus={onFocus}
index={index}
isColumn
/>
{block.children.map((childBlock) => (
<BlockRenderer key={childBlock.id} block={childBlock} isColumn />
))}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import React from 'react';
import Column from './Column';
import { columnListContainer } from './styles.css';
import { ColumnListBlock } from '../../../../types';

export interface ColumnListProps {
block: any;
onFocus?: () => void;
block: ColumnListBlock;
}

const ColumnList: React.FC<ColumnListProps> = ({ block, onFocus }) => {
const ColumnList = ({ block }: ColumnListProps) => {
if (!block || !block.children) return null;

return (
<div className={columnListContainer}>
{block.children.map((column: any) => (
<Column key={column.id} block={column} onFocus={onFocus} />
{block.children.map((column) => (
<Column key={column.id} block={column} />
))}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { MemoizedRichText } from '../MemoizedComponents';
import {
imageContainer,
Expand All @@ -16,15 +16,6 @@ export interface ImageFormat {
block_aspect_ratio?: number;
}

export interface ImageProps {
src: string;
alt: string;
caption?: RichTextItem[];
priority?: boolean;
format?: ImageFormat;
isColumn?: boolean;
}

const MAX_WIDTH = 720;

// 이미지 태그에 사용되는 aspectRatio 스타일
Expand All @@ -34,13 +25,22 @@ const getImageTagStyle = (format?: ImageFormat) => {
: undefined;
};

const Image: React.FC<ImageProps> = ({
export interface Props {
src: string;
alt: string;
caption?: RichTextItem[];
priority?: boolean;
format?: ImageFormat;
isColumn?: boolean;
}

const Image = ({
src,
alt,
caption: imageCaption,
format,
isColumn = false,
}) => {
}: Props) => {
const [isLoaded, setIsLoaded] = useState(false);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as Image } from './Image';
export type { ImageProps } from './Image';
export type { Props as ImageProps } from './Image';
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import * as styles from './styles.css';

export interface LinkPreviewProps {
url: string;
}

interface RepoData {
name: string;
full_name: string;
Expand Down Expand Up @@ -172,7 +168,10 @@ const formatUpdatedTime = (dateString: string): string => {
}
};

const LinkPreview: React.FC<LinkPreviewProps> = ({ url }) => {
export interface LinkPreviewProps {
url: string;
}
const LinkPreview = ({ url }: LinkPreviewProps) => {
const [repoData, setRepoData] = useState<RepoData | null>(null);
const [figmaData, setFigmaData] = useState<FigmaData | null>(null);
const [loading, setLoading] = useState(true);
Expand Down
Loading