@roottale/cms-renderer-next 0.40.0 → 0.41.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.
- package/CHANGELOG.md +70 -0
- package/dist/chunk-6AFM2UR5.js +1628 -0
- package/dist/chunk-6AFM2UR5.js.map +1 -0
- package/dist/chunk-IOYBEH6E.js +271 -0
- package/dist/chunk-IOYBEH6E.js.map +1 -0
- package/dist/cms-public.css +243 -2
- package/dist/routes.d.ts +13 -2
- package/dist/routes.js +12 -1
- package/dist/routes.js.map +1 -1
- package/dist/server.d.ts +21 -2
- package/dist/server.js +8 -3
- package/dist/templates.css +238 -7
- package/dist/templates.d.ts +414 -94
- package/dist/templates.js +63 -347
- package/dist/templates.js.map +1 -1
- package/dist/theme-css-vars-BiyHBsJG.d.ts +7 -0
- package/dist/validate.d.ts +90 -0
- package/dist/validate.js +415 -0
- package/dist/validate.js.map +1 -0
- package/package.json +10 -4
- package/dist/chunk-3JUIHMPP.js +0 -1057
- package/dist/chunk-3JUIHMPP.js.map +0 -1
package/dist/templates.js
CHANGED
|
@@ -1,336 +1,42 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RootTaleBlogList,
|
|
3
|
-
RootTaleLeadForm
|
|
4
|
-
|
|
3
|
+
RootTaleLeadForm,
|
|
4
|
+
TemplateContact,
|
|
5
|
+
TemplateCtaBanner,
|
|
6
|
+
TemplateFaq,
|
|
7
|
+
TemplateHero,
|
|
8
|
+
TemplateRichText,
|
|
9
|
+
TemplateServices,
|
|
10
|
+
TemplateTrust,
|
|
11
|
+
sectionThemeStyle,
|
|
12
|
+
themeToCssVars
|
|
13
|
+
} from "./chunk-6AFM2UR5.js";
|
|
5
14
|
import "./chunk-FUA5H6JE.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
var SectionBase = z.object({
|
|
26
|
-
/** 안정적 anchor·dedup·preview 매핑용. 페이지 내 유일. */
|
|
27
|
-
id: z.string().optional(),
|
|
28
|
-
/** 빌트인 또는 customSections registry 키. kebab-case. */
|
|
29
|
-
type: z.string(),
|
|
30
|
-
/** 같은 type 의 비주얼 변형. 빌트인 type 은 enum 으로 좁힘. */
|
|
31
|
-
variant: z.string().optional(),
|
|
32
|
-
/** 섹션 스코프 토큰 override (`--rt-*` CSS 변수 → 값). */
|
|
33
|
-
theme: z.record(z.string()).optional(),
|
|
34
|
-
/** 섹션별 직렬화 props. */
|
|
35
|
-
props: z.record(JsonValue).default({})
|
|
36
|
-
});
|
|
37
|
-
var HeroSectionSchema = SectionBase.extend({
|
|
38
|
-
type: z.literal("hero"),
|
|
39
|
-
variant: z.enum(["split", "image", "minimal"]).default("split"),
|
|
40
|
-
props: z.object({
|
|
41
|
-
eyebrow: z.string().optional(),
|
|
42
|
-
/** `\n` 으로 줄바꿈. */
|
|
43
|
-
title: z.string(),
|
|
44
|
-
description: z.string().optional(),
|
|
45
|
-
imageSrc: z.string().optional(),
|
|
46
|
-
imageAlt: z.string().optional(),
|
|
47
|
-
cta: CtaSchema.optional()
|
|
48
|
-
})
|
|
49
|
-
});
|
|
50
|
-
var RichTextSectionSchema = SectionBase.extend({
|
|
51
|
-
type: z.literal("rich-text"),
|
|
52
|
-
props: z.object({
|
|
53
|
-
heading: z.string().optional(),
|
|
54
|
-
/** `\n\n` 으로 문단 분리. */
|
|
55
|
-
body: z.string()
|
|
56
|
-
})
|
|
57
|
-
});
|
|
58
|
-
var ServicesSectionSchema = SectionBase.extend({
|
|
59
|
-
type: z.literal("services"),
|
|
60
|
-
variant: z.enum(["grid", "list"]).default("grid"),
|
|
61
|
-
props: z.object({
|
|
62
|
-
heading: z.string().optional(),
|
|
63
|
-
description: z.string().optional(),
|
|
64
|
-
items: z.array(
|
|
65
|
-
z.object({
|
|
66
|
-
title: z.string(),
|
|
67
|
-
description: z.string().optional(),
|
|
68
|
-
href: z.string().optional()
|
|
69
|
-
})
|
|
70
|
-
).min(1)
|
|
71
|
-
})
|
|
72
|
-
});
|
|
73
|
-
var FaqSectionSchema = SectionBase.extend({
|
|
74
|
-
type: z.literal("faq"),
|
|
75
|
-
props: z.object({
|
|
76
|
-
heading: z.string().optional(),
|
|
77
|
-
items: z.array(z.object({ question: z.string(), answer: z.string() })).min(1),
|
|
78
|
-
/** FAQPage JSON-LD 주입 (기본 true — SEO 3 pillars). 페이지에 FAQ 섹션이 여럿이면 하나만 true 로. */
|
|
79
|
-
jsonLd: z.boolean().default(true)
|
|
80
|
-
})
|
|
81
|
-
});
|
|
82
|
-
var CtaBannerSectionSchema = SectionBase.extend({
|
|
83
|
-
type: z.literal("cta-banner"),
|
|
84
|
-
props: z.object({
|
|
85
|
-
title: z.string(),
|
|
86
|
-
description: z.string().optional(),
|
|
87
|
-
cta: CtaSchema.optional()
|
|
88
|
-
})
|
|
89
|
-
});
|
|
90
|
-
var BlogListSectionSchema = SectionBase.extend({
|
|
91
|
-
type: z.literal("blog-list"),
|
|
92
|
-
props: z.object({
|
|
93
|
-
heading: z.string().optional(),
|
|
94
|
-
limit: z.number().int().positive().optional(),
|
|
95
|
-
/** 포스트 링크 베이스. 기본 `/blog`. */
|
|
96
|
-
blogBasePath: z.string().optional(),
|
|
97
|
-
emptyMessage: z.string().optional()
|
|
98
|
-
})
|
|
99
|
-
});
|
|
100
|
-
var LeadFormSectionSchema = SectionBase.extend({
|
|
101
|
-
type: z.literal("lead-form"),
|
|
102
|
-
props: z.object({
|
|
103
|
-
/** 사이트 자체 server 엔드포인트 (예: `/api/inquiry`). intake API 직접 지정 금지. */
|
|
104
|
-
action: z.string(),
|
|
105
|
-
vertical: z.enum(["consulting", "medical", "tax", "legal"]).optional(),
|
|
106
|
-
heading: z.string().optional(),
|
|
107
|
-
description: z.string().optional(),
|
|
108
|
-
submitLabel: z.string().optional()
|
|
109
|
-
})
|
|
110
|
-
});
|
|
111
|
-
var CustomSectionSchema = SectionBase;
|
|
112
|
-
var BUILTIN_SECTION_SCHEMAS = {
|
|
113
|
-
hero: HeroSectionSchema,
|
|
114
|
-
"rich-text": RichTextSectionSchema,
|
|
115
|
-
services: ServicesSectionSchema,
|
|
116
|
-
faq: FaqSectionSchema,
|
|
117
|
-
"cta-banner": CtaBannerSectionSchema,
|
|
118
|
-
"blog-list": BlogListSectionSchema,
|
|
119
|
-
"lead-form": LeadFormSectionSchema
|
|
120
|
-
};
|
|
121
|
-
var BUILTIN_SECTION_TYPES = Object.keys(
|
|
122
|
-
BUILTIN_SECTION_SCHEMAS
|
|
123
|
-
);
|
|
124
|
-
var SectionSchema = z.object({ type: z.string() }).passthrough().transform((section, ctx) => {
|
|
125
|
-
const schema = BUILTIN_SECTION_SCHEMAS[section.type] ?? CustomSectionSchema;
|
|
126
|
-
const result = schema.safeParse(section);
|
|
127
|
-
if (!result.success) {
|
|
128
|
-
for (const issue of result.error.issues) ctx.addIssue(issue);
|
|
129
|
-
return z.NEVER;
|
|
130
|
-
}
|
|
131
|
-
return result.data;
|
|
132
|
-
});
|
|
133
|
-
var CollectionSchema = z.object({
|
|
134
|
-
/** 안정 식별자 (라우트/가드/파생 키). */
|
|
135
|
-
key: z.string(),
|
|
136
|
-
/** 작성자 카드·nav 라벨. */
|
|
137
|
-
label: z.string(),
|
|
138
|
-
/** 라우트 base path (예: "/notice" | "/blog"). 앞 슬래시 포함. */
|
|
139
|
-
basePath: z.string(),
|
|
140
|
-
/** 레이아웃 레지스트리 키 (board=게시판, grid=카드 그리드, …). 미지 키는 렌더러 fallback. */
|
|
141
|
-
layout: z.string().default("grid"),
|
|
142
|
-
/** 이 스트림에 속하는 category slug 들. */
|
|
143
|
-
categories: z.array(z.string()).default([]),
|
|
144
|
-
/** RSS 피드 포함. */
|
|
145
|
-
feed: z.boolean().default(false),
|
|
146
|
-
/** `${basePath}/categories/{slug}` 아카이브 생성. */
|
|
147
|
-
archives: z.boolean().default(false),
|
|
148
|
-
/** 글별 동적 OG. */
|
|
149
|
-
og: z.boolean().default(false),
|
|
150
|
-
/** 작성 UX: 하위 카테고리 1개 필수(미선택 발행 차단). */
|
|
151
|
-
requireSubcategory: z.boolean().default(false),
|
|
152
|
-
/** 작성 UX: 상단고정 토글 노출. */
|
|
153
|
-
pinning: z.boolean().default(false),
|
|
154
|
-
/** nav/카드 아이콘 키 (선택). */
|
|
155
|
-
icon: z.string().optional()
|
|
156
|
-
});
|
|
157
|
-
var SiteConfigSchema = z.object({
|
|
158
|
-
site: z.object({
|
|
159
|
-
/** canonical origin. 생략 시 소비측이 `SITE_URL` env 등으로 해석. */
|
|
160
|
-
url: z.string().url().optional(),
|
|
161
|
-
title: z.string(),
|
|
162
|
-
description: z.string().optional(),
|
|
163
|
-
locale: z.string().default("ko-KR"),
|
|
164
|
-
og: z.object({ image: z.string().optional() }).optional()
|
|
165
|
-
}),
|
|
166
|
-
/**
|
|
167
|
-
* 콘텐츠 스트림 분리 (ADR-0060). 비우면 단일 /blog(레거시 동작). 선언 순서 =
|
|
168
|
-
* 라우팅 우선순위. `routes.ts` 팩토리에 `collections` 로 전달해 sitemap/feed/
|
|
169
|
-
* 아카이브를 스트림별로 파생.
|
|
170
|
-
*/
|
|
171
|
-
collections: z.array(CollectionSchema).default([]),
|
|
172
|
-
/** 사이트 전역 토큰 override (`--rt-*` CSS 변수 → 값). 비우면 admin /design SSoT. */
|
|
173
|
-
theme: z.record(z.string()).default({}),
|
|
174
|
-
nav: z.array(NavItemSchema).default([]),
|
|
175
|
-
/** path("/", "/services", …) → 섹션 배열. */
|
|
176
|
-
pages: z.record(z.array(SectionSchema))
|
|
177
|
-
});
|
|
178
|
-
function parseSiteConfig(input) {
|
|
179
|
-
return SiteConfigSchema.parse(input);
|
|
180
|
-
}
|
|
181
|
-
function defineSiteConfig(input) {
|
|
182
|
-
return parseSiteConfig(input);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// src/templates/sections.tsx
|
|
186
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
187
|
-
function sectionThemeStyle(theme) {
|
|
188
|
-
if (!theme) return void 0;
|
|
189
|
-
const style = {};
|
|
190
|
-
for (const [key, value] of Object.entries(theme)) {
|
|
191
|
-
if (key.startsWith("--")) style[key] = value;
|
|
192
|
-
}
|
|
193
|
-
return Object.keys(style).length > 0 ? style : void 0;
|
|
194
|
-
}
|
|
195
|
-
function CtaLink({
|
|
196
|
-
cta,
|
|
197
|
-
className
|
|
198
|
-
}) {
|
|
199
|
-
return /* @__PURE__ */ jsx("a", { className: className ?? "rt-tpl-btn rt-tpl-btn--primary", href: cta.href, children: cta.label });
|
|
200
|
-
}
|
|
201
|
-
function TemplateHero(section) {
|
|
202
|
-
const { eyebrow, title, description, imageSrc, imageAlt, cta } = section.props;
|
|
203
|
-
const variant = section.variant;
|
|
204
|
-
const showImage = variant !== "minimal" && imageSrc;
|
|
205
|
-
return /* @__PURE__ */ jsx(
|
|
206
|
-
"section",
|
|
207
|
-
{
|
|
208
|
-
"data-roottale-tpl": "hero",
|
|
209
|
-
"data-variant": variant,
|
|
210
|
-
id: section.id,
|
|
211
|
-
className: "rt-tpl-section rt-tpl-hero",
|
|
212
|
-
style: sectionThemeStyle(section.theme),
|
|
213
|
-
children: /* @__PURE__ */ jsxs("div", { className: "rt-tpl-container rt-tpl-hero-inner", children: [
|
|
214
|
-
/* @__PURE__ */ jsxs("div", { className: "rt-tpl-hero-copy", children: [
|
|
215
|
-
eyebrow ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-hero-eyebrow", children: eyebrow }) : null,
|
|
216
|
-
/* @__PURE__ */ jsx("h1", { className: "rt-tpl-hero-title", children: title.split("\n").map((line, i) => /* @__PURE__ */ jsx("span", { className: "rt-tpl-hero-title-line", children: line }, i)) }),
|
|
217
|
-
description ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-hero-desc", children: description }) : null,
|
|
218
|
-
cta ? /* @__PURE__ */ jsx(CtaLink, { cta }) : null
|
|
219
|
-
] }),
|
|
220
|
-
showImage ? /* @__PURE__ */ jsx("div", { className: "rt-tpl-hero-media", children: /* @__PURE__ */ jsx(
|
|
221
|
-
"img",
|
|
222
|
-
{
|
|
223
|
-
className: "rt-tpl-hero-img",
|
|
224
|
-
src: imageSrc,
|
|
225
|
-
alt: imageAlt ?? "",
|
|
226
|
-
loading: "eager"
|
|
227
|
-
}
|
|
228
|
-
) }) : null
|
|
229
|
-
] })
|
|
230
|
-
}
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
function TemplateRichText(section) {
|
|
234
|
-
const { heading, body } = section.props;
|
|
235
|
-
return /* @__PURE__ */ jsx(
|
|
236
|
-
"section",
|
|
237
|
-
{
|
|
238
|
-
"data-roottale-tpl": "rich-text",
|
|
239
|
-
id: section.id,
|
|
240
|
-
className: "rt-tpl-section rt-tpl-rich-text",
|
|
241
|
-
style: sectionThemeStyle(section.theme),
|
|
242
|
-
children: /* @__PURE__ */ jsxs("div", { className: "rt-tpl-container", children: [
|
|
243
|
-
heading ? /* @__PURE__ */ jsx("h2", { className: "rt-tpl-heading", children: heading }) : null,
|
|
244
|
-
body.split("\n\n").map((para, i) => /* @__PURE__ */ jsx("p", { className: "rt-tpl-rich-text-p", children: para }, i))
|
|
245
|
-
] })
|
|
246
|
-
}
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
function TemplateServices(section) {
|
|
250
|
-
const { heading, description, items } = section.props;
|
|
251
|
-
return /* @__PURE__ */ jsx(
|
|
252
|
-
"section",
|
|
253
|
-
{
|
|
254
|
-
"data-roottale-tpl": "services",
|
|
255
|
-
"data-variant": section.variant,
|
|
256
|
-
id: section.id,
|
|
257
|
-
className: "rt-tpl-section rt-tpl-services",
|
|
258
|
-
style: sectionThemeStyle(section.theme),
|
|
259
|
-
children: /* @__PURE__ */ jsxs("div", { className: "rt-tpl-container", children: [
|
|
260
|
-
heading ? /* @__PURE__ */ jsx("h2", { className: "rt-tpl-heading", children: heading }) : null,
|
|
261
|
-
description ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-section-desc", children: description }) : null,
|
|
262
|
-
/* @__PURE__ */ jsx("ul", { className: "rt-tpl-services-list", children: items.map((item, i) => {
|
|
263
|
-
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
264
|
-
/* @__PURE__ */ jsx("h3", { className: "rt-tpl-services-title", children: item.title }),
|
|
265
|
-
item.description ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-services-desc", children: item.description }) : null
|
|
266
|
-
] });
|
|
267
|
-
return /* @__PURE__ */ jsx("li", { className: "rt-tpl-services-item", children: item.href ? /* @__PURE__ */ jsx("a", { className: "rt-tpl-services-link", href: item.href, children: body }) : body }, i);
|
|
268
|
-
}) })
|
|
269
|
-
] })
|
|
270
|
-
}
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
function TemplateFaq(section) {
|
|
274
|
-
const { heading, items, jsonLd } = section.props;
|
|
275
|
-
return /* @__PURE__ */ jsxs(
|
|
276
|
-
"section",
|
|
277
|
-
{
|
|
278
|
-
"data-roottale-tpl": "faq",
|
|
279
|
-
id: section.id,
|
|
280
|
-
className: "rt-tpl-section rt-tpl-faq",
|
|
281
|
-
style: sectionThemeStyle(section.theme),
|
|
282
|
-
children: [
|
|
283
|
-
/* @__PURE__ */ jsxs("div", { className: "rt-tpl-container", children: [
|
|
284
|
-
heading ? /* @__PURE__ */ jsx("h2", { className: "rt-tpl-heading", children: heading }) : null,
|
|
285
|
-
/* @__PURE__ */ jsx("div", { className: "rt-tpl-faq-list", children: items.map((item, i) => /* @__PURE__ */ jsxs("details", { className: "rt-tpl-faq-item", children: [
|
|
286
|
-
/* @__PURE__ */ jsx("summary", { className: "rt-tpl-faq-q", children: item.question }),
|
|
287
|
-
/* @__PURE__ */ jsx("p", { className: "rt-tpl-faq-a", children: item.answer })
|
|
288
|
-
] }, i)) })
|
|
289
|
-
] }),
|
|
290
|
-
jsonLd ? /* @__PURE__ */ jsx(
|
|
291
|
-
"script",
|
|
292
|
-
{
|
|
293
|
-
type: "application/ld+json",
|
|
294
|
-
dangerouslySetInnerHTML: {
|
|
295
|
-
__html: JSON.stringify({
|
|
296
|
-
"@context": "https://schema.org",
|
|
297
|
-
"@type": "FAQPage",
|
|
298
|
-
mainEntity: items.map((item) => ({
|
|
299
|
-
"@type": "Question",
|
|
300
|
-
name: item.question,
|
|
301
|
-
acceptedAnswer: { "@type": "Answer", text: item.answer }
|
|
302
|
-
}))
|
|
303
|
-
})
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
) : null
|
|
307
|
-
]
|
|
308
|
-
}
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
function TemplateCtaBanner(section) {
|
|
312
|
-
const { title, description, cta } = section.props;
|
|
313
|
-
return /* @__PURE__ */ jsx(
|
|
314
|
-
"section",
|
|
315
|
-
{
|
|
316
|
-
"data-roottale-tpl": "cta-banner",
|
|
317
|
-
id: section.id,
|
|
318
|
-
className: "rt-tpl-section rt-tpl-cta-banner",
|
|
319
|
-
style: sectionThemeStyle(section.theme),
|
|
320
|
-
children: /* @__PURE__ */ jsxs("div", { className: "rt-tpl-container rt-tpl-cta-banner-inner", children: [
|
|
321
|
-
/* @__PURE__ */ jsx("h2", { className: "rt-tpl-cta-banner-title", children: title }),
|
|
322
|
-
description ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-cta-banner-desc", children: description }) : null,
|
|
323
|
-
cta ? /* @__PURE__ */ jsx(CtaLink, { cta }) : null
|
|
324
|
-
] })
|
|
325
|
-
}
|
|
326
|
-
);
|
|
327
|
-
}
|
|
15
|
+
import {
|
|
16
|
+
BUILTIN_SECTION_SCHEMAS,
|
|
17
|
+
BUILTIN_SECTION_TYPES,
|
|
18
|
+
BlogListSectionSchema,
|
|
19
|
+
ContactSectionSchema,
|
|
20
|
+
CtaBannerSectionSchema,
|
|
21
|
+
CustomSectionSchema,
|
|
22
|
+
FaqSectionSchema,
|
|
23
|
+
HeroSectionSchema,
|
|
24
|
+
LeadFormSectionSchema,
|
|
25
|
+
NavItemSchema,
|
|
26
|
+
RichTextSectionSchema,
|
|
27
|
+
SectionSchema,
|
|
28
|
+
ServicesSectionSchema,
|
|
29
|
+
SiteConfigSchema,
|
|
30
|
+
TrustSectionSchema,
|
|
31
|
+
defineSiteConfig,
|
|
32
|
+
parseSiteConfig
|
|
33
|
+
} from "./chunk-IOYBEH6E.js";
|
|
328
34
|
|
|
329
35
|
// src/templates/SiteRenderer.tsx
|
|
330
36
|
import {
|
|
331
|
-
Fragment
|
|
37
|
+
Fragment
|
|
332
38
|
} from "react";
|
|
333
|
-
import { Fragment as
|
|
39
|
+
import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
|
|
334
40
|
function isDev() {
|
|
335
41
|
const env = globalThis.process?.env ?? {};
|
|
336
42
|
return env.NODE_ENV !== "production";
|
|
@@ -341,13 +47,13 @@ function UnknownSection({
|
|
|
341
47
|
}) {
|
|
342
48
|
if (mode === "ignore") return null;
|
|
343
49
|
if (mode === "placeholder") {
|
|
344
|
-
return /* @__PURE__ */
|
|
50
|
+
return /* @__PURE__ */ jsx(
|
|
345
51
|
"section",
|
|
346
52
|
{
|
|
347
53
|
"data-roottale-tpl": "placeholder",
|
|
348
54
|
className: "rt-tpl-section rt-tpl-placeholder",
|
|
349
55
|
"aria-hidden": true,
|
|
350
|
-
children: /* @__PURE__ */
|
|
56
|
+
children: /* @__PURE__ */ jsx("div", { className: "rt-tpl-container", children: /* @__PURE__ */ jsx("div", { className: "rt-tpl-placeholder-box", children: type }) })
|
|
351
57
|
}
|
|
352
58
|
);
|
|
353
59
|
}
|
|
@@ -357,28 +63,28 @@ function UnknownSection({
|
|
|
357
63
|
border: "1px dashed #c00",
|
|
358
64
|
color: "#c00"
|
|
359
65
|
};
|
|
360
|
-
return /* @__PURE__ */
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", { style, children: [
|
|
361
67
|
"\uC54C \uC218 \uC5C6\uB294 \uC139\uC158 type: ",
|
|
362
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ jsx("code", { children: type }),
|
|
363
69
|
" \u2014 SiteRenderer \uC758 customSections \uC5D0 \uB4F1\uB85D\uD558\uC138\uC694."
|
|
364
70
|
] });
|
|
365
71
|
}
|
|
366
72
|
function renderBuiltin(section, ctx) {
|
|
367
73
|
switch (section.type) {
|
|
368
74
|
case "hero":
|
|
369
|
-
return /* @__PURE__ */
|
|
75
|
+
return /* @__PURE__ */ jsx(TemplateHero, { ...section });
|
|
370
76
|
case "rich-text":
|
|
371
|
-
return /* @__PURE__ */
|
|
77
|
+
return /* @__PURE__ */ jsx(TemplateRichText, { ...section });
|
|
372
78
|
case "services":
|
|
373
|
-
return /* @__PURE__ */
|
|
79
|
+
return /* @__PURE__ */ jsx(TemplateServices, { ...section });
|
|
374
80
|
case "faq":
|
|
375
|
-
return /* @__PURE__ */
|
|
81
|
+
return /* @__PURE__ */ jsx(TemplateFaq, { ...section });
|
|
376
82
|
case "cta-banner":
|
|
377
|
-
return /* @__PURE__ */
|
|
83
|
+
return /* @__PURE__ */ jsx(TemplateCtaBanner, { ...section });
|
|
378
84
|
case "blog-list": {
|
|
379
85
|
const s = section;
|
|
380
86
|
const base = s.props.blogBasePath ?? "/blog";
|
|
381
|
-
return /* @__PURE__ */
|
|
87
|
+
return /* @__PURE__ */ jsx(
|
|
382
88
|
"section",
|
|
383
89
|
{
|
|
384
90
|
"data-roottale-tpl": "blog-list",
|
|
@@ -386,9 +92,9 @@ function renderBuiltin(section, ctx) {
|
|
|
386
92
|
id: s.id,
|
|
387
93
|
className: "rt-tpl-section rt-tpl-blog-list",
|
|
388
94
|
style: sectionThemeStyle(s.theme),
|
|
389
|
-
children: /* @__PURE__ */
|
|
390
|
-
s.props.heading ? /* @__PURE__ */
|
|
391
|
-
/* @__PURE__ */
|
|
95
|
+
children: /* @__PURE__ */ jsxs("div", { className: "rt-tpl-container", children: [
|
|
96
|
+
s.props.heading ? /* @__PURE__ */ jsx("h2", { className: "rt-tpl-heading", children: s.props.heading }) : null,
|
|
97
|
+
/* @__PURE__ */ jsx(
|
|
392
98
|
RootTaleBlogList,
|
|
393
99
|
{
|
|
394
100
|
apiKey: ctx.apiKey,
|
|
@@ -404,7 +110,7 @@ function renderBuiltin(section, ctx) {
|
|
|
404
110
|
}
|
|
405
111
|
case "lead-form": {
|
|
406
112
|
const s = section;
|
|
407
|
-
return /* @__PURE__ */
|
|
113
|
+
return /* @__PURE__ */ jsx(
|
|
408
114
|
"section",
|
|
409
115
|
{
|
|
410
116
|
"data-roottale-tpl": "lead-form",
|
|
@@ -412,7 +118,7 @@ function renderBuiltin(section, ctx) {
|
|
|
412
118
|
id: s.id,
|
|
413
119
|
className: "rt-tpl-section rt-tpl-lead-form",
|
|
414
120
|
style: sectionThemeStyle(s.theme),
|
|
415
|
-
children: /* @__PURE__ */
|
|
121
|
+
children: /* @__PURE__ */ jsx("div", { className: "rt-tpl-container", children: /* @__PURE__ */ jsx(
|
|
416
122
|
RootTaleLeadForm,
|
|
417
123
|
{
|
|
418
124
|
action: s.props.action,
|
|
@@ -425,6 +131,10 @@ function renderBuiltin(section, ctx) {
|
|
|
425
131
|
}
|
|
426
132
|
);
|
|
427
133
|
}
|
|
134
|
+
case "trust":
|
|
135
|
+
return /* @__PURE__ */ jsx(TemplateTrust, { ...section });
|
|
136
|
+
case "contact":
|
|
137
|
+
return /* @__PURE__ */ jsx(TemplateContact, { ...section });
|
|
428
138
|
default:
|
|
429
139
|
return void 0;
|
|
430
140
|
}
|
|
@@ -439,8 +149,8 @@ function SiteRenderer(props) {
|
|
|
439
149
|
} = props;
|
|
440
150
|
const config = parseSiteConfig(props.config);
|
|
441
151
|
const sections = config.pages[path] ?? [];
|
|
442
|
-
return /* @__PURE__ */
|
|
443
|
-
sections.length === 0 ? /* @__PURE__ */
|
|
152
|
+
return /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
153
|
+
sections.length === 0 ? /* @__PURE__ */ jsx(
|
|
444
154
|
UnknownSection,
|
|
445
155
|
{
|
|
446
156
|
type: `pages["${path}"] (\uC139\uC158 0\uAC1C)`,
|
|
@@ -451,24 +161,26 @@ function SiteRenderer(props) {
|
|
|
451
161
|
const key = section.id ?? `${section.type}-${i}`;
|
|
452
162
|
const builtin = renderBuiltin(section, { apiKey, baseUrl });
|
|
453
163
|
if (builtin !== void 0) {
|
|
454
|
-
return /* @__PURE__ */
|
|
164
|
+
return /* @__PURE__ */ jsx(Fragment, { children: builtin }, key);
|
|
455
165
|
}
|
|
456
166
|
const Custom = customSections[section.type];
|
|
457
167
|
if (Custom) {
|
|
458
|
-
return /* @__PURE__ */
|
|
168
|
+
return /* @__PURE__ */ jsx(Fragment, { children: Custom({
|
|
459
169
|
id: section.id,
|
|
460
170
|
props: section.props,
|
|
461
171
|
variant: section.variant,
|
|
462
172
|
theme: section.theme
|
|
463
173
|
}) }, key);
|
|
464
174
|
}
|
|
465
|
-
return /* @__PURE__ */
|
|
175
|
+
return /* @__PURE__ */ jsx(UnknownSection, { type: section.type, mode: unknownSection }, key);
|
|
466
176
|
})
|
|
467
177
|
] });
|
|
468
178
|
}
|
|
469
179
|
export {
|
|
180
|
+
BUILTIN_SECTION_SCHEMAS,
|
|
470
181
|
BUILTIN_SECTION_TYPES,
|
|
471
182
|
BlogListSectionSchema,
|
|
183
|
+
ContactSectionSchema,
|
|
472
184
|
CtaBannerSectionSchema,
|
|
473
185
|
CustomSectionSchema,
|
|
474
186
|
FaqSectionSchema,
|
|
@@ -480,13 +192,17 @@ export {
|
|
|
480
192
|
ServicesSectionSchema,
|
|
481
193
|
SiteConfigSchema,
|
|
482
194
|
SiteRenderer,
|
|
195
|
+
TemplateContact,
|
|
483
196
|
TemplateCtaBanner,
|
|
484
197
|
TemplateFaq,
|
|
485
198
|
TemplateHero,
|
|
486
199
|
TemplateRichText,
|
|
487
200
|
TemplateServices,
|
|
201
|
+
TemplateTrust,
|
|
202
|
+
TrustSectionSchema,
|
|
488
203
|
defineSiteConfig,
|
|
489
204
|
parseSiteConfig,
|
|
490
|
-
sectionThemeStyle
|
|
205
|
+
sectionThemeStyle,
|
|
206
|
+
themeToCssVars
|
|
491
207
|
};
|
|
492
208
|
//# sourceMappingURL=templates.js.map
|
package/dist/templates.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/templates/site-config.ts","../src/templates/sections.tsx","../src/templates/SiteRenderer.tsx"],"sourcesContent":["/**\n * `@roottale/cms-renderer-next/templates` — canonical `SiteConfig` 스키마.\n *\n * ADR-0040 P2 + P0.5 RFC §① (`roottale-docs/docs/services/external-site-blockification.md`).\n *\n * 설계 원칙 (codex consult #1 반영):\n * - **계약 = `section type + variant + renderer registry`.** tier 는 스키마 필드가\n * 아니라 문서/거버넌스용 성숙도 메타데이터.\n * - **100% serializable** — 함수/JSX 금지 (Astro parity + admin preview 동일 경로).\n * escape hatch 는 config 안 함수가 아니라 `customSections` registry\n * (`SiteRenderer` 에 빌드 시 type→컴포넌트 주입).\n * - CMS-bound 섹션(`blog-list`/`lead-form`)은 `RootTaleBlogList`/`RootTaleLeadForm`\n * 으로 위임 — templates 는 cms-renderer 코어를 단방향 의존 (RFC §②).\n *\n * RFC §① 대비 구현 정정:\n * - `site.url` 은 optional — 스타터/외부 사이트가 `SITE_URL` env 로 런타임 해석하는\n * 기존 패턴(P4 스타터)과 충돌하므로 config 강제 대신 env fallback 허용.\n * - 빌트인에 `rich-text` 추가 (P4 스타터 기 운영 섹션 — \"동일 패턴\" 군).\n */\nimport { z } from \"zod\";\n\nconst JsonValue: z.ZodType<unknown> = z.lazy(() =>\n z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.array(JsonValue),\n z.record(JsonValue),\n ]),\n);\n\nexport const NavItemSchema = z.object({\n label: z.string(),\n href: z.string(),\n children: z\n .array(z.object({ label: z.string(), href: z.string() }))\n .optional(),\n});\nexport type NavItem = z.infer<typeof NavItemSchema>;\n\nconst CtaSchema = z.object({ label: z.string(), href: z.string() });\nexport type SectionCta = z.infer<typeof CtaSchema>;\n\n/**\n * 모든 섹션 공통 필드. `theme` 는 섹션 스코프 CSS 변수 override —\n * key 는 `--rt-*` custom property 이름, 값은 CSS 값.\n * 예) `{ \"--rt-color-primary\": \"#0B1F49\" }`\n */\nconst SectionBase = z.object({\n /** 안정적 anchor·dedup·preview 매핑용. 페이지 내 유일. */\n id: z.string().optional(),\n /** 빌트인 또는 customSections registry 키. kebab-case. */\n type: z.string(),\n /** 같은 type 의 비주얼 변형. 빌트인 type 은 enum 으로 좁힘. */\n variant: z.string().optional(),\n /** 섹션 스코프 토큰 override (`--rt-*` CSS 변수 → 값). */\n theme: z.record(z.string()).optional(),\n /** 섹션별 직렬화 props. */\n props: z.record(JsonValue).default({}),\n});\n\n// ── 빌트인 섹션 (props 좁힘 — 런타임 + 타입) ────────────────────────────────\n\nexport const HeroSectionSchema = SectionBase.extend({\n type: z.literal(\"hero\"),\n variant: z.enum([\"split\", \"image\", \"minimal\"]).default(\"split\"),\n props: z.object({\n eyebrow: z.string().optional(),\n /** `\\n` 으로 줄바꿈. */\n title: z.string(),\n description: z.string().optional(),\n imageSrc: z.string().optional(),\n imageAlt: z.string().optional(),\n cta: CtaSchema.optional(),\n }),\n});\n\nexport const RichTextSectionSchema = SectionBase.extend({\n type: z.literal(\"rich-text\"),\n props: z.object({\n heading: z.string().optional(),\n /** `\\n\\n` 으로 문단 분리. */\n body: z.string(),\n }),\n});\n\nexport const ServicesSectionSchema = SectionBase.extend({\n type: z.literal(\"services\"),\n variant: z.enum([\"grid\", \"list\"]).default(\"grid\"),\n props: z.object({\n heading: z.string().optional(),\n description: z.string().optional(),\n items: z\n .array(\n z.object({\n title: z.string(),\n description: z.string().optional(),\n href: z.string().optional(),\n }),\n )\n .min(1),\n }),\n});\n\nexport const FaqSectionSchema = SectionBase.extend({\n type: z.literal(\"faq\"),\n props: z.object({\n heading: z.string().optional(),\n items: z\n .array(z.object({ question: z.string(), answer: z.string() }))\n .min(1),\n /** FAQPage JSON-LD 주입 (기본 true — SEO 3 pillars). 페이지에 FAQ 섹션이 여럿이면 하나만 true 로. */\n jsonLd: z.boolean().default(true),\n }),\n});\n\nexport const CtaBannerSectionSchema = SectionBase.extend({\n type: z.literal(\"cta-banner\"),\n props: z.object({\n title: z.string(),\n description: z.string().optional(),\n cta: CtaSchema.optional(),\n }),\n});\n\n/** → `RootTaleBlogList` 위임 (cms-bound). */\nexport const BlogListSectionSchema = SectionBase.extend({\n type: z.literal(\"blog-list\"),\n props: z.object({\n heading: z.string().optional(),\n limit: z.number().int().positive().optional(),\n /** 포스트 링크 베이스. 기본 `/blog`. */\n blogBasePath: z.string().optional(),\n emptyMessage: z.string().optional(),\n }),\n});\n\n/** → `RootTaleLeadForm` 위임 (cms-bound). */\nexport const LeadFormSectionSchema = SectionBase.extend({\n type: z.literal(\"lead-form\"),\n props: z.object({\n /** 사이트 자체 server 엔드포인트 (예: `/api/inquiry`). intake API 직접 지정 금지. */\n action: z.string(),\n vertical: z.enum([\"consulting\", \"medical\", \"tax\", \"legal\"]).optional(),\n heading: z.string().optional(),\n description: z.string().optional(),\n submitLabel: z.string().optional(),\n }),\n});\n\n/** customSections 용 fallback — `type` 자유, props 는 직렬화만 강제. */\nexport const CustomSectionSchema = SectionBase;\n\nconst BUILTIN_SECTION_SCHEMAS: Record<string, z.ZodTypeAny> = {\n hero: HeroSectionSchema,\n \"rich-text\": RichTextSectionSchema,\n services: ServicesSectionSchema,\n faq: FaqSectionSchema,\n \"cta-banner\": CtaBannerSectionSchema,\n \"blog-list\": BlogListSectionSchema,\n \"lead-form\": LeadFormSectionSchema,\n};\n\n/** 빌트인 type 목록 (renderer registry 와 1:1). */\nexport const BUILTIN_SECTION_TYPES = Object.keys(\n BUILTIN_SECTION_SCHEMAS,\n) as readonly string[];\n\nexport type HeroSection = z.output<typeof HeroSectionSchema>;\nexport type RichTextSection = z.output<typeof RichTextSectionSchema>;\nexport type ServicesSection = z.output<typeof ServicesSectionSchema>;\nexport type FaqSection = z.output<typeof FaqSectionSchema>;\nexport type CtaBannerSection = z.output<typeof CtaBannerSectionSchema>;\nexport type BlogListSection = z.output<typeof BlogListSectionSchema>;\nexport type LeadFormSection = z.output<typeof LeadFormSectionSchema>;\nexport type CustomSection = z.output<typeof CustomSectionSchema>;\n\n/** 파싱 결과 섹션 (defaults 적용 후). */\nexport type Section =\n | HeroSection\n | RichTextSection\n | ServicesSection\n | FaqSection\n | CtaBannerSection\n | BlogListSection\n | LeadFormSection\n | CustomSection;\n\n/** config 작성용 섹션 (defaults 생략 가능). `site.config.ts` 에서 이 타입 사용. */\nexport type SectionInput =\n | z.input<typeof HeroSectionSchema>\n | z.input<typeof RichTextSectionSchema>\n | z.input<typeof ServicesSectionSchema>\n | z.input<typeof FaqSectionSchema>\n | z.input<typeof CtaBannerSectionSchema>\n | z.input<typeof BlogListSectionSchema>\n | z.input<typeof LeadFormSectionSchema>\n | z.input<typeof CustomSectionSchema>;\n\n/**\n * 섹션 스키마 — 빌트인 type 이면 해당 스키마로 *강제* 검증 (union fallthrough 로\n * 빌트인 오타이핑이 CustomSection 으로 조용히 통과하는 것을 차단), 미지 type 은\n * CustomSection 으로 통과 (렌더러가 customSections registry 로 해석).\n */\nexport const SectionSchema: z.ZodType<Section, z.ZodTypeDef, SectionInput> = z\n .object({ type: z.string() })\n .passthrough()\n .transform((section, ctx) => {\n const schema = BUILTIN_SECTION_SCHEMAS[section.type] ?? CustomSectionSchema;\n const result = schema.safeParse(section);\n if (!result.success) {\n for (const issue of result.error.issues) ctx.addIssue(issue);\n return z.NEVER;\n }\n return result.data as Section;\n }) as unknown as z.ZodType<Section, z.ZodTypeDef, SectionInput>;\n\n/**\n * 콘텐츠 Collection (ADR-0060) — 같은 글 풀(post)을 스트림으로 분리(공지 게시판 /\n * 블로그 그리드 등). `routes.ts` 의 `RouteCollection` 의 타입드·직렬화 상위집합 —\n * 한 category slug 는 정확히 하나의 collection 에 속해야 한다(다대일 = 라우팅 모호).\n * `layout` 은 레지스트리 키(open string) — 신규 레이아웃 추가 시 스키마 불변.\n * 선언 순서 = 라우팅 우선순위(`resolvePostCollection`).\n */\nexport const CollectionSchema = z.object({\n /** 안정 식별자 (라우트/가드/파생 키). */\n key: z.string(),\n /** 작성자 카드·nav 라벨. */\n label: z.string(),\n /** 라우트 base path (예: \"/notice\" | \"/blog\"). 앞 슬래시 포함. */\n basePath: z.string(),\n /** 레이아웃 레지스트리 키 (board=게시판, grid=카드 그리드, …). 미지 키는 렌더러 fallback. */\n layout: z.string().default(\"grid\"),\n /** 이 스트림에 속하는 category slug 들. */\n categories: z.array(z.string()).default([]),\n /** RSS 피드 포함. */\n feed: z.boolean().default(false),\n /** `${basePath}/categories/{slug}` 아카이브 생성. */\n archives: z.boolean().default(false),\n /** 글별 동적 OG. */\n og: z.boolean().default(false),\n /** 작성 UX: 하위 카테고리 1개 필수(미선택 발행 차단). */\n requireSubcategory: z.boolean().default(false),\n /** 작성 UX: 상단고정 토글 노출. */\n pinning: z.boolean().default(false),\n /** nav/카드 아이콘 키 (선택). */\n icon: z.string().optional(),\n});\nexport type Collection = z.output<typeof CollectionSchema>;\n\nexport const SiteConfigSchema = z.object({\n site: z.object({\n /** canonical origin. 생략 시 소비측이 `SITE_URL` env 등으로 해석. */\n url: z.string().url().optional(),\n title: z.string(),\n description: z.string().optional(),\n locale: z.string().default(\"ko-KR\"),\n og: z.object({ image: z.string().optional() }).optional(),\n }),\n /**\n * 콘텐츠 스트림 분리 (ADR-0060). 비우면 단일 /blog(레거시 동작). 선언 순서 =\n * 라우팅 우선순위. `routes.ts` 팩토리에 `collections` 로 전달해 sitemap/feed/\n * 아카이브를 스트림별로 파생.\n */\n collections: z.array(CollectionSchema).default([]),\n /** 사이트 전역 토큰 override (`--rt-*` CSS 변수 → 값). 비우면 admin /design SSoT. */\n theme: z.record(z.string()).default({}),\n nav: z.array(NavItemSchema).default([]),\n /** path(\"/\", \"/services\", …) → 섹션 배열. */\n pages: z.record(z.array(SectionSchema)),\n});\n\nexport type SiteConfig = z.output<typeof SiteConfigSchema>;\n/** config 작성용 (defaults 생략 가능). */\nexport type SiteConfigInput = Omit<\n z.input<typeof SiteConfigSchema>,\n \"pages\"\n> & { pages: Record<string, SectionInput[]> };\n\n/** config 검증 + defaults 적용. 실패 시 ZodError throw (빌드 타임 fail-fast 용). */\nexport function parseSiteConfig(input: unknown): SiteConfig {\n return SiteConfigSchema.parse(input);\n}\n\n/**\n * `site.config.ts` 작성 헬퍼 — 타입 추론 + 즉시 검증.\n *\n * ```ts\n * import { defineSiteConfig } from \"@roottale/cms-renderer-next/templates\";\n * export default defineSiteConfig({ site: { title: \"…\" }, pages: { \"/\": [ … ] } });\n * ```\n */\nexport function defineSiteConfig(input: SiteConfigInput): SiteConfig {\n return parseSiteConfig(input);\n}\n","/**\n * 빌트인 섹션 템플릿 — MUI-free, 0 JS RSC (ADR-0040 P2).\n *\n * 클래스는 전부 `rt-tpl-*` prefix, 스코프는 섹션 루트의 `data-roottale-tpl`\n * (templates.css 가 `[data-roottale-tpl] :where(…)` 로 zero-specificity 적용 —\n * customer 는 plain class / Tailwind utility 로 자유 override, cms-public.css\n * 와 동일 계약).\n *\n * 토큰은 `--rt-*` CSS 변수 + 정적 fallback — `@roottale/tokens`(private) 미의존\n * (RFC §②). admin /design 값은 `RootTaleThemeProvider` 가 주입.\n */\nimport type { CSSProperties, ReactElement } from \"react\";\n\nimport type {\n CtaBannerSection,\n FaqSection,\n HeroSection,\n RichTextSection,\n SectionCta,\n ServicesSection,\n} from \"./site-config.js\";\n\n/** 섹션 `theme`(--rt-* 변수 override)를 inline style 로. */\nexport function sectionThemeStyle(\n theme: Record<string, string> | undefined,\n): CSSProperties | undefined {\n if (!theme) return undefined;\n const style: Record<string, string> = {};\n for (const [key, value] of Object.entries(theme)) {\n if (key.startsWith(\"--\")) style[key] = value;\n }\n return Object.keys(style).length > 0 ? (style as CSSProperties) : undefined;\n}\n\nfunction CtaLink({\n cta,\n className,\n}: {\n cta: SectionCta;\n className?: string;\n}): ReactElement {\n return (\n <a className={className ?? \"rt-tpl-btn rt-tpl-btn--primary\"} href={cta.href}>\n {cta.label}\n </a>\n );\n}\n\n/** 빌트인 `hero`. variant: split(기본) / image / minimal. */\nexport function TemplateHero(section: HeroSection): ReactElement {\n const { eyebrow, title, description, imageSrc, imageAlt, cta } =\n section.props;\n const variant = section.variant;\n const showImage = variant !== \"minimal\" && imageSrc;\n return (\n <section\n data-roottale-tpl=\"hero\"\n data-variant={variant}\n id={section.id}\n className=\"rt-tpl-section rt-tpl-hero\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container rt-tpl-hero-inner\">\n <div className=\"rt-tpl-hero-copy\">\n {eyebrow ? <p className=\"rt-tpl-hero-eyebrow\">{eyebrow}</p> : null}\n <h1 className=\"rt-tpl-hero-title\">\n {title.split(\"\\n\").map((line, i) => (\n <span key={i} className=\"rt-tpl-hero-title-line\">\n {line}\n </span>\n ))}\n </h1>\n {description ? (\n <p className=\"rt-tpl-hero-desc\">{description}</p>\n ) : null}\n {cta ? <CtaLink cta={cta} /> : null}\n </div>\n {showImage ? (\n <div className=\"rt-tpl-hero-media\">\n <img\n className=\"rt-tpl-hero-img\"\n src={imageSrc}\n alt={imageAlt ?? \"\"}\n loading=\"eager\"\n />\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n\n/** 빌트인 `rich-text`. 문단은 `\\n\\n` 분리. */\nexport function TemplateRichText(section: RichTextSection): ReactElement {\n const { heading, body } = section.props;\n return (\n <section\n data-roottale-tpl=\"rich-text\"\n id={section.id}\n className=\"rt-tpl-section rt-tpl-rich-text\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container\">\n {heading ? <h2 className=\"rt-tpl-heading\">{heading}</h2> : null}\n {body.split(\"\\n\\n\").map((para, i) => (\n <p key={i} className=\"rt-tpl-rich-text-p\">\n {para}\n </p>\n ))}\n </div>\n </section>\n );\n}\n\n/** 빌트인 `services`. variant: grid(기본) / list. */\nexport function TemplateServices(section: ServicesSection): ReactElement {\n const { heading, description, items } = section.props;\n return (\n <section\n data-roottale-tpl=\"services\"\n data-variant={section.variant}\n id={section.id}\n className=\"rt-tpl-section rt-tpl-services\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container\">\n {heading ? <h2 className=\"rt-tpl-heading\">{heading}</h2> : null}\n {description ? (\n <p className=\"rt-tpl-section-desc\">{description}</p>\n ) : null}\n <ul className=\"rt-tpl-services-list\">\n {items.map((item, i) => {\n const body = (\n <>\n <h3 className=\"rt-tpl-services-title\">{item.title}</h3>\n {item.description ? (\n <p className=\"rt-tpl-services-desc\">{item.description}</p>\n ) : null}\n </>\n );\n return (\n <li key={i} className=\"rt-tpl-services-item\">\n {item.href ? (\n <a className=\"rt-tpl-services-link\" href={item.href}>\n {body}\n </a>\n ) : (\n body\n )}\n </li>\n );\n })}\n </ul>\n </div>\n </section>\n );\n}\n\n/** 빌트인 `faq`. `<details>` 0 JS + FAQPage JSON-LD (기본 on). */\nexport function TemplateFaq(section: FaqSection): ReactElement {\n const { heading, items, jsonLd } = section.props;\n return (\n <section\n data-roottale-tpl=\"faq\"\n id={section.id}\n className=\"rt-tpl-section rt-tpl-faq\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container\">\n {heading ? <h2 className=\"rt-tpl-heading\">{heading}</h2> : null}\n <div className=\"rt-tpl-faq-list\">\n {items.map((item, i) => (\n <details key={i} className=\"rt-tpl-faq-item\">\n <summary className=\"rt-tpl-faq-q\">{item.question}</summary>\n <p className=\"rt-tpl-faq-a\">{item.answer}</p>\n </details>\n ))}\n </div>\n </div>\n {jsonLd ? (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify({\n \"@context\": \"https://schema.org\",\n \"@type\": \"FAQPage\",\n mainEntity: items.map((item) => ({\n \"@type\": \"Question\",\n name: item.question,\n acceptedAnswer: { \"@type\": \"Answer\", text: item.answer },\n })),\n }),\n }}\n />\n ) : null}\n </section>\n );\n}\n\n/** 빌트인 `cta-banner`. */\nexport function TemplateCtaBanner(section: CtaBannerSection): ReactElement {\n const { title, description, cta } = section.props;\n return (\n <section\n data-roottale-tpl=\"cta-banner\"\n id={section.id}\n className=\"rt-tpl-section rt-tpl-cta-banner\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container rt-tpl-cta-banner-inner\">\n <h2 className=\"rt-tpl-cta-banner-title\">{title}</h2>\n {description ? (\n <p className=\"rt-tpl-cta-banner-desc\">{description}</p>\n ) : null}\n {cta ? <CtaLink cta={cta} /> : null}\n </div>\n </section>\n );\n}\n","/**\n * `SiteRenderer` — `SiteConfig.pages[path]` 섹션 배열을 registry 로 렌더.\n *\n * ADR-0040 P2 + P0.5 RFC §①:\n * - 빌트인 registry + 클라 주입 `customSections` 머지. **빌트인이 우선** —\n * 클라가 빌트인 type 을 가로채 계약이 갈라지는 것 방지 (고유 섹션은 고유\n * type 으로).\n * - **render-prop 금지**: 임의 JSX escape hatch 는 customSections registry 로만.\n * - **admin preview parity**: admin draft/preview 도 동일 `SiteRenderer` + 동일\n * 빌트인 registry 를 탄다. customSections 는 클라 repo 자산 — preview 에선\n * 미주입 → `unknownSection: \"placeholder\"` 로 skeleton 표시.\n * - cms-bound 섹션(blog-list/lead-form)은 `RootTaleBlogList`/`RootTaleLeadForm`\n * 위임 (단방향: templates → renderer 코어).\n */\nimport {\n Fragment,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\n\nimport { RootTaleLeadForm } from \"../LeadForm.js\";\nimport { RootTaleBlogList } from \"../server.js\";\nimport {\n TemplateCtaBanner,\n TemplateFaq,\n TemplateHero,\n TemplateRichText,\n TemplateServices,\n sectionThemeStyle,\n} from \"./sections.js\";\nimport {\n type BlogListSection,\n type CtaBannerSection,\n type FaqSection,\n type HeroSection,\n type LeadFormSection,\n type RichTextSection,\n type Section,\n type ServicesSection,\n type SiteConfig,\n type SiteConfigInput,\n parseSiteConfig,\n} from \"./site-config.js\";\n\n/** customSections registry 컴포넌트 계약 (RFC §①). */\nexport type SectionComponent = (p: {\n id?: string;\n props: Record<string, unknown>;\n variant?: string;\n theme?: Record<string, string>;\n}) => ReactNode;\n\nexport interface SiteRendererProps {\n /** `defineSiteConfig()` 결과 또는 raw config (내부에서 검증). */\n config: SiteConfig | SiteConfigInput;\n /** 현재 페이지 경로 — `config.pages` 의 key (\"/\", \"/services\", …). */\n path: string;\n /** cms-bound 섹션(blog-list/lead-form 의 theme fetch) 위임용. 서버 전용. */\n apiKey: string;\n /** self-host 등 API base override (`ROOTTALE_API_BASE`). */\n baseUrl?: string;\n /** 클라 repo 고유 섹션 registry (escape hatch). 빌트인 type 은 가로챌 수 없음. */\n customSections?: Record<string, SectionComponent>;\n /**\n * registry 에 없는 type 처리. 기본 \"warn-dev\" = dev 에서 경고 박스, prod 에서\n * 생략. admin preview 는 \"placeholder\" (customSections 미주입 환경).\n */\n unknownSection?: \"warn-dev\" | \"placeholder\" | \"ignore\";\n}\n\nfunction isDev(): boolean {\n // dts 빌드는 node types 없이 돈다 — process 는 globalThis 경유로 안전 접근.\n const env =\n (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env ?? {};\n return env.NODE_ENV !== \"production\";\n}\n\nfunction UnknownSection({\n type,\n mode,\n}: {\n type: string;\n mode: \"warn-dev\" | \"placeholder\" | \"ignore\";\n}): ReactElement | null {\n if (mode === \"ignore\") return null;\n if (mode === \"placeholder\") {\n return (\n <section\n data-roottale-tpl=\"placeholder\"\n className=\"rt-tpl-section rt-tpl-placeholder\"\n aria-hidden\n >\n <div className=\"rt-tpl-container\">\n <div className=\"rt-tpl-placeholder-box\">{type}</div>\n </div>\n </section>\n );\n }\n if (!isDev()) return null;\n const style: CSSProperties = {\n padding: \"1rem\",\n border: \"1px dashed #c00\",\n color: \"#c00\",\n };\n return (\n <div style={style}>\n 알 수 없는 섹션 type: <code>{type}</code> — SiteRenderer 의 customSections\n 에 등록하세요.\n </div>\n );\n}\n\nfunction renderBuiltin(\n section: Section,\n ctx: { apiKey: string; baseUrl?: string },\n): ReactNode | undefined {\n switch (section.type) {\n case \"hero\":\n return <TemplateHero {...(section as HeroSection)} />;\n case \"rich-text\":\n return <TemplateRichText {...(section as RichTextSection)} />;\n case \"services\":\n return <TemplateServices {...(section as ServicesSection)} />;\n case \"faq\":\n return <TemplateFaq {...(section as FaqSection)} />;\n case \"cta-banner\":\n return <TemplateCtaBanner {...(section as CtaBannerSection)} />;\n case \"blog-list\": {\n const s = section as BlogListSection;\n const base = s.props.blogBasePath ?? \"/blog\";\n return (\n <section\n data-roottale-tpl=\"blog-list\"\n data-roottale-cms=\"list\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-blog-list\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n {s.props.heading ? (\n <h2 className=\"rt-tpl-heading\">{s.props.heading}</h2>\n ) : null}\n <RootTaleBlogList\n apiKey={ctx.apiKey}\n baseUrl={ctx.baseUrl}\n limit={s.props.limit}\n emptyMessage={s.props.emptyMessage}\n postHref={(post) => `${base}/${post.slug}`}\n />\n </div>\n </section>\n );\n }\n case \"lead-form\": {\n const s = section as LeadFormSection;\n return (\n <section\n data-roottale-tpl=\"lead-form\"\n data-roottale-cms=\"lead-form\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-lead-form\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n <RootTaleLeadForm\n action={s.props.action}\n vertical={s.props.vertical}\n heading={s.props.heading}\n description={s.props.description}\n submitLabel={s.props.submitLabel}\n />\n </div>\n </section>\n );\n }\n default:\n return undefined;\n }\n}\n\n/**\n * 한 페이지의 섹션 배열을 순서대로 렌더.\n *\n * ```tsx\n * // app/services/page.tsx\n * import config from \"@/site.config\";\n * import { SiteRenderer } from \"@roottale/cms-renderer-next/templates\";\n *\n * export default function ServicesPage() {\n * return (\n * <SiteRenderer config={config} path=\"/services\"\n * apiKey={process.env.ROOTTALE_API_KEY!} />\n * );\n * }\n * ```\n */\nexport function SiteRenderer(props: SiteRendererProps): ReactElement {\n const {\n path,\n apiKey,\n baseUrl,\n customSections = {},\n unknownSection = \"warn-dev\",\n } = props;\n const config = parseSiteConfig(props.config);\n const sections = config.pages[path] ?? [];\n return (\n <>\n {sections.length === 0 ? (\n <UnknownSection\n type={`pages[\"${path}\"] (섹션 0개)`}\n mode={unknownSection}\n />\n ) : null}\n {sections.map((section, i) => {\n const key = section.id ?? `${section.type}-${i}`;\n const builtin = renderBuiltin(section, { apiKey, baseUrl });\n if (builtin !== undefined) {\n return <Fragment key={key}>{builtin}</Fragment>;\n }\n const Custom = customSections[section.type];\n if (Custom) {\n return (\n <Fragment key={key}>\n {Custom({\n id: section.id,\n props: section.props as Record<string, unknown>,\n variant: section.variant,\n theme: section.theme,\n })}\n </Fragment>\n );\n }\n return (\n <UnknownSection key={key} type={section.type} mode={unknownSection} />\n );\n })}\n </>\n );\n}\n"],"mappings":"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","names":["Fragment","Fragment","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/templates/SiteRenderer.tsx"],"sourcesContent":["/**\n * `SiteRenderer` — `SiteConfig.pages[path]` 섹션 배열을 registry 로 렌더.\n *\n * ADR-0040 P2 + P0.5 RFC §①:\n * - 빌트인 registry + 클라 주입 `customSections` 머지. **빌트인이 우선** —\n * 클라가 빌트인 type 을 가로채 계약이 갈라지는 것 방지 (고유 섹션은 고유\n * type 으로).\n * - **render-prop 금지**: 임의 JSX escape hatch 는 customSections registry 로만.\n * - **admin preview parity**: admin draft/preview 도 동일 `SiteRenderer` + 동일\n * 빌트인 registry 를 탄다. customSections 는 클라 repo 자산 — preview 에선\n * 미주입 → `unknownSection: \"placeholder\"` 로 skeleton 표시.\n * - cms-bound 섹션(blog-list/lead-form)은 `RootTaleBlogList`/`RootTaleLeadForm`\n * 위임 (단방향: templates → renderer 코어).\n */\nimport {\n Fragment,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\n\nimport { RootTaleLeadForm } from \"../LeadForm.js\";\nimport { RootTaleBlogList } from \"../server.js\";\nimport {\n TemplateContact,\n TemplateCtaBanner,\n TemplateFaq,\n TemplateHero,\n TemplateRichText,\n TemplateServices,\n TemplateTrust,\n sectionThemeStyle,\n} from \"./sections.js\";\nimport {\n type BlogListSection,\n type ContactSection,\n type CtaBannerSection,\n type FaqSection,\n type HeroSection,\n type LeadFormSection,\n type RichTextSection,\n type Section,\n type ServicesSection,\n type SiteConfig,\n type SiteConfigInput,\n type TrustSection,\n parseSiteConfig,\n} from \"./site-config.js\";\n\n/** customSections registry 컴포넌트 계약 (RFC §①). */\nexport type SectionComponent = (p: {\n id?: string;\n props: Record<string, unknown>;\n variant?: string;\n theme?: Record<string, string>;\n}) => ReactNode;\n\nexport interface SiteRendererProps {\n /** `defineSiteConfig()` 결과 또는 raw config (내부에서 검증). */\n config: SiteConfig | SiteConfigInput;\n /** 현재 페이지 경로 — `config.pages` 의 key (\"/\", \"/services\", …). */\n path: string;\n /** cms-bound 섹션(blog-list/lead-form 의 theme fetch) 위임용. 서버 전용. */\n apiKey: string;\n /** self-host 등 API base override (`ROOTTALE_API_BASE`). */\n baseUrl?: string;\n /** 클라 repo 고유 섹션 registry (escape hatch). 빌트인 type 은 가로챌 수 없음. */\n customSections?: Record<string, SectionComponent>;\n /**\n * registry 에 없는 type 처리. 기본 \"warn-dev\" = dev 에서 경고 박스, prod 에서\n * 생략. admin preview 는 \"placeholder\" (customSections 미주입 환경).\n */\n unknownSection?: \"warn-dev\" | \"placeholder\" | \"ignore\";\n}\n\nfunction isDev(): boolean {\n // dts 빌드는 node types 없이 돈다 — process 는 globalThis 경유로 안전 접근.\n const env =\n (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env ?? {};\n return env.NODE_ENV !== \"production\";\n}\n\nfunction UnknownSection({\n type,\n mode,\n}: {\n type: string;\n mode: \"warn-dev\" | \"placeholder\" | \"ignore\";\n}): ReactElement | null {\n if (mode === \"ignore\") return null;\n if (mode === \"placeholder\") {\n return (\n <section\n data-roottale-tpl=\"placeholder\"\n className=\"rt-tpl-section rt-tpl-placeholder\"\n aria-hidden\n >\n <div className=\"rt-tpl-container\">\n <div className=\"rt-tpl-placeholder-box\">{type}</div>\n </div>\n </section>\n );\n }\n if (!isDev()) return null;\n const style: CSSProperties = {\n padding: \"1rem\",\n border: \"1px dashed #c00\",\n color: \"#c00\",\n };\n return (\n <div style={style}>\n 알 수 없는 섹션 type: <code>{type}</code> — SiteRenderer 의 customSections\n 에 등록하세요.\n </div>\n );\n}\n\nfunction renderBuiltin(\n section: Section,\n ctx: { apiKey: string; baseUrl?: string },\n): ReactNode | undefined {\n switch (section.type) {\n case \"hero\":\n return <TemplateHero {...(section as HeroSection)} />;\n case \"rich-text\":\n return <TemplateRichText {...(section as RichTextSection)} />;\n case \"services\":\n return <TemplateServices {...(section as ServicesSection)} />;\n case \"faq\":\n return <TemplateFaq {...(section as FaqSection)} />;\n case \"cta-banner\":\n return <TemplateCtaBanner {...(section as CtaBannerSection)} />;\n case \"blog-list\": {\n const s = section as BlogListSection;\n const base = s.props.blogBasePath ?? \"/blog\";\n return (\n <section\n data-roottale-tpl=\"blog-list\"\n data-roottale-cms=\"list\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-blog-list\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n {s.props.heading ? (\n <h2 className=\"rt-tpl-heading\">{s.props.heading}</h2>\n ) : null}\n <RootTaleBlogList\n apiKey={ctx.apiKey}\n baseUrl={ctx.baseUrl}\n limit={s.props.limit}\n emptyMessage={s.props.emptyMessage}\n postHref={(post) => `${base}/${post.slug}`}\n />\n </div>\n </section>\n );\n }\n case \"lead-form\": {\n const s = section as LeadFormSection;\n return (\n <section\n data-roottale-tpl=\"lead-form\"\n data-roottale-cms=\"lead-form\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-lead-form\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n <RootTaleLeadForm\n action={s.props.action}\n vertical={s.props.vertical}\n heading={s.props.heading}\n description={s.props.description}\n submitLabel={s.props.submitLabel}\n />\n </div>\n </section>\n );\n }\n case \"trust\":\n return <TemplateTrust {...(section as TrustSection)} />;\n case \"contact\":\n return <TemplateContact {...(section as ContactSection)} />;\n default:\n return undefined;\n }\n}\n\n/**\n * 한 페이지의 섹션 배열을 순서대로 렌더.\n *\n * ```tsx\n * // app/services/page.tsx\n * import config from \"@/site.config\";\n * import { SiteRenderer } from \"@roottale/cms-renderer-next/templates\";\n *\n * export default function ServicesPage() {\n * return (\n * <SiteRenderer config={config} path=\"/services\"\n * apiKey={process.env.ROOTTALE_API_KEY!} />\n * );\n * }\n * ```\n */\nexport function SiteRenderer(props: SiteRendererProps): ReactElement {\n const {\n path,\n apiKey,\n baseUrl,\n customSections = {},\n unknownSection = \"warn-dev\",\n } = props;\n const config = parseSiteConfig(props.config);\n const sections = config.pages[path] ?? [];\n return (\n <>\n {sections.length === 0 ? (\n <UnknownSection\n type={`pages[\"${path}\"] (섹션 0개)`}\n mode={unknownSection}\n />\n ) : null}\n {sections.map((section, i) => {\n const key = section.id ?? `${section.type}-${i}`;\n const builtin = renderBuiltin(section, { apiKey, baseUrl });\n if (builtin !== undefined) {\n return <Fragment key={key}>{builtin}</Fragment>;\n }\n const Custom = customSections[section.type];\n if (Custom) {\n return (\n <Fragment key={key}>\n {Custom({\n id: section.id,\n props: section.props as Record<string, unknown>,\n variant: section.variant,\n theme: section.theme,\n })}\n </Fragment>\n );\n }\n return (\n <UnknownSection key={key} type={section.type} mode={unknownSection} />\n );\n })}\n </>\n );\n}\n"],"mappings":"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","names":["Fragment"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
import { RootTaleTheme } from '@roottale/cms-client/server';
|
|
3
|
+
|
|
4
|
+
type RootTaleCssVars = CSSProperties & Record<`--${string}`, string>;
|
|
5
|
+
declare function themeToCssVars(theme: RootTaleTheme | null): RootTaleCssVars | null;
|
|
6
|
+
|
|
7
|
+
export { type RootTaleCssVars as R, themeToCssVars as t };
|