sh-ui-cli 0.49.0 → 0.50.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.
Files changed (85) hide show
  1. package/data/changelog/versions.json +14 -0
  2. package/data/registry/react/components/accordion/index.vanilla-extract.tsx +97 -0
  3. package/data/registry/react/components/accordion/styles.css.ts +131 -0
  4. package/data/registry/react/components/avatar/index.vanilla-extract.tsx +73 -0
  5. package/data/registry/react/components/avatar/styles.css.ts +68 -0
  6. package/data/registry/react/components/badge/index.vanilla-extract.tsx +40 -0
  7. package/data/registry/react/components/badge/styles.css.ts +71 -0
  8. package/data/registry/react/components/breadcrumb/index.vanilla-extract.tsx +152 -0
  9. package/data/registry/react/components/breadcrumb/styles.css.ts +95 -0
  10. package/data/registry/react/components/calendar/index.vanilla-extract.tsx +806 -0
  11. package/data/registry/react/components/calendar/styles.css.ts +250 -0
  12. package/data/registry/react/components/carousel/index.vanilla-extract.tsx +430 -0
  13. package/data/registry/react/components/carousel/styles.css.ts +169 -0
  14. package/data/registry/react/components/checkbox/index.vanilla-extract.tsx +96 -0
  15. package/data/registry/react/components/checkbox/styles.css.ts +74 -0
  16. package/data/registry/react/components/code-editor/index.vanilla-extract.tsx +230 -0
  17. package/data/registry/react/components/code-editor/styles.css.ts +97 -0
  18. package/data/registry/react/components/code-panel/index.vanilla-extract.tsx +191 -0
  19. package/data/registry/react/components/code-panel/styles.css.ts +151 -0
  20. package/data/registry/react/components/color-picker/index.vanilla-extract.tsx +467 -0
  21. package/data/registry/react/components/color-picker/styles.css.ts +169 -0
  22. package/data/registry/react/components/combobox/index.vanilla-extract.tsx +165 -0
  23. package/data/registry/react/components/combobox/styles.css.ts +174 -0
  24. package/data/registry/react/components/context-menu/index.vanilla-extract.tsx +251 -0
  25. package/data/registry/react/components/context-menu/styles.css.ts +167 -0
  26. package/data/registry/react/components/date-picker/index.vanilla-extract.tsx +520 -0
  27. package/data/registry/react/components/date-picker/styles.css.ts +111 -0
  28. package/data/registry/react/components/dialog/index.vanilla-extract.tsx +95 -0
  29. package/data/registry/react/components/dialog/styles.css.ts +140 -0
  30. package/data/registry/react/components/dropdown-menu/index.vanilla-extract.tsx +255 -0
  31. package/data/registry/react/components/dropdown-menu/styles.css.ts +175 -0
  32. package/data/registry/react/components/file-upload/index.vanilla-extract.tsx +487 -0
  33. package/data/registry/react/components/file-upload/styles.css.ts +193 -0
  34. package/data/registry/react/components/form/index.vanilla-extract.tsx +61 -0
  35. package/data/registry/react/components/form/styles.css.ts +56 -0
  36. package/data/registry/react/components/header/index.vanilla-extract.tsx +805 -0
  37. package/data/registry/react/components/header/styles.css.ts +413 -0
  38. package/data/registry/react/components/label/index.vanilla-extract.tsx +52 -0
  39. package/data/registry/react/components/label/styles.css.ts +141 -0
  40. package/data/registry/react/components/markdown-editor/index.vanilla-extract.tsx +119 -0
  41. package/data/registry/react/components/markdown-editor/styles.css.ts +231 -0
  42. package/data/registry/react/components/menubar/index.vanilla-extract.tsx +32 -0
  43. package/data/registry/react/components/menubar/styles.css.ts +53 -0
  44. package/data/registry/react/components/numeric-input/index.vanilla-extract.tsx +148 -0
  45. package/data/registry/react/components/numeric-input/styles.css.ts +65 -0
  46. package/data/registry/react/components/page-toc/index.vanilla-extract.tsx +174 -0
  47. package/data/registry/react/components/page-toc/styles.css.ts +97 -0
  48. package/data/registry/react/components/pagination/index.vanilla-extract.tsx +269 -0
  49. package/data/registry/react/components/pagination/styles.css.ts +113 -0
  50. package/data/registry/react/components/popover/index.vanilla-extract.tsx +113 -0
  51. package/data/registry/react/components/popover/styles.css.ts +78 -0
  52. package/data/registry/react/components/progress/index.vanilla-extract.tsx +54 -0
  53. package/data/registry/react/components/progress/styles.css.ts +53 -0
  54. package/data/registry/react/components/radio/index.vanilla-extract.tsx +65 -0
  55. package/data/registry/react/components/radio/styles.css.ts +79 -0
  56. package/data/registry/react/components/rich-text-editor/index.vanilla-extract.tsx +348 -0
  57. package/data/registry/react/components/rich-text-editor/styles.css.ts +243 -0
  58. package/data/registry/react/components/select/index.vanilla-extract.tsx +234 -0
  59. package/data/registry/react/components/select/styles.css.ts +225 -0
  60. package/data/registry/react/components/separator/index.vanilla-extract.tsx +46 -0
  61. package/data/registry/react/components/separator/styles.css.ts +24 -0
  62. package/data/registry/react/components/sidebar/index.vanilla-extract.tsx +1067 -0
  63. package/data/registry/react/components/sidebar/styles.css.ts +578 -0
  64. package/data/registry/react/components/skeleton/index.vanilla-extract.tsx +22 -0
  65. package/data/registry/react/components/skeleton/styles.css.ts +30 -0
  66. package/data/registry/react/components/slider/index.vanilla-extract.tsx +298 -0
  67. package/data/registry/react/components/slider/styles.css.ts +75 -0
  68. package/data/registry/react/components/spinner/index.vanilla-extract.tsx +38 -0
  69. package/data/registry/react/components/spinner/styles.css.ts +60 -0
  70. package/data/registry/react/components/switch/index.vanilla-extract.tsx +39 -0
  71. package/data/registry/react/components/switch/styles.css.ts +87 -0
  72. package/data/registry/react/components/tabs/index.vanilla-extract.tsx +91 -0
  73. package/data/registry/react/components/tabs/styles.css.ts +145 -0
  74. package/data/registry/react/components/textarea/index.vanilla-extract.tsx +23 -0
  75. package/data/registry/react/components/textarea/styles.css.ts +55 -0
  76. package/data/registry/react/components/toast/index.vanilla-extract.tsx +258 -0
  77. package/data/registry/react/components/toast/styles.css.ts +307 -0
  78. package/data/registry/react/components/toggle/index.vanilla-extract.tsx +131 -0
  79. package/data/registry/react/components/toggle/styles.css.ts +109 -0
  80. package/data/registry/react/components/tooltip/index.vanilla-extract.tsx +83 -0
  81. package/data/registry/react/components/tooltip/styles.css.ts +59 -0
  82. package/data/registry/react/registry.json +853 -36
  83. package/package.json +1 -1
  84. package/src/api.d.ts +4 -3
  85. package/src/constants.js +4 -3
@@ -0,0 +1,348 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect } from "react";
4
+ import { useEditor, EditorContent, type Editor } from "@tiptap/react";
5
+ import StarterKit from "@tiptap/starter-kit";
6
+ import Placeholder from "@tiptap/extension-placeholder";
7
+ import Link from "@tiptap/extension-link";
8
+ import { cn } from "@SH_UI_UTILS@";
9
+ import {
10
+ BoldIcon,
11
+ ItalicIcon,
12
+ StrikethroughIcon,
13
+ Heading1Icon,
14
+ Heading2Icon,
15
+ Heading3Icon,
16
+ ListIcon,
17
+ ListOrderedIcon,
18
+ QuoteIcon,
19
+ CodeIcon,
20
+ Code2Icon,
21
+ LinkIcon,
22
+ MinusIcon,
23
+ Undo2Icon,
24
+ Redo2Icon,
25
+ } from "lucide-react";
26
+ import { byKey, rte, rte__toolbar, rte__btn, rte__sep, rte__viewport, rte__content, rteIsEmpty } from "./styles.css";
27
+
28
+ export interface RichTextEditorProps {
29
+ /**
30
+ * Controlled — 현재 HTML. 명시 시 외부 상태가 진실원천이 되고 onChange 로 갱신한다.
31
+ * 미지정이면 uncontrolled — Tiptap editor 가 자체 doc 으로 동작.
32
+ */
33
+ value?: string;
34
+ /**
35
+ * Uncontrolled 초기 HTML. value 미지정 시에만 사용.
36
+ * @default ""
37
+ */
38
+ defaultValue?: string;
39
+ /** 본문이 바뀔 때마다 호출 (controlled · uncontrolled 모두). HTML 문자열을 그대로 넘긴다. */
40
+ onChange?: (html: string) => void;
41
+ /** 비어 있을 때 표시할 placeholder. */
42
+ placeholder?: string;
43
+ /** 읽기 전용. 키 입력·툴바 차단. */
44
+ readOnly?: boolean;
45
+ /** 상단 툴바 숨기기. 본문 영역만 렌더. */
46
+ hideToolbar?: boolean;
47
+ /** 본문 영역의 최소 높이. */
48
+ minHeight?: string;
49
+ /** 본문 영역의 최대 높이. 초과 시 내부 스크롤. */
50
+ maxHeight?: string;
51
+ className?: string;
52
+ "aria-label"?: string;
53
+ }
54
+
55
+
56
+ /**
57
+ * Tiptap 기반 리치 텍스트 에디터.
58
+ *
59
+ * Controlled (value/onChange) · Uncontrolled (defaultValue) 모두 지원. 라우터·외부 상태와
60
+ * 동기화할 게 없는 단일 입력 폼이라면 defaultValue 한 줄로 끝 — useState 불필요.
61
+ *
62
+ * 기본 toolbar 는 StarterKit 의 표준 마크업(헤딩·리스트·인용·코드·링크 등) 을 다룬다.
63
+ */
64
+ export function RichTextEditor({
65
+ value: valueProp,
66
+ defaultValue,
67
+ onChange,
68
+ placeholder,
69
+ readOnly = false,
70
+ hideToolbar = false,
71
+ minHeight,
72
+ maxHeight,
73
+ className,
74
+ "aria-label": ariaLabel = "Rich text editor",
75
+ }: RichTextEditorProps) {
76
+ const isControlled = valueProp !== undefined;
77
+ const editor = useEditor({
78
+ extensions: [
79
+ StarterKit,
80
+ Placeholder.configure({
81
+ placeholder: placeholder ?? "",
82
+ emptyEditorClass: rteIsEmpty,
83
+ }),
84
+ Link.configure({
85
+ openOnClick: false,
86
+ autolink: true,
87
+ HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" },
88
+ }),
89
+ ],
90
+ content: valueProp ?? defaultValue ?? "",
91
+ editable: !readOnly,
92
+ immediatelyRender: false,
93
+ onUpdate: ({ editor }) => {
94
+ onChange?.(editor.getHTML());
95
+ },
96
+ editorProps: {
97
+ attributes: {
98
+ class: rte__content,
99
+ "aria-label": ariaLabel,
100
+ },
101
+ },
102
+ });
103
+
104
+ // controlled 모드에서만 외부 value 를 에디터 doc 에 동기화
105
+ useEffect(() => {
106
+ if (!isControlled) return;
107
+ if (!editor) return;
108
+ if (editor.getHTML() === valueProp) return;
109
+ editor.commands.setContent(valueProp ?? "", { emitUpdate: false });
110
+ }, [isControlled, valueProp, editor]);
111
+
112
+ useEffect(() => {
113
+ editor?.setEditable(!readOnly);
114
+ }, [readOnly, editor]);
115
+
116
+ return (
117
+ <div
118
+ className={cn(rte, className)}
119
+ data-readonly={readOnly || undefined}
120
+ style={
121
+ {
122
+ "--sh-ui-rte-min-height": minHeight,
123
+ "--sh-ui-rte-max-height": maxHeight,
124
+ } as React.CSSProperties
125
+ }
126
+ >
127
+ {!hideToolbar && <Toolbar editor={editor} disabled={readOnly} />}
128
+ <EditorContent editor={editor} className={rte__viewport} />
129
+ </div>
130
+ );
131
+ }
132
+
133
+ interface ToolbarProps {
134
+ editor: Editor | null;
135
+ disabled: boolean;
136
+ }
137
+
138
+ function Toolbar({ editor, disabled }: ToolbarProps) {
139
+ const promptLink = useCallback(() => {
140
+ if (!editor) return;
141
+ const previous = editor.getAttributes("link").href as string | undefined;
142
+ const url = window.prompt("URL", previous ?? "https://");
143
+ if (url === null) return;
144
+ if (url === "") {
145
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
146
+ return;
147
+ }
148
+ const { empty } = editor.state.selection;
149
+ const inLink = editor.isActive("link");
150
+ if (empty && !inLink) {
151
+ // 선택 없이 호출되면 URL 자체를 anchor 텍스트로 삽입 — 빈 링크 마크가 남는 걸 방지
152
+ editor
153
+ .chain()
154
+ .focus()
155
+ .insertContent({
156
+ type: "text",
157
+ text: url,
158
+ marks: [{ type: "link", attrs: { href: url } }],
159
+ })
160
+ .run();
161
+ return;
162
+ }
163
+ editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
164
+ }, [editor]);
165
+
166
+ return (
167
+ <div className={rte__toolbar} role="toolbar" aria-label="Formatting" aria-disabled={disabled || undefined}>
168
+ <ToolbarButton
169
+ editor={editor}
170
+ label="Bold"
171
+ icon={<BoldIcon size={16} />}
172
+ isActive={editor?.isActive("bold")}
173
+ canRun={() => !!editor?.can().toggleBold()}
174
+ run={() => editor?.chain().focus().toggleBold().run()}
175
+ disabled={disabled}
176
+ />
177
+ <ToolbarButton
178
+ editor={editor}
179
+ label="Italic"
180
+ icon={<ItalicIcon size={16} />}
181
+ isActive={editor?.isActive("italic")}
182
+ canRun={() => !!editor?.can().toggleItalic()}
183
+ run={() => editor?.chain().focus().toggleItalic().run()}
184
+ disabled={disabled}
185
+ />
186
+ <ToolbarButton
187
+ editor={editor}
188
+ label="Strikethrough"
189
+ icon={<StrikethroughIcon size={16} />}
190
+ isActive={editor?.isActive("strike")}
191
+ canRun={() => !!editor?.can().toggleStrike()}
192
+ run={() => editor?.chain().focus().toggleStrike().run()}
193
+ disabled={disabled}
194
+ />
195
+ <ToolbarButton
196
+ editor={editor}
197
+ label="Inline code"
198
+ icon={<CodeIcon size={16} />}
199
+ isActive={editor?.isActive("code")}
200
+ canRun={() => !!editor?.can().toggleCode()}
201
+ run={() => editor?.chain().focus().toggleCode().run()}
202
+ disabled={disabled}
203
+ />
204
+
205
+ <ToolbarSeparator />
206
+
207
+ <ToolbarButton
208
+ editor={editor}
209
+ label="Heading 1"
210
+ icon={<Heading1Icon size={16} />}
211
+ isActive={editor?.isActive("heading", { level: 1 })}
212
+ run={() => editor?.chain().focus().toggleHeading({ level: 1 }).run()}
213
+ disabled={disabled}
214
+ />
215
+ <ToolbarButton
216
+ editor={editor}
217
+ label="Heading 2"
218
+ icon={<Heading2Icon size={16} />}
219
+ isActive={editor?.isActive("heading", { level: 2 })}
220
+ run={() => editor?.chain().focus().toggleHeading({ level: 2 }).run()}
221
+ disabled={disabled}
222
+ />
223
+ <ToolbarButton
224
+ editor={editor}
225
+ label="Heading 3"
226
+ icon={<Heading3Icon size={16} />}
227
+ isActive={editor?.isActive("heading", { level: 3 })}
228
+ run={() => editor?.chain().focus().toggleHeading({ level: 3 }).run()}
229
+ disabled={disabled}
230
+ />
231
+
232
+ <ToolbarSeparator />
233
+
234
+ <ToolbarButton
235
+ editor={editor}
236
+ label="Bulleted list"
237
+ icon={<ListIcon size={16} />}
238
+ isActive={editor?.isActive("bulletList")}
239
+ run={() => editor?.chain().focus().toggleBulletList().run()}
240
+ disabled={disabled}
241
+ />
242
+ <ToolbarButton
243
+ editor={editor}
244
+ label="Ordered list"
245
+ icon={<ListOrderedIcon size={16} />}
246
+ isActive={editor?.isActive("orderedList")}
247
+ run={() => editor?.chain().focus().toggleOrderedList().run()}
248
+ disabled={disabled}
249
+ />
250
+ <ToolbarButton
251
+ editor={editor}
252
+ label="Blockquote"
253
+ icon={<QuoteIcon size={16} />}
254
+ isActive={editor?.isActive("blockquote")}
255
+ run={() => editor?.chain().focus().toggleBlockquote().run()}
256
+ disabled={disabled}
257
+ />
258
+ <ToolbarButton
259
+ editor={editor}
260
+ label="Code block"
261
+ icon={<Code2Icon size={16} />}
262
+ isActive={editor?.isActive("codeBlock")}
263
+ run={() => editor?.chain().focus().toggleCodeBlock().run()}
264
+ disabled={disabled}
265
+ />
266
+
267
+ <ToolbarSeparator />
268
+
269
+ <ToolbarButton
270
+ editor={editor}
271
+ label="Link"
272
+ icon={<LinkIcon size={16} />}
273
+ isActive={editor?.isActive("link")}
274
+ run={promptLink}
275
+ disabled={disabled}
276
+ />
277
+ <ToolbarButton
278
+ editor={editor}
279
+ label="Horizontal rule"
280
+ icon={<MinusIcon size={16} />}
281
+ run={() => editor?.chain().focus().setHorizontalRule().run()}
282
+ disabled={disabled}
283
+ />
284
+
285
+ <ToolbarSeparator />
286
+
287
+ <ToolbarButton
288
+ editor={editor}
289
+ label="Undo"
290
+ icon={<Undo2Icon size={16} />}
291
+ canRun={() => !!editor?.can().undo()}
292
+ run={() => editor?.chain().focus().undo().run()}
293
+ disabled={disabled}
294
+ />
295
+ <ToolbarButton
296
+ editor={editor}
297
+ label="Redo"
298
+ icon={<Redo2Icon size={16} />}
299
+ canRun={() => !!editor?.can().redo()}
300
+ run={() => editor?.chain().focus().redo().run()}
301
+ disabled={disabled}
302
+ />
303
+ </div>
304
+ );
305
+ }
306
+
307
+ interface ToolbarButtonProps {
308
+ editor: Editor | null;
309
+ label: string;
310
+ icon: React.ReactNode;
311
+ isActive?: boolean;
312
+ /** false 를 반환하면 비활성. */
313
+ canRun?: () => boolean;
314
+ run: () => void;
315
+ disabled: boolean;
316
+ }
317
+
318
+ function ToolbarButton({
319
+ editor,
320
+ label,
321
+ icon,
322
+ isActive,
323
+ canRun,
324
+ run,
325
+ disabled,
326
+ }: ToolbarButtonProps) {
327
+ const isDisabled = disabled || !editor || (canRun ? !canRun() : false);
328
+ return (
329
+ <button
330
+ type="button"
331
+ className={cn(rte__btn, isActive && "is-active")}
332
+ aria-label={label}
333
+ aria-pressed={isActive || undefined}
334
+ title={label}
335
+ disabled={isDisabled}
336
+ onMouseDown={(e) => {
337
+ e.preventDefault();
338
+ }}
339
+ onClick={run}
340
+ >
341
+ {icon}
342
+ </button>
343
+ );
344
+ }
345
+
346
+ function ToolbarSeparator() {
347
+ return <span aria-hidden className={rte__sep} />;
348
+ }
@@ -0,0 +1,243 @@
1
+ import { style } from "@vanilla-extract/css";
2
+
3
+ export const rte = style({
4
+ display: "flex",
5
+ flexDirection: "column",
6
+ border: "1px solid var(--border)",
7
+ borderRadius: "var(--radius)",
8
+ background: "var(--background)",
9
+ overflow: "hidden",
10
+ transition: "border-color var(--duration-fast)",
11
+ selectors: {
12
+ "&:focus-within": {
13
+ borderColor: "var(--foreground)",
14
+ outline: "var(--border-width-strong) solid var(--foreground)",
15
+ outlineOffset: "2px",
16
+ },
17
+ "&[data-readonly]": {
18
+ background: "var(--background-subtle)",
19
+ },
20
+ },
21
+ });
22
+
23
+ export const rte__toolbar = style({
24
+ display: "flex",
25
+ flexWrap: "wrap",
26
+ alignItems: "center",
27
+ gap: "0.125rem",
28
+ padding: "var(--space-1) var(--space-2)",
29
+ background: "var(--background-muted)",
30
+ borderBottom: "1px solid var(--border)",
31
+ });
32
+
33
+ export const rte__btn = style({
34
+ display: "inline-flex",
35
+ alignItems: "center",
36
+ justifyContent: "center",
37
+ width: "1.875rem",
38
+ height: "1.875rem",
39
+ padding: 0,
40
+ background: "transparent",
41
+ color: "var(--foreground-muted)",
42
+ border: "1px solid transparent",
43
+ borderRadius: "calc(var(--radius) - 2px)",
44
+ cursor: "pointer",
45
+ transition: "color var(--duration-fast),\n background-color var(--duration-fast),\n border-color var(--duration-fast)",
46
+ WebkitTapHighlightColor: "transparent",
47
+ selectors: {
48
+ "&:hover:not(:disabled)": {
49
+ color: "var(--foreground)",
50
+ background: "var(--background)",
51
+ borderColor: "var(--border)",
52
+ },
53
+ "&:focus-visible": {
54
+ outline: "var(--border-width-strong) solid var(--foreground)",
55
+ outlineOffset: "1px",
56
+ },
57
+ "&.is-active": {
58
+ color: "var(--foreground)",
59
+ background: "var(--background)",
60
+ borderColor: "var(--border-strong)",
61
+ },
62
+ "&:disabled": {
63
+ opacity: 0.5,
64
+ cursor: "not-allowed",
65
+ },
66
+ },
67
+ });
68
+
69
+ export const rte__sep = style({
70
+ display: "inline-block",
71
+ width: "1px",
72
+ height: "1.25rem",
73
+ margin: "0 var(--space-1)",
74
+ background: "var(--border)",
75
+ });
76
+
77
+ export const rte__viewport = style({
78
+ display: "flex",
79
+ minHeight: "var(--sh-ui-rte-min-height, 9rem)",
80
+ maxHeight: "var(--sh-ui-rte-max-height, 28rem)",
81
+ overflowY: "auto",
82
+ selectors: {
83
+ "& > .ProseMirror": {
84
+ flex: 1,
85
+ },
86
+ },
87
+ });
88
+
89
+ export const rte__content = style({
90
+ outline: "none",
91
+ padding: "var(--space-3) var(--space-4)",
92
+ fontSize: "0.9375rem",
93
+ lineHeight: 1.65,
94
+ color: "var(--foreground)",
95
+ selectors: {
96
+ "& > :first-child": {
97
+ marginTop: 0,
98
+ },
99
+ "& > :last-child": {
100
+ marginBottom: 0,
101
+ },
102
+ "& p": {
103
+ margin: "0 0 var(--space-3)",
104
+ },
105
+ "& h1": {
106
+ margin: "var(--space-4) 0 var(--space-2)",
107
+ fontWeight: 600,
108
+ lineHeight: 1.3,
109
+ },
110
+ "& h2": {
111
+ margin: "var(--space-4) 0 var(--space-2)",
112
+ fontWeight: 600,
113
+ lineHeight: 1.3,
114
+ },
115
+ "& h3": {
116
+ margin: "var(--space-4) 0 var(--space-2)",
117
+ fontWeight: 600,
118
+ lineHeight: 1.3,
119
+ },
120
+ "& h4": {
121
+ margin: "var(--space-4) 0 var(--space-2)",
122
+ fontWeight: 600,
123
+ lineHeight: 1.3,
124
+ },
125
+ "& h5": {
126
+ margin: "var(--space-4) 0 var(--space-2)",
127
+ fontWeight: 600,
128
+ lineHeight: 1.3,
129
+ },
130
+ "& h6": {
131
+ margin: "var(--space-4) 0 var(--space-2)",
132
+ fontWeight: 600,
133
+ lineHeight: 1.3,
134
+ },
135
+ "& h1": {
136
+ fontSize: "1.5rem",
137
+ },
138
+ "& h2": {
139
+ fontSize: "1.25rem",
140
+ },
141
+ "& h3": {
142
+ fontSize: "1.125rem",
143
+ },
144
+ "& ul": {
145
+ margin: "0 0 var(--space-3)",
146
+ paddingLeft: "var(--space-5)",
147
+ },
148
+ "& ol": {
149
+ margin: "0 0 var(--space-3)",
150
+ paddingLeft: "var(--space-5)",
151
+ },
152
+ "& li": {
153
+ marginBottom: "var(--space-1)",
154
+ },
155
+ "& li > p": {
156
+ margin: 0,
157
+ },
158
+ "& blockquote": {
159
+ margin: "0 0 var(--space-3)",
160
+ padding: "var(--space-2) var(--space-3)",
161
+ borderLeft: "3px solid var(--border-strong)",
162
+ background: "var(--background-subtle)",
163
+ color: "var(--foreground-muted)",
164
+ borderRadius: "0 calc(var(--radius) - 2px) calc(var(--radius) - 2px) 0",
165
+ },
166
+ "& blockquote > :last-child": {
167
+ marginBottom: 0,
168
+ },
169
+ "& code": {
170
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
171
+ fontSize: "0.875em",
172
+ padding: "0.125rem 0.375rem",
173
+ borderRadius: "calc(var(--radius) - 4px)",
174
+ background: "var(--background-muted)",
175
+ color: "var(--foreground)",
176
+ },
177
+ "& pre": {
178
+ margin: "0 0 var(--space-3)",
179
+ padding: "var(--space-3)",
180
+ border: "1px solid var(--border)",
181
+ borderRadius: "var(--radius)",
182
+ background: "var(--background-subtle)",
183
+ overflowX: "auto",
184
+ fontSize: "0.8125rem",
185
+ lineHeight: 1.6,
186
+ },
187
+ "& pre code": {
188
+ padding: 0,
189
+ background: "transparent",
190
+ fontSize: "inherit",
191
+ },
192
+ "& hr": {
193
+ border: 0,
194
+ borderTop: "1px solid var(--border)",
195
+ margin: "var(--space-4) 0",
196
+ },
197
+ "& a": {
198
+ color: "var(--primary)",
199
+ textDecoration: "underline",
200
+ textUnderlineOffset: "2px",
201
+ },
202
+ "& a:hover": {
203
+ textDecorationThickness: "2px",
204
+ },
205
+ "& p.is-editor-empty:first-child::before": {
206
+ content: "attr(data-placeholder)",
207
+ color: "var(--foreground-muted)",
208
+ float: "left",
209
+ pointerEvents: "none",
210
+ height: 0,
211
+ },
212
+ "& .is-editor-empty:first-child::before": {
213
+ content: "attr(data-placeholder)",
214
+ color: "var(--foreground-muted)",
215
+ float: "left",
216
+ pointerEvents: "none",
217
+ height: 0,
218
+ },
219
+ "& del": {
220
+ color: "var(--foreground-muted)",
221
+ },
222
+ "& s": {
223
+ color: "var(--foreground-muted)",
224
+ },
225
+ "& ::selection": {
226
+ background: "var(--background-muted)",
227
+ },
228
+ },
229
+ });
230
+
231
+ export const rteIsEmpty = style({
232
+ });
233
+
234
+ /** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
235
+ export const byKey: Record<string, string> = {
236
+ "rte": rte,
237
+ "rte__toolbar": rte__toolbar,
238
+ "rte__btn": rte__btn,
239
+ "rte__sep": rte__sep,
240
+ "rte__viewport": rte__viewport,
241
+ "rte__content": rte__content,
242
+ "rte__is-empty": rteIsEmpty,
243
+ };