@roottale/cms-renderer-next 0.20.0 → 0.22.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.
@@ -0,0 +1,715 @@
1
+ import { z } from 'zod';
2
+ import { ReactElement, CSSProperties, ReactNode } from 'react';
3
+
4
+ /**
5
+ * `@roottale/cms-renderer-next/templates` — canonical `SiteConfig` 스키마.
6
+ *
7
+ * ADR-0040 P2 + P0.5 RFC §① (`roottale-docs/docs/services/external-site-blockification.md`).
8
+ *
9
+ * 설계 원칙 (codex consult #1 반영):
10
+ * - **계약 = `section type + variant + renderer registry`.** tier 는 스키마 필드가
11
+ * 아니라 문서/거버넌스용 성숙도 메타데이터.
12
+ * - **100% serializable** — 함수/JSX 금지 (Astro parity + admin preview 동일 경로).
13
+ * escape hatch 는 config 안 함수가 아니라 `customSections` registry
14
+ * (`SiteRenderer` 에 빌드 시 type→컴포넌트 주입).
15
+ * - CMS-bound 섹션(`blog-list`/`lead-form`)은 `RootTaleBlogList`/`RootTaleLeadForm`
16
+ * 으로 위임 — templates 는 cms-renderer 코어를 단방향 의존 (RFC §②).
17
+ *
18
+ * RFC §① 대비 구현 정정:
19
+ * - `site.url` 은 optional — 스타터/외부 사이트가 `SITE_URL` env 로 런타임 해석하는
20
+ * 기존 패턴(P4 스타터)과 충돌하므로 config 강제 대신 env fallback 허용.
21
+ * - 빌트인에 `rich-text` 추가 (P4 스타터 기 운영 섹션 — "동일 패턴" 군).
22
+ */
23
+
24
+ declare const NavItemSchema: z.ZodObject<{
25
+ label: z.ZodString;
26
+ href: z.ZodString;
27
+ children: z.ZodOptional<z.ZodArray<z.ZodObject<{
28
+ label: z.ZodString;
29
+ href: z.ZodString;
30
+ }, "strip", z.ZodTypeAny, {
31
+ label: string;
32
+ href: string;
33
+ }, {
34
+ label: string;
35
+ href: string;
36
+ }>, "many">>;
37
+ }, "strip", z.ZodTypeAny, {
38
+ label: string;
39
+ href: string;
40
+ children?: {
41
+ label: string;
42
+ href: string;
43
+ }[] | undefined;
44
+ }, {
45
+ label: string;
46
+ href: string;
47
+ children?: {
48
+ label: string;
49
+ href: string;
50
+ }[] | undefined;
51
+ }>;
52
+ type NavItem = z.infer<typeof NavItemSchema>;
53
+ declare const CtaSchema: z.ZodObject<{
54
+ label: z.ZodString;
55
+ href: z.ZodString;
56
+ }, "strip", z.ZodTypeAny, {
57
+ label: string;
58
+ href: string;
59
+ }, {
60
+ label: string;
61
+ href: string;
62
+ }>;
63
+ type SectionCta = z.infer<typeof CtaSchema>;
64
+ declare const HeroSectionSchema: z.ZodObject<{
65
+ id: z.ZodOptional<z.ZodString>;
66
+ theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
67
+ } & {
68
+ type: z.ZodLiteral<"hero">;
69
+ variant: z.ZodDefault<z.ZodEnum<["split", "image", "minimal"]>>;
70
+ props: z.ZodObject<{
71
+ eyebrow: z.ZodOptional<z.ZodString>;
72
+ /** `\n` 으로 줄바꿈. */
73
+ title: z.ZodString;
74
+ description: z.ZodOptional<z.ZodString>;
75
+ imageSrc: z.ZodOptional<z.ZodString>;
76
+ imageAlt: z.ZodOptional<z.ZodString>;
77
+ cta: z.ZodOptional<z.ZodObject<{
78
+ label: z.ZodString;
79
+ href: z.ZodString;
80
+ }, "strip", z.ZodTypeAny, {
81
+ label: string;
82
+ href: string;
83
+ }, {
84
+ label: string;
85
+ href: string;
86
+ }>>;
87
+ }, "strip", z.ZodTypeAny, {
88
+ title: string;
89
+ eyebrow?: string | undefined;
90
+ description?: string | undefined;
91
+ imageSrc?: string | undefined;
92
+ imageAlt?: string | undefined;
93
+ cta?: {
94
+ label: string;
95
+ href: string;
96
+ } | undefined;
97
+ }, {
98
+ title: string;
99
+ eyebrow?: string | undefined;
100
+ description?: string | undefined;
101
+ imageSrc?: string | undefined;
102
+ imageAlt?: string | undefined;
103
+ cta?: {
104
+ label: string;
105
+ href: string;
106
+ } | undefined;
107
+ }>;
108
+ }, "strip", z.ZodTypeAny, {
109
+ type: "hero";
110
+ variant: "split" | "image" | "minimal";
111
+ props: {
112
+ title: string;
113
+ eyebrow?: string | undefined;
114
+ description?: string | undefined;
115
+ imageSrc?: string | undefined;
116
+ imageAlt?: string | undefined;
117
+ cta?: {
118
+ label: string;
119
+ href: string;
120
+ } | undefined;
121
+ };
122
+ id?: string | undefined;
123
+ theme?: Record<string, string> | undefined;
124
+ }, {
125
+ type: "hero";
126
+ props: {
127
+ title: string;
128
+ eyebrow?: string | undefined;
129
+ description?: string | undefined;
130
+ imageSrc?: string | undefined;
131
+ imageAlt?: string | undefined;
132
+ cta?: {
133
+ label: string;
134
+ href: string;
135
+ } | undefined;
136
+ };
137
+ id?: string | undefined;
138
+ variant?: "split" | "image" | "minimal" | undefined;
139
+ theme?: Record<string, string> | undefined;
140
+ }>;
141
+ declare const RichTextSectionSchema: z.ZodObject<{
142
+ id: z.ZodOptional<z.ZodString>;
143
+ variant: z.ZodOptional<z.ZodString>;
144
+ theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
145
+ } & {
146
+ type: z.ZodLiteral<"rich-text">;
147
+ props: z.ZodObject<{
148
+ heading: z.ZodOptional<z.ZodString>;
149
+ /** `\n\n` 으로 문단 분리. */
150
+ body: z.ZodString;
151
+ }, "strip", z.ZodTypeAny, {
152
+ body: string;
153
+ heading?: string | undefined;
154
+ }, {
155
+ body: string;
156
+ heading?: string | undefined;
157
+ }>;
158
+ }, "strip", z.ZodTypeAny, {
159
+ type: "rich-text";
160
+ props: {
161
+ body: string;
162
+ heading?: string | undefined;
163
+ };
164
+ id?: string | undefined;
165
+ variant?: string | undefined;
166
+ theme?: Record<string, string> | undefined;
167
+ }, {
168
+ type: "rich-text";
169
+ props: {
170
+ body: string;
171
+ heading?: string | undefined;
172
+ };
173
+ id?: string | undefined;
174
+ variant?: string | undefined;
175
+ theme?: Record<string, string> | undefined;
176
+ }>;
177
+ declare const ServicesSectionSchema: z.ZodObject<{
178
+ id: z.ZodOptional<z.ZodString>;
179
+ theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
180
+ } & {
181
+ type: z.ZodLiteral<"services">;
182
+ variant: z.ZodDefault<z.ZodEnum<["grid", "list"]>>;
183
+ props: z.ZodObject<{
184
+ heading: z.ZodOptional<z.ZodString>;
185
+ description: z.ZodOptional<z.ZodString>;
186
+ items: z.ZodArray<z.ZodObject<{
187
+ title: z.ZodString;
188
+ description: z.ZodOptional<z.ZodString>;
189
+ href: z.ZodOptional<z.ZodString>;
190
+ }, "strip", z.ZodTypeAny, {
191
+ title: string;
192
+ href?: string | undefined;
193
+ description?: string | undefined;
194
+ }, {
195
+ title: string;
196
+ href?: string | undefined;
197
+ description?: string | undefined;
198
+ }>, "many">;
199
+ }, "strip", z.ZodTypeAny, {
200
+ items: {
201
+ title: string;
202
+ href?: string | undefined;
203
+ description?: string | undefined;
204
+ }[];
205
+ description?: string | undefined;
206
+ heading?: string | undefined;
207
+ }, {
208
+ items: {
209
+ title: string;
210
+ href?: string | undefined;
211
+ description?: string | undefined;
212
+ }[];
213
+ description?: string | undefined;
214
+ heading?: string | undefined;
215
+ }>;
216
+ }, "strip", z.ZodTypeAny, {
217
+ type: "services";
218
+ variant: "grid" | "list";
219
+ props: {
220
+ items: {
221
+ title: string;
222
+ href?: string | undefined;
223
+ description?: string | undefined;
224
+ }[];
225
+ description?: string | undefined;
226
+ heading?: string | undefined;
227
+ };
228
+ id?: string | undefined;
229
+ theme?: Record<string, string> | undefined;
230
+ }, {
231
+ type: "services";
232
+ props: {
233
+ items: {
234
+ title: string;
235
+ href?: string | undefined;
236
+ description?: string | undefined;
237
+ }[];
238
+ description?: string | undefined;
239
+ heading?: string | undefined;
240
+ };
241
+ id?: string | undefined;
242
+ variant?: "grid" | "list" | undefined;
243
+ theme?: Record<string, string> | undefined;
244
+ }>;
245
+ declare const FaqSectionSchema: z.ZodObject<{
246
+ id: z.ZodOptional<z.ZodString>;
247
+ variant: z.ZodOptional<z.ZodString>;
248
+ theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
249
+ } & {
250
+ type: z.ZodLiteral<"faq">;
251
+ props: z.ZodObject<{
252
+ heading: z.ZodOptional<z.ZodString>;
253
+ items: z.ZodArray<z.ZodObject<{
254
+ question: z.ZodString;
255
+ answer: z.ZodString;
256
+ }, "strip", z.ZodTypeAny, {
257
+ question: string;
258
+ answer: string;
259
+ }, {
260
+ question: string;
261
+ answer: string;
262
+ }>, "many">;
263
+ /** FAQPage JSON-LD 주입 (기본 true — SEO 3 pillars). 페이지에 FAQ 섹션이 여럿이면 하나만 true 로. */
264
+ jsonLd: z.ZodDefault<z.ZodBoolean>;
265
+ }, "strip", z.ZodTypeAny, {
266
+ items: {
267
+ question: string;
268
+ answer: string;
269
+ }[];
270
+ jsonLd: boolean;
271
+ heading?: string | undefined;
272
+ }, {
273
+ items: {
274
+ question: string;
275
+ answer: string;
276
+ }[];
277
+ heading?: string | undefined;
278
+ jsonLd?: boolean | undefined;
279
+ }>;
280
+ }, "strip", z.ZodTypeAny, {
281
+ type: "faq";
282
+ props: {
283
+ items: {
284
+ question: string;
285
+ answer: string;
286
+ }[];
287
+ jsonLd: boolean;
288
+ heading?: string | undefined;
289
+ };
290
+ id?: string | undefined;
291
+ variant?: string | undefined;
292
+ theme?: Record<string, string> | undefined;
293
+ }, {
294
+ type: "faq";
295
+ props: {
296
+ items: {
297
+ question: string;
298
+ answer: string;
299
+ }[];
300
+ heading?: string | undefined;
301
+ jsonLd?: boolean | undefined;
302
+ };
303
+ id?: string | undefined;
304
+ variant?: string | undefined;
305
+ theme?: Record<string, string> | undefined;
306
+ }>;
307
+ declare const CtaBannerSectionSchema: z.ZodObject<{
308
+ id: z.ZodOptional<z.ZodString>;
309
+ variant: z.ZodOptional<z.ZodString>;
310
+ theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
311
+ } & {
312
+ type: z.ZodLiteral<"cta-banner">;
313
+ props: z.ZodObject<{
314
+ title: z.ZodString;
315
+ description: z.ZodOptional<z.ZodString>;
316
+ cta: z.ZodOptional<z.ZodObject<{
317
+ label: z.ZodString;
318
+ href: z.ZodString;
319
+ }, "strip", z.ZodTypeAny, {
320
+ label: string;
321
+ href: string;
322
+ }, {
323
+ label: string;
324
+ href: string;
325
+ }>>;
326
+ }, "strip", z.ZodTypeAny, {
327
+ title: string;
328
+ description?: string | undefined;
329
+ cta?: {
330
+ label: string;
331
+ href: string;
332
+ } | undefined;
333
+ }, {
334
+ title: string;
335
+ description?: string | undefined;
336
+ cta?: {
337
+ label: string;
338
+ href: string;
339
+ } | undefined;
340
+ }>;
341
+ }, "strip", z.ZodTypeAny, {
342
+ type: "cta-banner";
343
+ props: {
344
+ title: string;
345
+ description?: string | undefined;
346
+ cta?: {
347
+ label: string;
348
+ href: string;
349
+ } | undefined;
350
+ };
351
+ id?: string | undefined;
352
+ variant?: string | undefined;
353
+ theme?: Record<string, string> | undefined;
354
+ }, {
355
+ type: "cta-banner";
356
+ props: {
357
+ title: string;
358
+ description?: string | undefined;
359
+ cta?: {
360
+ label: string;
361
+ href: string;
362
+ } | undefined;
363
+ };
364
+ id?: string | undefined;
365
+ variant?: string | undefined;
366
+ theme?: Record<string, string> | undefined;
367
+ }>;
368
+ /** → `RootTaleBlogList` 위임 (cms-bound). */
369
+ declare const BlogListSectionSchema: z.ZodObject<{
370
+ id: z.ZodOptional<z.ZodString>;
371
+ variant: z.ZodOptional<z.ZodString>;
372
+ theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
373
+ } & {
374
+ type: z.ZodLiteral<"blog-list">;
375
+ props: z.ZodObject<{
376
+ heading: z.ZodOptional<z.ZodString>;
377
+ limit: z.ZodOptional<z.ZodNumber>;
378
+ /** 포스트 링크 베이스. 기본 `/blog`. */
379
+ blogBasePath: z.ZodOptional<z.ZodString>;
380
+ emptyMessage: z.ZodOptional<z.ZodString>;
381
+ }, "strip", z.ZodTypeAny, {
382
+ heading?: string | undefined;
383
+ limit?: number | undefined;
384
+ blogBasePath?: string | undefined;
385
+ emptyMessage?: string | undefined;
386
+ }, {
387
+ heading?: string | undefined;
388
+ limit?: number | undefined;
389
+ blogBasePath?: string | undefined;
390
+ emptyMessage?: string | undefined;
391
+ }>;
392
+ }, "strip", z.ZodTypeAny, {
393
+ type: "blog-list";
394
+ props: {
395
+ heading?: string | undefined;
396
+ limit?: number | undefined;
397
+ blogBasePath?: string | undefined;
398
+ emptyMessage?: string | undefined;
399
+ };
400
+ id?: string | undefined;
401
+ variant?: string | undefined;
402
+ theme?: Record<string, string> | undefined;
403
+ }, {
404
+ type: "blog-list";
405
+ props: {
406
+ heading?: string | undefined;
407
+ limit?: number | undefined;
408
+ blogBasePath?: string | undefined;
409
+ emptyMessage?: string | undefined;
410
+ };
411
+ id?: string | undefined;
412
+ variant?: string | undefined;
413
+ theme?: Record<string, string> | undefined;
414
+ }>;
415
+ /** → `RootTaleLeadForm` 위임 (cms-bound). */
416
+ declare const LeadFormSectionSchema: z.ZodObject<{
417
+ id: z.ZodOptional<z.ZodString>;
418
+ variant: z.ZodOptional<z.ZodString>;
419
+ theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
420
+ } & {
421
+ type: z.ZodLiteral<"lead-form">;
422
+ props: z.ZodObject<{
423
+ /** 사이트 자체 server 엔드포인트 (예: `/api/inquiry`). intake API 직접 지정 금지. */
424
+ action: z.ZodString;
425
+ vertical: z.ZodOptional<z.ZodEnum<["consulting", "medical", "tax", "legal"]>>;
426
+ heading: z.ZodOptional<z.ZodString>;
427
+ description: z.ZodOptional<z.ZodString>;
428
+ submitLabel: z.ZodOptional<z.ZodString>;
429
+ }, "strip", z.ZodTypeAny, {
430
+ action: string;
431
+ description?: string | undefined;
432
+ heading?: string | undefined;
433
+ vertical?: "consulting" | "medical" | "tax" | "legal" | undefined;
434
+ submitLabel?: string | undefined;
435
+ }, {
436
+ action: string;
437
+ description?: string | undefined;
438
+ heading?: string | undefined;
439
+ vertical?: "consulting" | "medical" | "tax" | "legal" | undefined;
440
+ submitLabel?: string | undefined;
441
+ }>;
442
+ }, "strip", z.ZodTypeAny, {
443
+ type: "lead-form";
444
+ props: {
445
+ action: string;
446
+ description?: string | undefined;
447
+ heading?: string | undefined;
448
+ vertical?: "consulting" | "medical" | "tax" | "legal" | undefined;
449
+ submitLabel?: string | undefined;
450
+ };
451
+ id?: string | undefined;
452
+ variant?: string | undefined;
453
+ theme?: Record<string, string> | undefined;
454
+ }, {
455
+ type: "lead-form";
456
+ props: {
457
+ action: string;
458
+ description?: string | undefined;
459
+ heading?: string | undefined;
460
+ vertical?: "consulting" | "medical" | "tax" | "legal" | undefined;
461
+ submitLabel?: string | undefined;
462
+ };
463
+ id?: string | undefined;
464
+ variant?: string | undefined;
465
+ theme?: Record<string, string> | undefined;
466
+ }>;
467
+ /** customSections 용 fallback — `type` 자유, props 는 직렬화만 강제. */
468
+ declare const CustomSectionSchema: z.ZodObject<{
469
+ /** 안정적 anchor·dedup·preview 매핑용. 페이지 내 유일. */
470
+ id: z.ZodOptional<z.ZodString>;
471
+ /** 빌트인 또는 customSections registry 키. kebab-case. */
472
+ type: z.ZodString;
473
+ /** 같은 type 의 비주얼 변형. 빌트인 type 은 enum 으로 좁힘. */
474
+ variant: z.ZodOptional<z.ZodString>;
475
+ /** 섹션 스코프 토큰 override (`--rt-*` CSS 변수 → 값). */
476
+ theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
477
+ /** 섹션별 직렬화 props. */
478
+ props: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodType<unknown, z.ZodTypeDef, unknown>>>;
479
+ }, "strip", z.ZodTypeAny, {
480
+ type: string;
481
+ props: Record<string, unknown>;
482
+ id?: string | undefined;
483
+ variant?: string | undefined;
484
+ theme?: Record<string, string> | undefined;
485
+ }, {
486
+ type: string;
487
+ id?: string | undefined;
488
+ variant?: string | undefined;
489
+ theme?: Record<string, string> | undefined;
490
+ props?: Record<string, unknown> | undefined;
491
+ }>;
492
+ /** 빌트인 type 목록 (renderer registry 와 1:1). */
493
+ declare const BUILTIN_SECTION_TYPES: readonly string[];
494
+ type HeroSection = z.output<typeof HeroSectionSchema>;
495
+ type RichTextSection = z.output<typeof RichTextSectionSchema>;
496
+ type ServicesSection = z.output<typeof ServicesSectionSchema>;
497
+ type FaqSection = z.output<typeof FaqSectionSchema>;
498
+ type CtaBannerSection = z.output<typeof CtaBannerSectionSchema>;
499
+ type BlogListSection = z.output<typeof BlogListSectionSchema>;
500
+ type LeadFormSection = z.output<typeof LeadFormSectionSchema>;
501
+ type CustomSection = z.output<typeof CustomSectionSchema>;
502
+ /** 파싱 결과 섹션 (defaults 적용 후). */
503
+ type Section = HeroSection | RichTextSection | ServicesSection | FaqSection | CtaBannerSection | BlogListSection | LeadFormSection | CustomSection;
504
+ /** config 작성용 섹션 (defaults 생략 가능). `site.config.ts` 에서 이 타입 사용. */
505
+ type SectionInput = z.input<typeof HeroSectionSchema> | z.input<typeof RichTextSectionSchema> | z.input<typeof ServicesSectionSchema> | z.input<typeof FaqSectionSchema> | z.input<typeof CtaBannerSectionSchema> | z.input<typeof BlogListSectionSchema> | z.input<typeof LeadFormSectionSchema> | z.input<typeof CustomSectionSchema>;
506
+ /**
507
+ * 섹션 스키마 — 빌트인 type 이면 해당 스키마로 *강제* 검증 (union fallthrough 로
508
+ * 빌트인 오타이핑이 CustomSection 으로 조용히 통과하는 것을 차단), 미지 type 은
509
+ * CustomSection 으로 통과 (렌더러가 customSections registry 로 해석).
510
+ */
511
+ declare const SectionSchema: z.ZodType<Section, z.ZodTypeDef, SectionInput>;
512
+ declare const SiteConfigSchema: z.ZodObject<{
513
+ site: z.ZodObject<{
514
+ /** canonical origin. 생략 시 소비측이 `SITE_URL` env 등으로 해석. */
515
+ url: z.ZodOptional<z.ZodString>;
516
+ title: z.ZodString;
517
+ description: z.ZodOptional<z.ZodString>;
518
+ locale: z.ZodDefault<z.ZodString>;
519
+ og: z.ZodOptional<z.ZodObject<{
520
+ image: z.ZodOptional<z.ZodString>;
521
+ }, "strip", z.ZodTypeAny, {
522
+ image?: string | undefined;
523
+ }, {
524
+ image?: string | undefined;
525
+ }>>;
526
+ }, "strip", z.ZodTypeAny, {
527
+ title: string;
528
+ locale: string;
529
+ description?: string | undefined;
530
+ url?: string | undefined;
531
+ og?: {
532
+ image?: string | undefined;
533
+ } | undefined;
534
+ }, {
535
+ title: string;
536
+ description?: string | undefined;
537
+ url?: string | undefined;
538
+ locale?: string | undefined;
539
+ og?: {
540
+ image?: string | undefined;
541
+ } | undefined;
542
+ }>;
543
+ /** 사이트 전역 토큰 override (`--rt-*` CSS 변수 → 값). 비우면 admin /design SSoT. */
544
+ theme: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
545
+ nav: z.ZodDefault<z.ZodArray<z.ZodObject<{
546
+ label: z.ZodString;
547
+ href: z.ZodString;
548
+ children: z.ZodOptional<z.ZodArray<z.ZodObject<{
549
+ label: z.ZodString;
550
+ href: z.ZodString;
551
+ }, "strip", z.ZodTypeAny, {
552
+ label: string;
553
+ href: string;
554
+ }, {
555
+ label: string;
556
+ href: string;
557
+ }>, "many">>;
558
+ }, "strip", z.ZodTypeAny, {
559
+ label: string;
560
+ href: string;
561
+ children?: {
562
+ label: string;
563
+ href: string;
564
+ }[] | undefined;
565
+ }, {
566
+ label: string;
567
+ href: string;
568
+ children?: {
569
+ label: string;
570
+ href: string;
571
+ }[] | undefined;
572
+ }>, "many">>;
573
+ /** path("/", "/services", …) → 섹션 배열. */
574
+ pages: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodType<Section, z.ZodTypeDef, SectionInput>, "many">>;
575
+ }, "strip", z.ZodTypeAny, {
576
+ theme: Record<string, string>;
577
+ site: {
578
+ title: string;
579
+ locale: string;
580
+ description?: string | undefined;
581
+ url?: string | undefined;
582
+ og?: {
583
+ image?: string | undefined;
584
+ } | undefined;
585
+ };
586
+ nav: {
587
+ label: string;
588
+ href: string;
589
+ children?: {
590
+ label: string;
591
+ href: string;
592
+ }[] | undefined;
593
+ }[];
594
+ pages: Record<string, Section[]>;
595
+ }, {
596
+ site: {
597
+ title: string;
598
+ description?: string | undefined;
599
+ url?: string | undefined;
600
+ locale?: string | undefined;
601
+ og?: {
602
+ image?: string | undefined;
603
+ } | undefined;
604
+ };
605
+ pages: Record<string, SectionInput[]>;
606
+ theme?: Record<string, string> | undefined;
607
+ nav?: {
608
+ label: string;
609
+ href: string;
610
+ children?: {
611
+ label: string;
612
+ href: string;
613
+ }[] | undefined;
614
+ }[] | undefined;
615
+ }>;
616
+ type SiteConfig = z.output<typeof SiteConfigSchema>;
617
+ /** config 작성용 (defaults 생략 가능). */
618
+ type SiteConfigInput = Omit<z.input<typeof SiteConfigSchema>, "pages"> & {
619
+ pages: Record<string, SectionInput[]>;
620
+ };
621
+ /** config 검증 + defaults 적용. 실패 시 ZodError throw (빌드 타임 fail-fast 용). */
622
+ declare function parseSiteConfig(input: unknown): SiteConfig;
623
+ /**
624
+ * `site.config.ts` 작성 헬퍼 — 타입 추론 + 즉시 검증.
625
+ *
626
+ * ```ts
627
+ * import { defineSiteConfig } from "@roottale/cms-renderer-next/templates";
628
+ * export default defineSiteConfig({ site: { title: "…" }, pages: { "/": [ … ] } });
629
+ * ```
630
+ */
631
+ declare function defineSiteConfig(input: SiteConfigInput): SiteConfig;
632
+
633
+ /**
634
+ * 빌트인 섹션 템플릿 — MUI-free, 0 JS RSC (ADR-0040 P2).
635
+ *
636
+ * 클래스는 전부 `rt-tpl-*` prefix, 스코프는 섹션 루트의 `data-roottale-tpl`
637
+ * (templates.css 가 `[data-roottale-tpl] :where(…)` 로 zero-specificity 적용 —
638
+ * customer 는 plain class / Tailwind utility 로 자유 override, cms-public.css
639
+ * 와 동일 계약).
640
+ *
641
+ * 토큰은 `--rt-*` CSS 변수 + 정적 fallback — `@roottale/tokens`(private) 미의존
642
+ * (RFC §②). admin /design 값은 `RootTaleThemeProvider` 가 주입.
643
+ */
644
+
645
+ /** 섹션 `theme`(--rt-* 변수 override)를 inline style 로. */
646
+ declare function sectionThemeStyle(theme: Record<string, string> | undefined): CSSProperties | undefined;
647
+ /** 빌트인 `hero`. variant: split(기본) / image / minimal. */
648
+ declare function TemplateHero(section: HeroSection): ReactElement;
649
+ /** 빌트인 `rich-text`. 문단은 `\n\n` 분리. */
650
+ declare function TemplateRichText(section: RichTextSection): ReactElement;
651
+ /** 빌트인 `services`. variant: grid(기본) / list. */
652
+ declare function TemplateServices(section: ServicesSection): ReactElement;
653
+ /** 빌트인 `faq`. `<details>` 0 JS + FAQPage JSON-LD (기본 on). */
654
+ declare function TemplateFaq(section: FaqSection): ReactElement;
655
+ /** 빌트인 `cta-banner`. */
656
+ declare function TemplateCtaBanner(section: CtaBannerSection): ReactElement;
657
+
658
+ /**
659
+ * `SiteRenderer` — `SiteConfig.pages[path]` 섹션 배열을 registry 로 렌더.
660
+ *
661
+ * ADR-0040 P2 + P0.5 RFC §①:
662
+ * - 빌트인 registry + 클라 주입 `customSections` 머지. **빌트인이 우선** —
663
+ * 클라가 빌트인 type 을 가로채 계약이 갈라지는 것 방지 (고유 섹션은 고유
664
+ * type 으로).
665
+ * - **render-prop 금지**: 임의 JSX escape hatch 는 customSections registry 로만.
666
+ * - **admin preview parity**: admin draft/preview 도 동일 `SiteRenderer` + 동일
667
+ * 빌트인 registry 를 탄다. customSections 는 클라 repo 자산 — preview 에선
668
+ * 미주입 → `unknownSection: "placeholder"` 로 skeleton 표시.
669
+ * - cms-bound 섹션(blog-list/lead-form)은 `RootTaleBlogList`/`RootTaleLeadForm`
670
+ * 위임 (단방향: templates → renderer 코어).
671
+ */
672
+
673
+ /** customSections registry 컴포넌트 계약 (RFC §①). */
674
+ type SectionComponent = (p: {
675
+ id?: string;
676
+ props: Record<string, unknown>;
677
+ variant?: string;
678
+ theme?: Record<string, string>;
679
+ }) => ReactNode;
680
+ interface SiteRendererProps {
681
+ /** `defineSiteConfig()` 결과 또는 raw config (내부에서 검증). */
682
+ config: SiteConfig | SiteConfigInput;
683
+ /** 현재 페이지 경로 — `config.pages` 의 key ("/", "/services", …). */
684
+ path: string;
685
+ /** cms-bound 섹션(blog-list/lead-form 의 theme fetch) 위임용. 서버 전용. */
686
+ apiKey: string;
687
+ /** self-host 등 API base override (`ROOTTALE_API_BASE`). */
688
+ baseUrl?: string;
689
+ /** 클라 repo 고유 섹션 registry (escape hatch). 빌트인 type 은 가로챌 수 없음. */
690
+ customSections?: Record<string, SectionComponent>;
691
+ /**
692
+ * registry 에 없는 type 처리. 기본 "warn-dev" = dev 에서 경고 박스, prod 에서
693
+ * 생략. admin preview 는 "placeholder" (customSections 미주입 환경).
694
+ */
695
+ unknownSection?: "warn-dev" | "placeholder" | "ignore";
696
+ }
697
+ /**
698
+ * 한 페이지의 섹션 배열을 순서대로 렌더.
699
+ *
700
+ * ```tsx
701
+ * // app/services/page.tsx
702
+ * import config from "@/site.config";
703
+ * import { SiteRenderer } from "@roottale/cms-renderer-next/templates";
704
+ *
705
+ * export default function ServicesPage() {
706
+ * return (
707
+ * <SiteRenderer config={config} path="/services"
708
+ * apiKey={process.env.ROOTTALE_API_KEY!} />
709
+ * );
710
+ * }
711
+ * ```
712
+ */
713
+ declare function SiteRenderer(props: SiteRendererProps): ReactElement;
714
+
715
+ export { BUILTIN_SECTION_TYPES, type BlogListSection, BlogListSectionSchema, type CtaBannerSection, CtaBannerSectionSchema, type CustomSection, CustomSectionSchema, type FaqSection, FaqSectionSchema, type HeroSection, HeroSectionSchema, type LeadFormSection, LeadFormSectionSchema, type NavItem, NavItemSchema, type RichTextSection, RichTextSectionSchema, type Section, type SectionComponent, type SectionCta, type SectionInput, SectionSchema, type ServicesSection, ServicesSectionSchema, type SiteConfig, type SiteConfigInput, SiteConfigSchema, SiteRenderer, type SiteRendererProps, TemplateCtaBanner, TemplateFaq, TemplateHero, TemplateRichText, TemplateServices, defineSiteConfig, parseSiteConfig, sectionThemeStyle };