Skip to content

Commit 7ccd4f8

Browse files
Merge pull request #85 from ShipFriend0516/refactor/callout-ui-dom-to-react
feat: Callout 컴포넌트 커스텀으로 만들어 DOM 엘리먼트 생성방식 대체
2 parents c9f3ca8 + 336ebe1 commit 7ccd4f8

4 files changed

Lines changed: 46 additions & 22 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use client';
2+
import { ReactNode } from 'react';
3+
4+
interface CalloutProps {
5+
emoji?: string;
6+
children?: ReactNode;
7+
}
8+
9+
const Callout = ({ emoji, children }: CalloutProps) => {
10+
return (
11+
<div className="flex gap-3 rounded-lg border border-neutral-200 dark:border-neutral-700 bg-neutral-100 dark:bg-neutral-800/60 px-4 py-3 my-4 not-prose">
12+
{emoji && (
13+
<span className="shrink-0 text-xl leading-7 select-none">{emoji}</span>
14+
)}
15+
<div className="min-w-0 flex-1 text-sm leading-7 text-neutral-800 dark:text-neutral-200 [&>p:last-child]:mb-0 [&>p]:mb-1">
16+
{children}
17+
</div>
18+
</div>
19+
);
20+
};
21+
22+
export default Callout;

app/entities/post/detail/PostBody.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { useState } from 'react';
33
import LoadingIndicator from '@/app/entities/common/Loading/LoadingIndicator';
44
import ImageZoomOverlayContainer from '@/app/entities/common/Overlay/Image/ImageZoomOverlayContainer';
55
import Overlay from '@/app/entities/common/Overlay/Overlay';
6+
import Callout from '@/app/entities/post/detail/Callout';
67
import OgLinkCard from '@/app/entities/post/detail/OgLinkCard';
78
import PostTOC from '@/app/entities/post/detail/PostTOC';
89
import TagBox from '@/app/entities/post/tags/TagBox';
910
import useOverlay from '@/app/hooks/common/useOverlay';
1011
import useTheme from '@/app/hooks/useTheme';
1112
import MDEditor from '@uiw/react-md-editor';
1213
import {
13-
asideStyleRewrite,
14+
asideToCallout,
1415
addDescriptionUnderImage,
1516
renderYoutubeEmbed,
1617
renderOpenGraph,
@@ -79,9 +80,11 @@ const PostBody = ({ content, tags, loading }: Props) => {
7980
components={{
8081
ogcard: ({ href }: { href?: string }) =>
8182
href ? <OgLinkCard href={href} /> : null,
83+
callout: ({ emoji, children }: { emoji?: string; children?: React.ReactNode }) =>
84+
<Callout emoji={emoji}>{children}</Callout>,
8285
} as any}
8386
rehypeRewrite={(node, index?, parent?) => {
84-
asideStyleRewrite(node);
87+
asideToCallout(node);
8588
renderOpenGraph(node, index, parent as Element | undefined);
8689
renderYoutubeEmbed(
8790
node,

app/entities/post/write/BlogForm.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSearchParams } from 'next/navigation';
66
import { useEffect, useState } from 'react';
77
import ImageZoomOverlayContainer from '@/app/entities/common/Overlay/Image/ImageZoomOverlayContainer';
88
import Overlay from '@/app/entities/common/Overlay/Overlay';
9+
import Callout from '@/app/entities/post/detail/Callout';
910
import DraftListOverlay from '@/app/entities/post/write/DraftListOverlay';
1011
import PostMetadataForm from '@/app/entities/post/write/PostMetadataForm';
1112
import PostWriteButtons from '@/app/entities/post/write/PostWriteButtons';
@@ -21,7 +22,7 @@ import usePost from '@/app/hooks/post/usePost';
2122
import useTheme from '@/app/hooks/useTheme';
2223
import useToast from '@/app/hooks/useToast';
2324
import {
24-
asideStyleRewrite,
25+
asideToCallout,
2526
addDescriptionUnderImage,
2627
renderYoutubeEmbed,
2728
createImageClickHandler,
@@ -289,8 +290,12 @@ const BlogForm = () => {
289290
wrapperElement: {
290291
'data-color-mode': theme,
291292
},
293+
components: {
294+
callout: ({ emoji, children }: { emoji?: string; children?: React.ReactNode }) =>
295+
<Callout emoji={emoji}>{children}</Callout>,
296+
} as any,
292297
rehypeRewrite: (node, index?, parent?) => {
293-
asideStyleRewrite(node);
298+
asideToCallout(node);
294299
renderYoutubeEmbed(
295300
node,
296301
index || 0,

app/lib/utils/rehypeUtils.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,21 @@
55
import { SelectedImage } from '../../entities/post/detail/PostBody';
66

77
/**
8-
* aside 태그의 첫 번째 텍스트 노드를 emoji span으로 래핑
8+
* aside 태그를 callout 커스텀 컴포넌트 노드로 변환
9+
* 첫 번째 텍스트 노드를 emoji prop으로 추출하고 나머지는 children으로 유지
910
*/
10-
export const asideStyleRewrite = (node: any) => {
11+
export const asideToCallout = (node: any) => {
1112
if (node.type === 'element' && node.tagName === 'aside') {
12-
for (const child of [...node.children]) {
13-
if (node.children[0] === child && child.type === 'text') {
14-
node.children[0] = {
15-
type: 'element',
16-
tagName: 'span',
17-
properties: {
18-
className: 'aside-emoji',
19-
},
20-
children: [
21-
{
22-
type: 'text',
23-
value: child.value!,
24-
},
25-
],
26-
};
27-
}
13+
const firstChild = node.children[0];
14+
let emoji = '';
15+
16+
if (firstChild?.type === 'text') {
17+
emoji = firstChild.value.trim();
18+
node.children = node.children.slice(1);
2819
}
20+
21+
node.tagName = 'callout';
22+
node.properties = { ...node.properties, emoji };
2923
}
3024
};
3125

0 commit comments

Comments
 (0)