Skip to content

Commit deb0233

Browse files
committed
fix: panel 개선
1 parent f868bda commit deb0233

File tree

1 file changed

+156
-26
lines changed

1 file changed

+156
-26
lines changed

components/Panel.tsx

Lines changed: 156 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,165 @@
1-
import { ReactNode } from 'react';
2-
3-
type InfoPanelType =
4-
| 'info'
5-
| 'warning'
6-
| 'danger'
7-
| 'success'
8-
| 'note'
9-
| 'tip'
10-
| 'neutral'
11-
| 'quote';
12-
13-
type InfoPanelProps = {
1+
import React, { ReactNode } from 'react';
2+
import {
3+
Info,
4+
AlertTriangle,
5+
ShieldAlert,
6+
BadgeCheck,
7+
AlignLeft ,
8+
Sparkles,
9+
CheckCircle,
10+
CircleEllipsis,
11+
} from 'lucide-react';
12+
13+
export type InfoPanelType =
14+
| 'info'
15+
| 'warning'
16+
| 'danger'
17+
| 'success'
18+
| 'note'
19+
| 'tip'
20+
| 'neutral'
21+
| 'quote';
22+
23+
interface InfoPanelProps {
1424
type?: InfoPanelType;
1525
children: ReactNode;
26+
}
27+
28+
const typeStyles: Record<
29+
InfoPanelType,
30+
{
31+
bg: string;
32+
border: string;
33+
text: string;
34+
icon: JSX.Element;
35+
iconColor: string;
36+
titleColor: string;
37+
}
38+
> = {
39+
info: {
40+
bg: 'bg-blue-50 dark:bg-blue-900/30',
41+
border: 'border-l-4 border-blue-400',
42+
text: 'text-blue-900 dark:text-blue-100',
43+
icon: <Info className="w-4 h-4" />,
44+
iconColor: 'text-blue-500',
45+
titleColor: 'text-blue-700 dark:text-blue-300',
46+
},
47+
warning: {
48+
bg: 'bg-yellow-50 dark:bg-yellow-900/30',
49+
border: 'border-l-4 border-yellow-400',
50+
text: 'text-yellow-900 dark:text-yellow-100',
51+
icon: <AlertTriangle className="w-4 h-4" />,
52+
iconColor: 'text-yellow-500',
53+
titleColor: 'text-yellow-700 dark:text-yellow-200',
54+
},
55+
danger: {
56+
bg: 'bg-red-50 dark:bg-red-900/30',
57+
border: 'border-l-4 border-red-400',
58+
text: 'text-red-900 dark:text-red-100',
59+
icon: <ShieldAlert className="w-4 h-4" />,
60+
iconColor: 'text-red-500',
61+
titleColor: 'text-red-700 dark:text-red-300',
62+
},
63+
success: {
64+
bg: 'bg-green-50 dark:bg-green-900/30',
65+
border: 'border-l-4 border-green-400',
66+
text: 'text-green-900 dark:text-green-100',
67+
icon: <CheckCircle className="w-4 h-4" />,
68+
iconColor: 'text-green-500',
69+
titleColor: 'text-green-700 dark:text-green-300',
70+
},
71+
note: {
72+
bg: 'bg-purple-50 dark:bg-purple-900/30',
73+
border: 'border-l-4 border-purple-400',
74+
text: 'text-purple-900 dark:text-purple-100',
75+
icon: <BadgeCheck className="w-4 h-4" />,
76+
iconColor: 'text-purple-500',
77+
titleColor: 'text-purple-700 dark:text-purple-300',
78+
},
79+
tip: {
80+
bg: 'bg-cyan-50 dark:bg-cyan-900/30',
81+
border: 'border-l-4 border-cyan-400',
82+
text: 'text-cyan-900 dark:text-cyan-100',
83+
icon: <Sparkles className="w-4 h-4" />,
84+
iconColor: 'text-cyan-500',
85+
titleColor: 'text-cyan-700 dark:text-cyan-300',
86+
},
87+
neutral: {
88+
bg: 'bg-gray-50 dark:bg-gray-900/30',
89+
border: 'border-l-4 border-gray-400',
90+
text: 'text-gray-900 dark:text-gray-100',
91+
icon: <CircleEllipsis className="w-4 h-4" />,
92+
iconColor: 'text-gray-500',
93+
titleColor: 'text-gray-700 dark:text-gray-300',
94+
},
95+
quote: {
96+
bg: 'bg-indigo-50 dark:bg-indigo-900/30',
97+
border: 'border-l-4 border-indigo-400',
98+
text: 'text-indigo-900 dark:text-indigo-100',
99+
icon: <AlignLeft className="w-4 h-4" />,
100+
iconColor: 'text-indigo-500',
101+
titleColor: 'text-indigo-700 dark:text-indigo-300',
102+
},
16103
};
17104

105+
function extractTextFromChildren(children: ReactNode): string {
106+
if (typeof children === 'string') {
107+
return children;
108+
}
109+
if (Array.isArray(children)) {
110+
return children.map(extractTextFromChildren).join('\n');
111+
}
112+
if (typeof children === 'object' && 'props' in children) {
113+
return extractTextFromChildren((children as any).props.children);
114+
}
115+
return '';
116+
}
117+
118+
function renderText(type: InfoPanelType, children: ReactNode) {
119+
const text = extractTextFromChildren(children);
120+
const lines = text
121+
.split(/\r?\n/)
122+
.reduce<string[]>((acc, line) => {
123+
const trimmed = line.trim();
124+
if (!trimmed) return acc;
125+
126+
const hasHtmlTag = /^<\w+/.test(trimmed); // <a ...>, <code>, <strong> 등
127+
if (hasHtmlTag) {
128+
acc.push(trimmed); // 그대로 넣음
129+
} else if (acc.length > 0 && !/^<\w+/.test(acc[acc.length - 1])) {
130+
acc[acc.length - 1] += ' ' + trimmed; // 이전 줄에 이어붙임
131+
} else {
132+
acc.push(trimmed);
133+
}
134+
135+
return acc;
136+
}, []);
137+
138+
const [title, ...bodyLines] = lines;
139+
140+
const style = typeStyles[type];
141+
142+
return (
143+
<>
144+
<div className={`text-mi flex items-center gap-2 mt-2 mb-2 ${style.titleColor}`}>
145+
{React.cloneElement(style.icon, { className: `w-4 h-4 ${style.titleColor}` })}
146+
{title}
147+
</div>
148+
<div className={`space-y-1 text-mi pl-[1.3rem] ${style.titleColor}`}>
149+
{bodyLines.map((line, idx) => (
150+
<div key={idx}>{line}</div>
151+
))}
152+
</div>
153+
</>
154+
);
155+
}
156+
18157
export default function InfoPanel({ type = 'info', children }: InfoPanelProps) {
19-
const styles: Record<InfoPanelType, string> = {
20-
info: 'bg-blue-50 border-l-4 border-blue-400 text-blue-900',
21-
warning: 'bg-yellow-50 border-l-4 border-yellow-400 text-yellow-900',
22-
danger: 'bg-red-50 border-l-4 border-red-400 text-red-900',
23-
success: 'bg-green-50 border-l-4 border-green-400 text-green-900',
24-
note: 'bg-purple-50 border-l-4 border-purple-400 text-purple-900',
25-
tip: 'bg-cyan-50 border-l-4 border-cyan-400 text-cyan-900',
26-
neutral: 'bg-gray-50 border-l-4 border-gray-400 text-gray-900',
27-
quote: 'bg-indigo-50 border-l-4 border-indigo-400 text-indigo-900',
28-
};
158+
const style = typeStyles[type];
29159

30160
return (
31-
<div className={`px-4 py-2 my-4 rounded ${styles[type]}`}>
32-
<div className="prose-sm">{children}</div>
33-
</div>
161+
<div className={`my-4 px-4 py-3 rounded-md prose ${style.bg} ${style.border} ${style.text}`}>
162+
{renderText(type, children)}
163+
</div>
34164
);
35165
}

0 commit comments

Comments
 (0)