@roottale/cms-renderer-next 0.48.0 → 0.51.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 (36) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/blog-filter-BKCGM0qd.d.ts +29 -0
  3. package/dist/{chunk-6MFHA555.js → chunk-27RT6B3O.js} +1 -1
  4. package/dist/chunk-27RT6B3O.js.map +1 -0
  5. package/dist/chunk-2QU4JU46.js +3927 -0
  6. package/dist/chunk-2QU4JU46.js.map +1 -0
  7. package/dist/chunk-4RQ3SEDL.js +106 -0
  8. package/dist/chunk-4RQ3SEDL.js.map +1 -0
  9. package/dist/{chunk-OMXEU4LS.js → chunk-QYNEOFL7.js} +140 -136
  10. package/dist/chunk-QYNEOFL7.js.map +1 -0
  11. package/dist/concerns-client.d.ts +1 -1
  12. package/dist/expertise-client.d.ts +1 -1
  13. package/dist/expertise-client.js +1 -1
  14. package/dist/hero-carousel-client.js +1 -1
  15. package/dist/routes.d.ts +334 -20
  16. package/dist/routes.js +249 -68
  17. package/dist/routes.js.map +1 -1
  18. package/dist/seo-asset-hosts.d.ts +36 -0
  19. package/dist/seo-asset-hosts.js +391 -0
  20. package/dist/seo-asset-hosts.js.map +1 -0
  21. package/dist/server.d.ts +23 -241
  22. package/dist/server.js +6 -6
  23. package/dist/{site-config-Dicv57oY.d.ts → site-config-DNpvlqA5.d.ts} +201 -201
  24. package/dist/spaces-client.d.ts +1 -1
  25. package/dist/team-expanding-stage-client.d.ts +1 -1
  26. package/dist/team-expanding-stage-client.js +1 -1
  27. package/dist/templates.d.ts +64 -25
  28. package/dist/templates.js +13 -6
  29. package/dist/templates.js.map +1 -1
  30. package/package.json +7 -2
  31. package/dist/chunk-6MFHA555.js.map +0 -1
  32. package/dist/chunk-FZK3EXYZ.js +0 -103
  33. package/dist/chunk-FZK3EXYZ.js.map +0 -1
  34. package/dist/chunk-LIBSVC4M.js +0 -3642
  35. package/dist/chunk-LIBSVC4M.js.map +0 -1
  36. package/dist/chunk-OMXEU4LS.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { S as SpacesSection } from './site-config-Dicv57oY.js';
2
+ import { S as SpacesSection } from './site-config-DNpvlqA5.js';
3
3
  import 'zod';
4
4
 
5
5
  type SpaceItem = SpacesSection["props"]["spaces"][number];
@@ -1,5 +1,5 @@
1
1
  import { ReactElement } from 'react';
2
- import { T as TeamSection } from './site-config-Dicv57oY.js';
2
+ import { T as TeamSection } from './site-config-DNpvlqA5.js';
3
3
  import 'zod';
4
4
 
5
5
  type TeamMember = TeamSection["props"]["members"][number];
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  TeamExpandingStageClient
4
- } from "./chunk-FZK3EXYZ.js";
4
+ } from "./chunk-4RQ3SEDL.js";
5
5
  export {
6
6
  TeamExpandingStageClient
7
7
  };
@@ -1,9 +1,9 @@
1
- import { C as ConcernsSection, E as ExpertiseSection, S as SpacesSection, B as BeforeAfterSection, a as ContactSection, b as CtaBannerSection, F as FaqSection, c as FooterSection, H as HeroSection, d as HoursSection, P as PricingSection, e as ProcessSection, R as RichTextSection, f as ServicesSection, T as TeamSection, g as TestimonialsSection, h as TrustSection, i as SiteConfig, j as SiteConfigInput } from './site-config-Dicv57oY.js';
2
- export { k as BUILTIN_SECTION_SCHEMAS, l as BUILTIN_SECTION_TYPES, m as BeforeAfterSectionSchema, n as BlogListSection, o as BlogListSectionSchema, p as ConcernsSectionSchema, q as ContactSectionSchema, r as CtaBannerSectionSchema, s as CustomSection, t as CustomSectionSchema, u as ExpertiseSectionSchema, v as FaqSectionSchema, w as FooterSectionSchema, x as HeroSectionSchema, y as HoursSectionSchema, L as LeadFormSection, z as LeadFormSectionSchema, N as NavItem, A as NavItemSchema, D as PricingSectionSchema, G as ProcessSectionSchema, I as RichTextSectionSchema, J as SECTION_VARIANTS, K as Section, M as SectionCta, O as SectionInput, Q as SectionSchema, U as ServicesSectionSchema, V as SiteConfigSchema, W as SpacesSectionSchema, X as TeamSectionSchema, Y as TestimonialsSectionSchema, Z as TrustSectionSchema, _ as defineSiteConfig, $ as parseSiteConfig } from './site-config-Dicv57oY.js';
1
+ import { C as ConcernsSection, E as ExpertiseSection, S as SpacesSection, H as HeroSection, R as RichTextSection, a as ServicesSection, F as FooterSection, b as FaqSection, c as CtaBannerSection, d as TrustSection, e as TestimonialsSection, T as TeamSection, f as ContactSection, g as HoursSection, P as PricingSection, h as ProcessSection, B as BeforeAfterSection, i as SiteConfig, j as SiteConfigInput } from './site-config-DNpvlqA5.js';
2
+ export { k as BUILTIN_SECTION_SCHEMAS, l as BUILTIN_SECTION_TYPES, m as BeforeAfterSectionSchema, n as BlogListSection, o as BlogListSectionSchema, p as ConcernsSectionSchema, q as ContactSectionSchema, r as CtaBannerSectionSchema, s as CustomSection, t as CustomSectionSchema, u as ExpertiseSectionSchema, v as FaqSectionSchema, w as FooterSectionSchema, x as HeroSectionSchema, y as HoursSectionSchema, L as LeadFormSection, z as LeadFormSectionSchema, N as NavItem, A as NavItemSchema, D as PricingSectionSchema, G as ProcessSectionSchema, I as RichTextSectionSchema, J as SECTION_VARIANTS, K as Section, M as SectionCta, O as SectionInput, Q as SectionSchema, U as ServicesSectionSchema, V as SiteConfigSchema, W as SpacesSectionSchema, X as TeamSectionSchema, Y as TestimonialsSectionSchema, Z as TrustSectionSchema, _ as defineSiteConfig, $ as parseSiteConfig } from './site-config-DNpvlqA5.js';
3
3
  export { R as RootTaleCssVars, t as themeToCssVars } from './theme-css-vars-CD3jcEkp.js';
4
4
  import { ReactElement, CSSProperties, ReactNode } from 'react';
5
+ import { CmsPostContent } from '@roottale/cms-client/server';
5
6
  import 'zod';
6
- import '@roottale/cms-client/server';
7
7
  import '@roottale/ui-react/theme';
8
8
 
9
9
  /**
@@ -33,45 +33,39 @@ declare function TemplateExpertise(section: ExpertiseSection): ReactElement;
33
33
  declare function TemplateSpaces(section: SpacesSection): ReactElement;
34
34
  declare function TemplateConcerns(section: ConcernsSection): ReactElement;
35
35
 
36
- /**
37
- * 빌트인 섹션 템플릿 — MUI-free, 0 JS RSC (ADR-0040 P2).
38
- *
39
- * 클래스는 전부 `rt-tpl-*` prefix, 스코프는 섹션 루트의 `data-roottale-tpl`
40
- * (templates.css 가 `[data-roottale-tpl] :where(…)` 로 zero-specificity 적용 —
41
- * customer 는 plain class / Tailwind utility 로 자유 override, cms-public.css
42
- * 와 동일 계약).
43
- *
44
- * 토큰은 `--rt-*` CSS 변수 + 정적 fallback — `@roottale/tokens`(private) 미의존
45
- * (RFC §②). admin /design 값은 `RootTaleThemeProvider` 가 주입.
46
- * allow: SIZE_OK — W1-2 소유 계약이 Trust·Contact 템플릿을 이 파일에 고정한다.
47
- */
48
-
49
- /** 섹션 `theme`(--rt-* 변수 override)를 inline style 로. */
50
- declare function sectionThemeStyle(theme: Record<string, string> | undefined): CSSProperties | undefined;
51
36
  /** 빌트인 `hero`. variant: split(기본) / image / minimal / carousel. */
52
37
  declare function TemplateHero(section: HeroSection): ReactElement;
53
- /** 빌트인 `rich-text`. 문단은 `\n\n` 분리. */
38
+
54
39
  declare function TemplateRichText(section: RichTextSection): ReactElement;
55
- /** 빌트인 `services`. variant: grid(기본) / list / index-rows / split-featured. */
40
+
56
41
  declare function TemplateServices(section: ServicesSection): ReactElement;
42
+
57
43
  declare function TemplateFooter(section: FooterSection): ReactElement;
44
+
58
45
  /** 빌트인 `faq`. `<details>` 0 JS + FAQPage JSON-LD (기본 on). */
59
46
  declare function TemplateFaq(section: FaqSection): ReactElement;
47
+
60
48
  /** 빌트인 `cta-banner`. variant: default(기본) / photo-overlay. */
61
49
  declare function TemplateCtaBanner(section: CtaBannerSection): ReactElement;
50
+
62
51
  declare function TemplateTrust(section: TrustSection): ReactElement;
63
- /**
64
- * 빌트인 `testimonials`(C11 + W8-7). variant: editorial-quote(기본) /
65
- * quote-rail / star-compact.
66
- */
52
+
67
53
  declare function TemplateTestimonials(section: TestimonialsSection): ReactElement;
54
+
68
55
  declare function TemplateTeam(section: TeamSection): ReactElement;
56
+
69
57
  declare function TemplateContact(section: ContactSection): ReactElement;
58
+
70
59
  declare function TemplateHours(section: HoursSection): ReactElement;
60
+
71
61
  declare function TemplatePricing(section: PricingSection): ReactElement;
62
+
72
63
  declare function TemplateProcess(section: ProcessSection): ReactElement;
64
+
73
65
  declare function TemplateBeforeAfter(section: BeforeAfterSection): ReactElement;
74
66
 
67
+ declare function sectionThemeStyle(theme: Record<string, string> | undefined): CSSProperties | undefined;
68
+
75
69
  /**
76
70
  * `SiteRenderer` — `SiteConfig.pages[path]` 섹션 배열을 registry 로 렌더.
77
71
  *
@@ -129,4 +123,49 @@ interface SiteRendererProps {
129
123
  */
130
124
  declare function SiteRenderer(props: SiteRendererProps): ReactElement;
131
125
 
132
- export { BeforeAfterSection, ConcernsSection, ContactSection, CtaBannerSection, ExpertiseSection, FaqSection, FooterSection, HeroSection, HoursSection, PricingSection, ProcessSection, RichTextSection, SITE_CONFIG_SCHEMA_VERSION, type SectionComponent, ServicesSection, SiteConfig, SiteConfigInput, SiteRenderer, type SiteRendererProps, SpacesSection, TeamSection, TemplateBeforeAfter, TemplateConcerns, TemplateContact, TemplateCtaBanner, TemplateExpertise, TemplateFaq, TemplateFooter, TemplateHero, TemplateHours, TemplatePricing, TemplateProcess, TemplateRichText, TemplateServices, TemplateSpaces, TemplateTeam, TemplateTestimonials, TemplateTrust, TestimonialsSection, TrustSection, resolveSiteConfigSchemaVersion, sectionThemeStyle };
126
+ declare function registerSectionTemplates(): void;
127
+
128
+ /**
129
+ * 섹션 렌더러 주입 지점 — ADR-0040 P2 단방향 계약(코어 ◄ templates).
130
+ *
131
+ * 본문(tiptap) 안의 `section` 노드는 `templates/` 의 컴포넌트로 그려야 하는데,
132
+ * 코어가 `templates/` 를 직접 import 하면 P3+ `site-kit` 분리 시 순환·번들
133
+ * 오염이 된다(dependency-cruiser `no-renderer-core-to-templates`). 그래서 코어는
134
+ * "섹션을 그리는 함수" 의 자리만 비워두고, `@roottale/cms-renderer-next/templates`
135
+ * 를 import 하는 쪽에서 채운다(그 배럴이 로드 시 자동 등록).
136
+ *
137
+ * 등록 없이 `section` 노드를 만나면 dev 경고 후 skip — 미지 섹션 타입을 만났을
138
+ * 때와 같은 fail-soft 동작이다.
139
+ */
140
+
141
+ /** 발행 API 호출에 필요한 최소 컨텍스트(공개 키 + 베이스 URL). */
142
+ type CmsBoundContext = {
143
+ apiKey: string;
144
+ baseUrl?: string;
145
+ };
146
+ type BlogListProps = {
147
+ apiKey: string;
148
+ baseUrl?: string;
149
+ limit?: number;
150
+ emptyMessage?: string;
151
+ postHref?: (post: CmsPostContent) => string;
152
+ };
153
+ type SectionRenderContext = {
154
+ cms: CmsBoundContext | null;
155
+ renderBlogList: (props: BlogListProps) => ReactNode;
156
+ isDev: () => boolean;
157
+ };
158
+
159
+ /**
160
+ * `section` 노드 → 템플릿 컴포넌트 매핑. ADR-0040 P2 단방향 계약에 따라 코어가
161
+ * 아니라 templates 계층에 산다 — 코어(`section-render-context.ts`)는 이 함수의
162
+ * 자리만 비워두고, templates 배럴이 로드되며 주입한다.
163
+ */
164
+
165
+ type TiptapNode = {
166
+ type?: string;
167
+ attrs?: Record<string, unknown>;
168
+ };
169
+ declare function renderSectionNode(node: TiptapNode, key: number, context: SectionRenderContext): ReactElement | null;
170
+
171
+ export { BeforeAfterSection, ConcernsSection, ContactSection, CtaBannerSection, ExpertiseSection, FaqSection, FooterSection, HeroSection, HoursSection, PricingSection, ProcessSection, RichTextSection, SITE_CONFIG_SCHEMA_VERSION, type SectionComponent, ServicesSection, SiteConfig, SiteConfigInput, SiteRenderer, type SiteRendererProps, SpacesSection, TeamSection, TemplateBeforeAfter, TemplateConcerns, TemplateContact, TemplateCtaBanner, TemplateExpertise, TemplateFaq, TemplateFooter, TemplateHero, TemplateHours, TemplatePricing, TemplateProcess, TemplateRichText, TemplateServices, TemplateSpaces, TemplateTeam, TemplateTestimonials, TemplateTrust, TestimonialsSection, TrustSection, registerSectionTemplates, renderSectionNode, resolveSiteConfigSchemaVersion, sectionThemeStyle };
package/dist/templates.js CHANGED
@@ -18,17 +18,21 @@ import {
18
18
  TemplateTeam,
19
19
  TemplateTestimonials,
20
20
  TemplateTrust,
21
+ registerSectionTemplates,
22
+ renderSectionNode,
21
23
  sectionThemeStyle,
22
24
  themeToCssVars
23
- } from "./chunk-LIBSVC4M.js";
25
+ } from "./chunk-2QU4JU46.js";
26
+ import "./chunk-VG6UUF4D.js";
27
+ import "./chunk-QYNEOFL7.js";
24
28
  import "./chunk-U5AKACPN.js";
25
29
  import "./chunk-LEHHSI6G.js";
26
- import "./chunk-6MFHA555.js";
27
- import "./chunk-FZK3EXYZ.js";
30
+ import "./chunk-27RT6B3O.js";
31
+ import "./chunk-YLSQAT6B.js";
32
+ import "./chunk-4RQ3SEDL.js";
28
33
  import "./chunk-K62RR4UQ.js";
29
34
  import "./chunk-H46JUIHZ.js";
30
35
  import "./chunk-ZL2NA3QT.js";
31
- import "./chunk-VG6UUF4D.js";
32
36
  import "./chunk-U2IM6OCQ.js";
33
37
  import {
34
38
  BUILTIN_SECTION_SCHEMAS,
@@ -62,8 +66,6 @@ import {
62
66
  parseSiteConfig,
63
67
  resolveSiteConfigSchemaVersion
64
68
  } from "./chunk-UM37DOIK.js";
65
- import "./chunk-OMXEU4LS.js";
66
- import "./chunk-YLSQAT6B.js";
67
69
 
68
70
  // src/templates/SiteRenderer.tsx
69
71
  import {
@@ -229,6 +231,9 @@ function SiteRenderer(props) {
229
231
  })
230
232
  ] });
231
233
  }
234
+
235
+ // src/templates/index.ts
236
+ registerSectionTemplates();
232
237
  export {
233
238
  BUILTIN_SECTION_SCHEMAS,
234
239
  BUILTIN_SECTION_TYPES,
@@ -277,6 +282,8 @@ export {
277
282
  TrustSectionSchema,
278
283
  defineSiteConfig,
279
284
  parseSiteConfig,
285
+ registerSectionTemplates,
286
+ renderSectionNode,
280
287
  resolveSiteConfigSchemaVersion,
281
288
  sectionThemeStyle,
282
289
  themeToCssVars
@@ -1 +1 @@
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 TemplateConcerns,\n TemplateExpertise,\n TemplateSpaces,\n} from \"./professional-sections.js\";\nimport {\n TemplateBeforeAfter,\n TemplateContact,\n TemplateCtaBanner,\n TemplateFaq,\n TemplateFooter,\n TemplateHero,\n TemplateHours,\n TemplatePricing,\n TemplateProcess,\n TemplateRichText,\n TemplateServices,\n TemplateTeam,\n TemplateTestimonials,\n TemplateTrust,\n sectionThemeStyle,\n} from \"./sections.js\";\nimport {\n type BeforeAfterSection,\n type BlogListSection,\n type ConcernsSection,\n type ContactSection,\n type CtaBannerSection,\n type ExpertiseSection,\n type FaqSection,\n type FooterSection,\n type HeroSection,\n type HoursSection,\n type LeadFormSection,\n type PricingSection,\n type ProcessSection,\n type RichTextSection,\n type Section,\n type ServicesSection,\n type SpacesSection,\n type SiteConfig,\n type SiteConfigInput,\n type TeamSection,\n type TestimonialsSection,\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 \"footer\":\n return <TemplateFooter {...(section as FooterSection)} />;\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 case \"hours\":\n return <TemplateHours {...(section as HoursSection)} />;\n case \"pricing\":\n return <TemplatePricing {...(section as PricingSection)} />;\n case \"process\":\n return <TemplateProcess {...(section as ProcessSection)} />;\n case \"before-after\":\n return <TemplateBeforeAfter {...(section as BeforeAfterSection)} />;\n case \"testimonials\":\n return <TemplateTestimonials {...(section as TestimonialsSection)} />;\n case \"expertise\":\n return <TemplateExpertise {...(section as ExpertiseSection)} />;\n case \"spaces\":\n return <TemplateSpaces {...(section as SpacesSection)} />;\n case \"concerns\":\n return <TemplateConcerns {...(section as ConcernsSection)} />;\n case \"team\":\n return <TemplateTeam {...(section as TeamSection)} />;\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"]}
1
+ {"version":3,"sources":["../src/templates/SiteRenderer.tsx","../src/templates/index.ts"],"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 TemplateConcerns,\n TemplateExpertise,\n TemplateSpaces,\n} from \"./professional-sections.js\";\nimport {\n TemplateBeforeAfter,\n TemplateContact,\n TemplateCtaBanner,\n TemplateFaq,\n TemplateFooter,\n TemplateHero,\n TemplateHours,\n TemplatePricing,\n TemplateProcess,\n TemplateRichText,\n TemplateServices,\n TemplateTeam,\n TemplateTestimonials,\n TemplateTrust,\n sectionThemeStyle,\n} from \"./sections.js\";\nimport {\n type BeforeAfterSection,\n type BlogListSection,\n type ConcernsSection,\n type ContactSection,\n type CtaBannerSection,\n type ExpertiseSection,\n type FaqSection,\n type FooterSection,\n type HeroSection,\n type HoursSection,\n type LeadFormSection,\n type PricingSection,\n type ProcessSection,\n type RichTextSection,\n type Section,\n type ServicesSection,\n type SpacesSection,\n type SiteConfig,\n type SiteConfigInput,\n type TeamSection,\n type TestimonialsSection,\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 \"footer\":\n return <TemplateFooter {...(section as FooterSection)} />;\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 case \"hours\":\n return <TemplateHours {...(section as HoursSection)} />;\n case \"pricing\":\n return <TemplatePricing {...(section as PricingSection)} />;\n case \"process\":\n return <TemplateProcess {...(section as ProcessSection)} />;\n case \"before-after\":\n return <TemplateBeforeAfter {...(section as BeforeAfterSection)} />;\n case \"testimonials\":\n return <TemplateTestimonials {...(section as TestimonialsSection)} />;\n case \"expertise\":\n return <TemplateExpertise {...(section as ExpertiseSection)} />;\n case \"spaces\":\n return <TemplateSpaces {...(section as SpacesSection)} />;\n case \"concerns\":\n return <TemplateConcerns {...(section as ConcernsSection)} />;\n case \"team\":\n return <TemplateTeam {...(section as TeamSection)} />;\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","/**\n * `@roottale/cms-renderer-next/templates` — config-driven 사이트 템플릿\n * (ADR-0040 P2, P0.5 RFC §①).\n *\n * canonical `SiteConfig`(zod) + MUI-free 섹션 템플릿 + `SiteRenderer`.\n * CSS 는 root layout 에서 명시적으로:\n * `import \"@roottale/cms-renderer-next/templates/styles\";`\n * (cms-public.css 와 동일하게 side-effect import 하지 않음 — `<head>` 삽입\n * 순서 계약은 RFC §③: tokens → emotion → cms styles/templates → globals.)\n *\n * 별 패키지(`@roottale/site-kit`) 분리는 RFC §② 기준 충족 시 P3+ — 그때까지\n * 본 서브패스가 유일 진입점.\n */\nexport {\n BUILTIN_SECTION_SCHEMAS,\n BUILTIN_SECTION_TYPES,\n SITE_CONFIG_SCHEMA_VERSION,\n resolveSiteConfigSchemaVersion,\n BeforeAfterSectionSchema,\n BlogListSectionSchema,\n ContactSectionSchema,\n ConcernsSectionSchema,\n CtaBannerSectionSchema,\n CustomSectionSchema,\n ExpertiseSectionSchema,\n FaqSectionSchema,\n FooterSectionSchema,\n HeroSectionSchema,\n HoursSectionSchema,\n LeadFormSectionSchema,\n NavItemSchema,\n PricingSectionSchema,\n ProcessSectionSchema,\n RichTextSectionSchema,\n SECTION_VARIANTS,\n SectionSchema,\n ServicesSectionSchema,\n SiteConfigSchema,\n SpacesSectionSchema,\n TeamSectionSchema,\n TestimonialsSectionSchema,\n TrustSectionSchema,\n defineSiteConfig,\n parseSiteConfig,\n type BeforeAfterSection,\n type BlogListSection,\n type ContactSection,\n type ConcernsSection,\n type CtaBannerSection,\n type CustomSection,\n type ExpertiseSection,\n type FaqSection,\n type FooterSection,\n type HeroSection,\n type HoursSection,\n type LeadFormSection,\n type NavItem,\n type PricingSection,\n type ProcessSection,\n type RichTextSection,\n type Section,\n type SectionCta,\n type SectionInput,\n type ServicesSection,\n type SiteConfig,\n type SiteConfigInput,\n type SpacesSection,\n type TeamSection,\n type TestimonialsSection,\n type TrustSection,\n} from \"./site-config.js\";\n\nexport { themeToCssVars, type RootTaleCssVars } from \"../theme-css-vars.js\";\n\nexport {\n TemplateConcerns,\n TemplateExpertise,\n TemplateSpaces,\n} from \"./professional-sections.js\";\n\nexport {\n TemplateBeforeAfter,\n TemplateContact,\n TemplateCtaBanner,\n TemplateFaq,\n TemplateFooter,\n TemplateHero,\n TemplateHours,\n TemplatePricing,\n TemplateProcess,\n TemplateRichText,\n TemplateServices,\n TemplateTeam,\n TemplateTestimonials,\n TemplateTrust,\n sectionThemeStyle,\n} from \"./sections.js\";\n\nexport {\n SiteRenderer,\n type SectionComponent,\n type SiteRendererProps,\n} from \"./SiteRenderer.js\";\n\n/**\n * 이 배럴만 쓰는 경로(SiteRenderer 등)에서도 본문 `section` 노드가 그려지도록\n * 로드 시 등록한다. `/server` 진입점도 같은 등록을 하므로 중복 호출이지만\n * 같은 함수를 다시 넣을 뿐이라 무해하다.\n */\nimport { registerSectionTemplates } from \"./register.js\";\n\nregisterSectionTemplates();\n\nexport { registerSectionTemplates };\nexport { renderSectionNode } from \"./render-section-node.js\";\n"],"mappings":"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","names":["Fragment"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roottale/cms-renderer-next",
3
- "version": "0.48.0",
3
+ "version": "0.51.0",
4
4
  "type": "module",
5
5
  "description": "RootTale CMS public-render React/Next.js Server Components. SSR-only RSC components (RootTaleBlogList / RootTaleBlogPost / RootTaleLeadForm) for external customer sites.",
6
6
  "main": "./dist/index.js",
@@ -44,6 +44,11 @@
44
44
  "import": "./dist/seo-checks.js",
45
45
  "default": "./dist/seo-checks.js"
46
46
  },
47
+ "./seo-asset-hosts": {
48
+ "types": "./dist/seo-asset-hosts.d.ts",
49
+ "import": "./dist/seo-asset-hosts.js",
50
+ "default": "./dist/seo-asset-hosts.js"
51
+ },
47
52
  "./styles": "./dist/cms-public.css",
48
53
  "./templates/styles": "./dist/templates.css",
49
54
  "./package.json": "./package.json"
@@ -57,7 +62,7 @@
57
62
  "embla-carousel-react": "^8.6.0",
58
63
  "motion": "^12.24.12",
59
64
  "zod": "^3.23.8",
60
- "@roottale/cms-client": "^0.48.0",
65
+ "@roottale/cms-client": "^0.51.0",
61
66
  "@roottale/cms-core": "^0.46.0"
62
67
  },
63
68
  "peerDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/templates/expertise-client.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\n\nimport type { ExpertiseSection } from \"./site-config.js\";\nimport { useRovingTablist } from \"./use-roving-tablist.js\";\n\ntype ExpertiseItem = ExpertiseSection[\"props\"][\"items\"][number];\n\n/**\n * `expertise` `tab-stage` variant — 좌측 탭 목록(항상 세로 1열, templates.css\n * `.rt-tpl-expertise-tabs`) + 우측 상세 패널. 키보드 계약은 WAI-ARIA APG\n * tablist 패턴(roving tabindex, 단일 tab stop) — `use-roving-tablist.ts` 공용\n * hook 사용(D4, 적대 리뷰 R1 실측 지적 대응).\n */\nexport function ExpertiseTabStage({\n items,\n}: {\n readonly items: readonly ExpertiseItem[];\n}) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n const selected = items[selectedIndex] ?? items[0];\n\n const { getTabProps, onKeyDown } = useRovingTablist<HTMLButtonElement>({\n count: items.length,\n selectedIndex,\n onSelect: setSelectedIndex,\n // 레이아웃은 항상 세로 1열(templates.css)이지만 좌우 화살표도 관대하게\n // 허용 — 반응형 변형이나 향후 가로 배치 재사용에도 동일 훅이 그대로 맞는다.\n orientation: \"both\",\n });\n\n if (!selected) return null;\n\n return (\n <div className=\"rt-tpl-expertise-stage\">\n <div\n className=\"rt-tpl-expertise-tabs\"\n role=\"tablist\"\n aria-label=\"전문 분야 선택\"\n aria-orientation=\"vertical\"\n onKeyDown={onKeyDown}\n >\n {items.map((item, index) => {\n const active = index === selectedIndex;\n const tabProps = getTabProps(index);\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"tab\"\n className=\"rt-tpl-expertise-tab\"\n aria-selected={active}\n aria-controls={`rt-expertise-panel-${item.id}`}\n id={`rt-expertise-tab-${item.id}`}\n data-active={active ? \"true\" : \"false\"}\n tabIndex={tabProps.tabIndex}\n ref={tabProps.ref}\n onClick={() => setSelectedIndex(index)}\n >\n <span className=\"rt-tpl-expertise-tab-label\">{item.label}</span>\n <span className=\"rt-tpl-expertise-tab-title\">{item.title}</span>\n </button>\n );\n })}\n </div>\n <article\n className=\"rt-tpl-expertise-panel\"\n role=\"tabpanel\"\n id={`rt-expertise-panel-${selected.id}`}\n aria-labelledby={`rt-expertise-tab-${selected.id}`}\n tabIndex={0}\n >\n {selected.imageSrc ? (\n <img\n className=\"rt-tpl-expertise-panel-img\"\n src={selected.imageSrc}\n alt={selected.imageAlt ?? \"\"}\n loading=\"lazy\"\n />\n ) : null}\n <div className=\"rt-tpl-expertise-panel-body\">\n {selected.kicker ? (\n <p className=\"rt-tpl-expertise-kicker\">{selected.kicker}</p>\n ) : null}\n <h3 className=\"rt-tpl-expertise-title\">{selected.title}</h3>\n <p className=\"rt-tpl-expertise-body\">{selected.body}</p>\n {selected.tags ? (\n <ul className=\"rt-tpl-expertise-tags\" aria-label={`${selected.title} 태그`}>\n {selected.tags.map((tag) => (\n <li key={tag}>{tag}</li>\n ))}\n </ul>\n ) : null}\n {selected.href ? (\n <a className=\"rt-tpl-expertise-cta\" href={selected.href}>\n {selected.ctaLabel ?? \"자세히 보기\"}\n </a>\n ) : null}\n </div>\n </article>\n </div>\n );\n}\n"],"mappings":";;;;;AAEA,SAAS,gBAAgB;AA6Cb,SAaE,KAbF;AAhCL,SAAS,kBAAkB;AAAA,EAChC;AACF,GAEG;AACD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,CAAC;AACpD,QAAM,WAAW,MAAM,aAAa,KAAK,MAAM,CAAC;AAEhD,QAAM,EAAE,aAAa,UAAU,IAAI,iBAAoC;AAAA,IACrE,OAAO,MAAM;AAAA,IACb;AAAA,IACA,UAAU;AAAA;AAAA;AAAA,IAGV,aAAa;AAAA,EACf,CAAC;AAED,MAAI,CAAC,SAAU,QAAO;AAEtB,SACE,qBAAC,SAAI,WAAU,0BACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAW;AAAA,QACX,oBAAiB;AAAA,QACjB;AAAA,QAEC,gBAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,gBAAM,SAAS,UAAU;AACzB,gBAAM,WAAW,YAAY,KAAK;AAClC,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,MAAK;AAAA,cACL,WAAU;AAAA,cACV,iBAAe;AAAA,cACf,iBAAe,sBAAsB,KAAK,EAAE;AAAA,cAC5C,IAAI,oBAAoB,KAAK,EAAE;AAAA,cAC/B,eAAa,SAAS,SAAS;AAAA,cAC/B,UAAU,SAAS;AAAA,cACnB,KAAK,SAAS;AAAA,cACd,SAAS,MAAM,iBAAiB,KAAK;AAAA,cAErC;AAAA,oCAAC,UAAK,WAAU,8BAA8B,eAAK,OAAM;AAAA,gBACzD,oBAAC,UAAK,WAAU,8BAA8B,eAAK,OAAM;AAAA;AAAA;AAAA,YAbpD,KAAK;AAAA,UAcZ;AAAA,QAEJ,CAAC;AAAA;AAAA,IACH;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,MAAK;AAAA,QACL,IAAI,sBAAsB,SAAS,EAAE;AAAA,QACrC,mBAAiB,oBAAoB,SAAS,EAAE;AAAA,QAChD,UAAU;AAAA,QAET;AAAA,mBAAS,WACR;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,KAAK,SAAS;AAAA,cACd,KAAK,SAAS,YAAY;AAAA,cAC1B,SAAQ;AAAA;AAAA,UACV,IACE;AAAA,UACJ,qBAAC,SAAI,WAAU,+BACZ;AAAA,qBAAS,SACR,oBAAC,OAAE,WAAU,2BAA2B,mBAAS,QAAO,IACtD;AAAA,YACJ,oBAAC,QAAG,WAAU,0BAA0B,mBAAS,OAAM;AAAA,YACvD,oBAAC,OAAE,WAAU,yBAAyB,mBAAS,MAAK;AAAA,YACnD,SAAS,OACR,oBAAC,QAAG,WAAU,yBAAwB,cAAY,GAAG,SAAS,KAAK,iBAChE,mBAAS,KAAK,IAAI,CAAC,QAClB,oBAAC,QAAc,iBAAN,GAAU,CACpB,GACH,IACE;AAAA,YACH,SAAS,OACR,oBAAC,OAAE,WAAU,wBAAuB,MAAM,SAAS,MAChD,mBAAS,YAAY,mCACxB,IACE;AAAA,aACN;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
@@ -1,103 +0,0 @@
1
- "use client";
2
-
3
- // src/templates/team-expanding-stage-client.tsx
4
- import { useState } from "react";
5
- import { useReducedMotion } from "motion/react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
- function memberInitials(name) {
8
- const parts = name.trim().split(/\s+/).filter(Boolean);
9
- if (parts.length === 0) return "?";
10
- const initials = parts.slice(0, 2).map((part) => part.at(0) ?? "").join("");
11
- return initials || "?";
12
- }
13
- function TeamExpandingStageClient({
14
- members
15
- }) {
16
- const [activeIndex, setActiveIndex] = useState(0);
17
- const reducedMotion = useReducedMotion();
18
- if (members.length === 0) return null;
19
- const activate = (index) => setActiveIndex(index);
20
- const onItemKeyDown = (event, index) => {
21
- if ((event.key === "Enter" || event.key === " ") && index !== activeIndex) {
22
- event.preventDefault();
23
- activate(index);
24
- }
25
- };
26
- return /* @__PURE__ */ jsx(
27
- "ul",
28
- {
29
- className: "rt-tpl-team-expanding-stage",
30
- "data-reduced-motion": reducedMotion ? "true" : "false",
31
- children: members.map((member, index) => {
32
- const active = index === activeIndex;
33
- return /* @__PURE__ */ jsxs(
34
- "li",
35
- {
36
- className: "rt-tpl-team-expanding-item",
37
- "data-active": active ? "true" : "false",
38
- tabIndex: 0,
39
- onMouseEnter: () => activate(index),
40
- onFocus: () => activate(index),
41
- onClick: (event) => {
42
- if (!active) event.preventDefault();
43
- activate(index);
44
- },
45
- onKeyDown: (event) => onItemKeyDown(event, index),
46
- children: [
47
- /* @__PURE__ */ jsx("span", { className: "rt-tpl-team-expanding-index", "aria-hidden": "true", children: String(index + 1).padStart(2, "0") }),
48
- member.photoSrc ? /* @__PURE__ */ jsx(
49
- "img",
50
- {
51
- className: "rt-tpl-team-expanding-photo",
52
- src: member.photoSrc,
53
- alt: member.photoAlt ?? "",
54
- loading: "lazy"
55
- }
56
- ) : /* @__PURE__ */ jsx(
57
- "div",
58
- {
59
- className: "rt-tpl-team-expanding-photo rt-tpl-team-expanding-photo--initials",
60
- "aria-hidden": "true",
61
- children: memberInitials(member.name)
62
- }
63
- ),
64
- /* @__PURE__ */ jsxs("div", { className: "rt-tpl-team-expanding-name", "aria-hidden": "true", children: [
65
- /* @__PURE__ */ jsx("b", { children: member.name }),
66
- /* @__PURE__ */ jsx("span", { children: member.title })
67
- ] }),
68
- /* @__PURE__ */ jsxs("div", { className: "rt-tpl-team-expanding-detail", children: [
69
- /* @__PURE__ */ jsx("p", { className: "rt-tpl-team-expanding-role", children: member.title }),
70
- /* @__PURE__ */ jsx("h3", { className: "rt-tpl-team-name", children: member.name }),
71
- member.specialties ? /* @__PURE__ */ jsx(
72
- "ul",
73
- {
74
- className: "rt-tpl-team-specialties",
75
- "aria-label": `${member.name} \uC804\uBB38 \uBD84\uC57C`,
76
- children: member.specialties.map((specialty) => /* @__PURE__ */ jsx("li", { children: specialty }, specialty))
77
- }
78
- ) : null,
79
- member.credentials ? /* @__PURE__ */ jsx("ul", { className: "rt-tpl-team-credentials", children: member.credentials.map((credential) => /* @__PURE__ */ jsx("li", { children: credential }, credential)) }) : null,
80
- member.bio ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-team-bio", children: member.bio }) : null,
81
- member.href ? /* @__PURE__ */ jsx(
82
- "a",
83
- {
84
- className: "rt-tpl-team-cta",
85
- href: member.href,
86
- tabIndex: active ? 0 : -1,
87
- children: member.ctaLabel ?? "\uD504\uB85C\uD544 \uBCF4\uAE30"
88
- }
89
- ) : null
90
- ] })
91
- ]
92
- },
93
- member.name
94
- );
95
- })
96
- }
97
- );
98
- }
99
-
100
- export {
101
- TeamExpandingStageClient
102
- };
103
- //# sourceMappingURL=chunk-FZK3EXYZ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/templates/team-expanding-stage-client.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, type KeyboardEvent, type ReactElement } from \"react\";\nimport { useReducedMotion } from \"motion/react\";\n\nimport type { TeamSection } from \"./site-config.js\";\n\ntype TeamMember = TeamSection[\"props\"][\"members\"][number];\n\nfunction memberInitials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"?\";\n const initials = parts\n .slice(0, 2)\n .map((part) => part.at(0) ?? \"\")\n .join(\"\");\n return initials || \"?\";\n}\n\n/**\n * `team` `expanding-stage` variant 의 실모션 client island (D3 크래프트).\n * chambarun `.staff-stage`/`.staff-panel` 스펙(mockup/assets/site.js·site.css)\n * 이식 — hover/focus/tap 한 인물이 확장되며 자격 정보가 드러난다.\n *\n * 현재 `sections.tsx` `TemplateTeamExpandingStage` 는 이 컴포넌트를 아직\n * import 하지 않는다(정적 격자만 렌더) — `sections.tsx` 는 이 작업 소유 밖이라\n * 와이어링은 별도 보고 대상(README/보고 참조). 이 파일은 그 자리에 그대로\n * 끼워 넣을 수 있도록 `members` 하나만 받는 독립 컴포넌트로 설계했다.\n *\n * 접근성(계약, DESIGN.md \"Medical Staff Cards\" 원칙 확장):\n * - 각 패널은 `tabIndex=0` 컨테이너(hover/focus/click 시 확장) — chambarun\n * 원본과 동일하게 명시적 role 은 두지 않는다(내부에 실제 CTA 링크가 있어\n * `role=\"button\"` 을 얹으면 중첩 인터랙티브 요소가 된다).\n * - 자격정보·경력·CTA 는 접힌 상태에서도 DOM 에 그대로 존재한다(opacity 로만\n * 가림) — 스크린리더는 hover 없이 전체 내용을 읽을 수 있다(\"decorative hover\n * layers do not replace content\").\n * - CTA 링크는 비활성 패널일 때 `tabIndex=-1` — 보이지 않는 링크가 키보드\n * 포커스를 가로채지 않는다(chambarun 원본에는 없던, React 상태로만 가능한\n * 개선).\n * - `prefers-reduced-motion: reduce` → 전환 자체가 CSS 에서 제거된다\n * (templates.css, `@media (prefers-reduced-motion: no-preference)` 안에만\n * transition 선언 — ADR-0073 K3 C1 과 동일 관행). `data-reduced-motion`\n * 속성은 시각 회귀 테스트/QA 스크린샷 식별용.\n */\nexport function TeamExpandingStageClient({\n members,\n}: {\n readonly members: readonly TeamMember[];\n}): ReactElement | null {\n const [activeIndex, setActiveIndex] = useState(0);\n const reducedMotion = useReducedMotion();\n\n if (members.length === 0) return null;\n\n const activate = (index: number) => setActiveIndex(index);\n\n const onItemKeyDown = (event: KeyboardEvent<HTMLLIElement>, index: number) => {\n if ((event.key === \"Enter\" || event.key === \" \") && index !== activeIndex) {\n event.preventDefault();\n activate(index);\n }\n };\n\n return (\n <ul\n className=\"rt-tpl-team-expanding-stage\"\n data-reduced-motion={reducedMotion ? \"true\" : \"false\"}\n >\n {members.map((member, index) => {\n const active = index === activeIndex;\n return (\n <li\n key={member.name}\n className=\"rt-tpl-team-expanding-item\"\n data-active={active ? \"true\" : \"false\"}\n tabIndex={0}\n onMouseEnter={() => activate(index)}\n onFocus={() => activate(index)}\n onClick={(event) => {\n // 첫 탭(hover/focus 없이 바로 터치)은 확장만 — CTA 로 즉시\n // 이동하지 않는다. 이미 활성(=두 번째 탭)이면 기본 클릭 동작\n // (링크 없는 li 자체는 아무 것도 안 함, 내부 CTA 는 그대로 이동).\n if (!active) event.preventDefault();\n activate(index);\n }}\n onKeyDown={(event) => onItemKeyDown(event, index)}\n >\n <span className=\"rt-tpl-team-expanding-index\" aria-hidden=\"true\">\n {String(index + 1).padStart(2, \"0\")}\n </span>\n {member.photoSrc ? (\n <img\n className=\"rt-tpl-team-expanding-photo\"\n src={member.photoSrc}\n alt={member.photoAlt ?? \"\"}\n loading=\"lazy\"\n />\n ) : (\n <div\n className=\"rt-tpl-team-expanding-photo rt-tpl-team-expanding-photo--initials\"\n aria-hidden=\"true\"\n >\n {memberInitials(member.name)}\n </div>\n )}\n <div className=\"rt-tpl-team-expanding-name\" aria-hidden=\"true\">\n <b>{member.name}</b>\n <span>{member.title}</span>\n </div>\n <div className=\"rt-tpl-team-expanding-detail\">\n <p className=\"rt-tpl-team-expanding-role\">{member.title}</p>\n <h3 className=\"rt-tpl-team-name\">{member.name}</h3>\n {member.specialties ? (\n <ul\n className=\"rt-tpl-team-specialties\"\n aria-label={`${member.name} 전문 분야`}\n >\n {member.specialties.map((specialty) => (\n <li key={specialty}>{specialty}</li>\n ))}\n </ul>\n ) : null}\n {member.credentials ? (\n <ul className=\"rt-tpl-team-credentials\">\n {member.credentials.map((credential) => (\n <li key={credential}>{credential}</li>\n ))}\n </ul>\n ) : null}\n {member.bio ? <p className=\"rt-tpl-team-bio\">{member.bio}</p> : null}\n {member.href ? (\n <a\n className=\"rt-tpl-team-cta\"\n href={member.href}\n tabIndex={active ? 0 : -1}\n >\n {member.ctaLabel ?? \"프로필 보기\"}\n </a>\n ) : null}\n </div>\n </li>\n );\n })}\n </ul>\n );\n}\n"],"mappings":"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","names":[]}