@flamingo-stack/openframe-frontend-core 0.0.265 → 0.0.266-snapshot.20260613153054

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 (104) hide show
  1. package/dist/{chunk-J5R3QIOJ.cjs → chunk-5QJQAFTP.cjs} +126 -55
  2. package/dist/chunk-5QJQAFTP.cjs.map +1 -0
  3. package/dist/chunk-722TTRIJ.cjs +29 -0
  4. package/dist/chunk-722TTRIJ.cjs.map +1 -0
  5. package/dist/{chunk-A56L7UQG.cjs → chunk-AA77CC2Y.cjs} +12 -12
  6. package/dist/{chunk-A56L7UQG.cjs.map → chunk-AA77CC2Y.cjs.map} +1 -1
  7. package/dist/{chunk-J5BBBDNA.cjs → chunk-CI5XE4LB.cjs} +37 -37
  8. package/dist/{chunk-J5BBBDNA.cjs.map → chunk-CI5XE4LB.cjs.map} +1 -1
  9. package/dist/{chunk-7MNBE7XA.cjs → chunk-CICK3FUU.cjs} +102 -4
  10. package/dist/chunk-CICK3FUU.cjs.map +1 -0
  11. package/dist/{chunk-GBUGYHDY.js → chunk-CYMMGPPV.js} +125 -54
  12. package/dist/chunk-CYMMGPPV.js.map +1 -0
  13. package/dist/{chunk-Z5YEQWLC.js → chunk-GJVSLS2R.js} +5 -7
  14. package/dist/{chunk-Z5YEQWLC.js.map → chunk-GJVSLS2R.js.map} +1 -1
  15. package/dist/{chunk-URJQGMNH.js → chunk-ICOMPUMX.js} +345 -247
  16. package/dist/chunk-ICOMPUMX.js.map +1 -0
  17. package/dist/{chunk-EXU5N5RM.js → chunk-J6O57C7S.js} +2461 -2546
  18. package/dist/chunk-J6O57C7S.js.map +1 -0
  19. package/dist/{chunk-2P6V2EWZ.cjs → chunk-O6UA475M.cjs} +302 -387
  20. package/dist/chunk-O6UA475M.cjs.map +1 -0
  21. package/dist/chunk-OO4ZGDRO.js +29 -0
  22. package/dist/chunk-OO4ZGDRO.js.map +1 -0
  23. package/dist/{chunk-TZ3PV5NT.js → chunk-P7JHUX4X.js} +2 -2
  24. package/dist/{chunk-NR535XIY.js → chunk-T2URJAX2.js} +66 -1
  25. package/dist/chunk-T2URJAX2.js.map +1 -0
  26. package/dist/{chunk-HMHMK6U6.cjs → chunk-TPZI7NBN.cjs} +16 -18
  27. package/dist/chunk-TPZI7NBN.cjs.map +1 -0
  28. package/dist/{chunk-266DUZ6D.js → chunk-XSK72QRR.js} +6 -3
  29. package/dist/chunk-XSK72QRR.js.map +1 -0
  30. package/dist/{chunk-7VMEOFEA.cjs → chunk-Y2B6WDU6.cjs} +67 -2
  31. package/dist/chunk-Y2B6WDU6.cjs.map +1 -0
  32. package/dist/{chunk-D4TWJNO5.cjs → chunk-YV55JZAD.cjs} +29 -26
  33. package/dist/chunk-YV55JZAD.cjs.map +1 -0
  34. package/dist/{chunk-6A2YQ5CY.js → chunk-Z6MKG53Z.js} +4 -4
  35. package/dist/components/chat/index.cjs +5 -5
  36. package/dist/components/chat/index.js +4 -4
  37. package/dist/components/contact/contact-form.d.ts.map +1 -1
  38. package/dist/components/contact/index.cjs +6 -6
  39. package/dist/components/contact/index.js +5 -5
  40. package/dist/components/faq/faq-section.d.ts +20 -23
  41. package/dist/components/faq/faq-section.d.ts.map +1 -1
  42. package/dist/components/faq/index.cjs +4 -4
  43. package/dist/components/faq/index.js +3 -3
  44. package/dist/components/faq-accordion.d.ts +0 -5
  45. package/dist/components/faq-accordion.d.ts.map +1 -1
  46. package/dist/components/features/entity-video-section.d.ts.map +1 -1
  47. package/dist/components/features/index.cjs +7 -8
  48. package/dist/components/features/index.cjs.map +1 -1
  49. package/dist/components/features/index.js +6 -7
  50. package/dist/components/features/video-bites-display.d.ts.map +1 -1
  51. package/dist/components/index.cjs +85 -83
  52. package/dist/components/index.cjs.map +1 -1
  53. package/dist/components/index.js +16 -14
  54. package/dist/components/index.js.map +1 -1
  55. package/dist/components/layout/page-heading.d.ts +8 -0
  56. package/dist/components/layout/page-heading.d.ts.map +1 -1
  57. package/dist/components/navigation/index.cjs +5 -5
  58. package/dist/components/navigation/index.js +4 -4
  59. package/dist/components/onboarding-guides/index.cjs +24 -25
  60. package/dist/components/onboarding-guides/index.cjs.map +1 -1
  61. package/dist/components/onboarding-guides/index.js +4 -5
  62. package/dist/components/onboarding-guides/index.js.map +1 -1
  63. package/dist/components/open-source-features.d.ts.map +1 -1
  64. package/dist/components/related-content/index.cjs +6 -6
  65. package/dist/components/related-content/index.js +5 -5
  66. package/dist/components/shared/onboarding/onboarding-walkthrough.d.ts.map +1 -1
  67. package/dist/components/tickets/index.cjs +69 -69
  68. package/dist/components/tickets/index.cjs.map +1 -1
  69. package/dist/components/tickets/index.js +10 -10
  70. package/dist/components/ui/index.cjs +9 -7
  71. package/dist/components/ui/index.cjs.map +1 -1
  72. package/dist/components/ui/index.js +10 -8
  73. package/dist/components/why-it-matters.d.ts.map +1 -1
  74. package/dist/hooks/use-toast.d.ts +1 -1
  75. package/dist/index.cjs +11 -9
  76. package/dist/index.cjs.map +1 -1
  77. package/dist/index.js +13 -11
  78. package/package.json +1 -1
  79. package/src/components/contact/contact-form.tsx +2 -1
  80. package/src/components/faq/faq-section.tsx +212 -103
  81. package/src/components/faq-accordion.tsx +1 -15
  82. package/src/components/features/entity-video-section.tsx +2 -1
  83. package/src/components/features/video-bites-display.tsx +2 -1
  84. package/src/components/layout/page-heading.tsx +9 -0
  85. package/src/components/open-source-features.tsx +3 -2
  86. package/src/components/shared/onboarding/onboarding-walkthrough.tsx +2 -1
  87. package/src/components/why-it-matters.tsx +3 -2
  88. package/dist/chunk-266DUZ6D.js.map +0 -1
  89. package/dist/chunk-2P6V2EWZ.cjs.map +0 -1
  90. package/dist/chunk-7MNBE7XA.cjs.map +0 -1
  91. package/dist/chunk-7VMEOFEA.cjs.map +0 -1
  92. package/dist/chunk-D4TWJNO5.cjs.map +0 -1
  93. package/dist/chunk-ERFO4JFK.js +0 -104
  94. package/dist/chunk-ERFO4JFK.js.map +0 -1
  95. package/dist/chunk-EXU5N5RM.js.map +0 -1
  96. package/dist/chunk-GBUGYHDY.js.map +0 -1
  97. package/dist/chunk-HMHMK6U6.cjs.map +0 -1
  98. package/dist/chunk-J5R3QIOJ.cjs.map +0 -1
  99. package/dist/chunk-NR535XIY.js.map +0 -1
  100. package/dist/chunk-TT2VBR7P.cjs +0 -104
  101. package/dist/chunk-TT2VBR7P.cjs.map +0 -1
  102. package/dist/chunk-URJQGMNH.js.map +0 -1
  103. /package/dist/{chunk-TZ3PV5NT.js.map → chunk-P7JHUX4X.js.map} +0 -0
  104. /package/dist/{chunk-6A2YQ5CY.js.map → chunk-Z6MKG53Z.js.map} +0 -0
@@ -6,11 +6,11 @@ var _chunkT7AJBKZPcjs = require('./chunk-T7AJBKZP.cjs');
6
6
 
7
7
 
8
8
 
9
- var _chunk7VMEOFEAcjs = require('./chunk-7VMEOFEA.cjs');
10
9
 
10
+ var _chunkY2B6WDU6cjs = require('./chunk-Y2B6WDU6.cjs');
11
11
 
12
12
 
13
- var _chunkTT2VBR7Pcjs = require('./chunk-TT2VBR7P.cjs');
13
+ var _chunk722TTRIJcjs = require('./chunk-722TTRIJ.cjs');
14
14
 
15
15
 
16
16
 
@@ -74,19 +74,9 @@ function FaqAccordion({ items, defaultOpenIds = [] }) {
74
74
  "aria-expanded": isOpen,
75
75
  className: "flex w-full items-center justify-between px-6 md:px-8 py-6 text-left focus:outline-none transition-colors cursor-pointer",
76
76
  children: [
77
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex min-w-0 flex-col items-start gap-2 pr-4", children: [
78
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { children: item.question }),
79
- item.badge && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
80
- _chunkTT2VBR7Pcjs.StatusBadge,
81
- {
82
- text: item.badge.toUpperCase(),
83
- variant: "card",
84
- className: _chunkTT2VBR7Pcjs.TAG_BADGE_CLASS
85
- }
86
- )
87
- ] }),
77
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "min-w-0 pr-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { children: item.question }) }),
88
78
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex-shrink-0", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
89
- _chunk7VMEOFEAcjs.ChevronButton,
79
+ _chunkY2B6WDU6cjs.ChevronButton,
90
80
  {
91
81
  "aria-label": isOpen ? "Collapse question" : "Expand question",
92
82
  size: "md",
@@ -113,6 +103,9 @@ function FaqAccordion({ items, defaultOpenIds = [] }) {
113
103
  }) });
114
104
  }
115
105
 
106
+ // src/components/faq/faq-section.tsx
107
+ _chunkFIG2RKZFcjs.init_cn.call(void 0, );
108
+
116
109
  // src/components/faq/json-ld.ts
117
110
  var DEFAULT_NAME = "Frequently Asked Questions";
118
111
  var DEFAULT_DESCRIPTION = "Answers to common questions.";
@@ -143,15 +136,114 @@ function buildFaqJsonLdFromFaqs(faqs, opts = {}) {
143
136
 
144
137
  var DEFAULT_HEADING_TEXT = "Frequently Asked Questions";
145
138
  function buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl = "") {
146
- return _chunk7VMEOFEAcjs.buildSuggestionUrl.call(void 0, "/api/faqs", { apiBaseUrl, entityType, entityId, count: minResults });
139
+ return _chunkY2B6WDU6cjs.buildSuggestionUrl.call(void 0, "/api/faqs", { apiBaseUrl, entityType, entityId, count: minResults });
140
+ }
141
+ function sectionSlug(section) {
142
+ return "faq-" + section.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
143
+ }
144
+ function groupFaqsBySection(faqs) {
145
+ const order = [];
146
+ const byName = /* @__PURE__ */ new Map();
147
+ let uncategorized = null;
148
+ for (const faq of faqs) {
149
+ const item = { id: faq.id, question: faq.question, answer: faq.answer };
150
+ const name = _optionalChain([faq, 'access', _2 => _2.section, 'optionalAccess', _3 => _3.trim, 'call', _4 => _4()]);
151
+ if (!name) {
152
+ if (!uncategorized) uncategorized = { section: null, slug: null, items: [] };
153
+ uncategorized.items.push(item);
154
+ continue;
155
+ }
156
+ let group = byName.get(name);
157
+ if (!group) {
158
+ group = { section: name, slug: sectionSlug(name), items: [] };
159
+ byName.set(name, group);
160
+ order.push(name);
161
+ }
162
+ group.items.push(item);
163
+ }
164
+ const groups = order.map((name) => byName.get(name));
165
+ if (uncategorized) groups.push(uncategorized);
166
+ return groups;
147
167
  }
148
- function toAccordionItems(faqs) {
149
- return faqs.map((faq) => ({
150
- id: faq.id,
151
- question: faq.question,
152
- answer: faq.answer,
153
- badge: faq.section || void 0
154
- }));
168
+ var FAQ_NAV_HEADER_OFFSET = 96;
169
+ function GroupedFaqList({
170
+ groups,
171
+ categoryHeadingAs
172
+ }) {
173
+ const CategoryHeading = categoryHeadingAs;
174
+ const navGroups = _react.useMemo.call(void 0, () => groups.filter((g) => g.slug), [groups]);
175
+ const [activeSlug, setActiveSlug] = _react.useState.call(void 0, _nullishCoalesce(_optionalChain([navGroups, 'access', _5 => _5[0], 'optionalAccess', _6 => _6.slug]), () => ( null)));
176
+ const slugKey = navGroups.map((g) => g.slug).join("|");
177
+ _react.useEffect.call(void 0, () => {
178
+ if (navGroups.length < 2) return;
179
+ const tops = /* @__PURE__ */ new Map();
180
+ const observer = new IntersectionObserver(
181
+ (entries) => {
182
+ for (const entry of entries) {
183
+ const id = entry.target.id;
184
+ if (entry.isIntersecting) tops.set(id, entry.boundingClientRect.top);
185
+ else tops.delete(id);
186
+ }
187
+ let bestId = null;
188
+ let bestTop = Number.POSITIVE_INFINITY;
189
+ for (const [id, top] of tops) {
190
+ if (top < bestTop) {
191
+ bestTop = top;
192
+ bestId = id;
193
+ }
194
+ }
195
+ if (bestId) setActiveSlug(bestId);
196
+ },
197
+ { rootMargin: `-${FAQ_NAV_HEADER_OFFSET}px 0px -55% 0px`, threshold: 0 }
198
+ );
199
+ for (const group of navGroups) {
200
+ const el = group.slug ? document.getElementById(group.slug) : null;
201
+ if (el) observer.observe(el);
202
+ }
203
+ return () => observer.disconnect();
204
+ }, [slugKey]);
205
+ const handleJump = _react.useCallback.call(void 0,
206
+ (e, slug) => {
207
+ e.preventDefault();
208
+ setActiveSlug(slug);
209
+ _chunkY2B6WDU6cjs.scrollElementIntoView.call(void 0, document.getElementById(slug), {
210
+ headerOffset: FAQ_NAV_HEADER_OFFSET
211
+ });
212
+ if (typeof history !== "undefined") history.replaceState(null, "", `#${slug}`);
213
+ },
214
+ []
215
+ );
216
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-8", children: [
217
+ navGroups.length > 1 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "nav", { "aria-label": "FAQ categories", className: "flex flex-wrap gap-2", children: navGroups.map((group) => {
218
+ const isActive = group.slug === activeSlug;
219
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
220
+ "a",
221
+ {
222
+ href: `#${group.slug}`,
223
+ "aria-current": isActive ? "true" : void 0,
224
+ onClick: (e) => handleJump(e, group.slug),
225
+ className: _chunkFIG2RKZFcjs.cn.call(void 0,
226
+ "rounded-full border px-4 py-2 text-sm font-medium font-['DM_Sans'] transition-colors",
227
+ isActive ? "border-ods-text-primary bg-ods-card text-ods-text-primary" : "border-ods-border bg-ods-card text-ods-text-secondary hover:border-ods-text-secondary hover:text-ods-text-primary"
228
+ ),
229
+ children: group.section
230
+ },
231
+ group.slug
232
+ );
233
+ }) }),
234
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "space-y-10", children: groups.map((group) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
235
+ "section",
236
+ {
237
+ id: _nullishCoalesce(group.slug, () => ( void 0)),
238
+ className: "scroll-mt-24 space-y-4",
239
+ children: [
240
+ group.section && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CategoryHeading, { className: _chunk722TTRIJcjs.SECTION_HEADING_CLASS, children: group.section }),
241
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FaqAccordion, { items: group.items })
242
+ ]
243
+ },
244
+ _nullishCoalesce(group.slug, () => ( "faq-uncategorized"))
245
+ )) })
246
+ ] });
155
247
  }
156
248
  function FaqSkeleton() {
157
249
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-8 animate-pulse", children: [
@@ -167,7 +259,6 @@ function FaqSection({
167
259
  entityType,
168
260
  entityId,
169
261
  heading,
170
- variant,
171
262
  emitJsonLd = false,
172
263
  jsonLd,
173
264
  className,
@@ -180,45 +271,25 @@ function FaqSection({
180
271
  [initialFaqs]
181
272
  );
182
273
  const { data, isLoading, error } = _chunkT7AJBKZPcjs.useSelfFetch.call(void 0, url, { initialData });
183
- const faqs = _nullishCoalesce(_optionalChain([data, 'optionalAccess', _2 => _2.faqs]), () => ( []));
184
- const isEmbedded = variant ? variant === "embedded" : heading === null;
185
- const headingNode = heading === void 0 ? isEmbedded ? (
186
- // Embedded default is an <h2> — a host page already owns the <h1>,
187
- // and the schema/heading pair must read "Frequently Asked Questions"
188
- // (one FAQ heading per page, never tag/section names).
189
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "text-h2 tracking-[-0.04em] text-ods-text-primary", children: DEFAULT_HEADING_TEXT })
190
- ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { className: "text-h1 tracking-[-0.04em] text-ods-text-primary", children: DEFAULT_HEADING_TEXT }) : heading;
191
- if (error && isEmbedded) {
192
- return null;
193
- }
194
- if (!isLoading && !error && faqs.length === 0 && isEmbedded) {
195
- return null;
196
- }
274
+ const faqs = _nullishCoalesce(_optionalChain([data, 'optionalAccess', _7 => _7.faqs]), () => ( []));
275
+ const groups = _react.useMemo.call(void 0, () => faqs.length > 0 ? groupFaqsBySection(faqs) : [], [faqs]);
276
+ const headingNode = heading === void 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: _chunk722TTRIJcjs.SECTION_HEADING_CLASS, children: DEFAULT_HEADING_TEXT }) : heading;
277
+ if (error) return null;
278
+ if (!isLoading && faqs.length === 0) return null;
197
279
  if (isLoading && faqs.length === 0) {
198
- if (isEmbedded) {
199
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FaqSkeleton, {}) });
200
- }
201
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "main", { className: _nullishCoalesce(className, () => ( "bg-ods-bg")), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FaqSkeleton, {}) }) });
202
- }
203
- if (error && faqs.length === 0) {
204
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "main", { className: _nullishCoalesce(className, () => ( "bg-ods-bg flex items-center justify-center py-20")), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-ods-text-primary font-medium", children: "Failed to load FAQs. Please try again later." }) });
280
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FaqSkeleton, {}) });
205
281
  }
206
282
  const schema = emitJsonLd ? buildFaqJsonLdFromFaqs(faqs, jsonLd) : null;
207
- const accordion = /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FaqAccordion, { items: toAccordionItems(faqs) });
208
- const body = isEmbedded ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "section", { className: _nullishCoalesce(className, () => ( "space-y-10")), children: [
209
- headingNode,
210
- accordion
211
- ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "main", { className: _nullishCoalesce(className, () => ( "bg-ods-bg")), children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto space-y-10", children: [
212
- headingNode,
213
- accordion
214
- ] }) });
215
283
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
216
- body,
284
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "section", { className: _nullishCoalesce(className, () => ( "space-y-10")), children: [
285
+ headingNode,
286
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, GroupedFaqList, { groups, categoryHeadingAs: heading === null ? "h2" : "h3" })
287
+ ] }),
217
288
  schema && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
218
289
  "script",
219
290
  {
220
291
  type: "application/ld+json",
221
- dangerouslySetInnerHTML: { __html: _chunk7VMEOFEAcjs.serializeJsonLd.call(void 0, schema) }
292
+ dangerouslySetInnerHTML: { __html: _chunkY2B6WDU6cjs.serializeJsonLd.call(void 0, schema) }
222
293
  }
223
294
  )
224
295
  ] });
@@ -228,4 +299,4 @@ function FaqSection({
228
299
 
229
300
 
230
301
  exports.FaqAccordion = FaqAccordion; exports.FaqSection = FaqSection;
231
- //# sourceMappingURL=chunk-J5R3QIOJ.cjs.map
302
+ //# sourceMappingURL=chunk-5QJQAFTP.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-5QJQAFTP.cjs","../src/components/faq/faq-section.tsx","../src/components/faq-accordion.tsx","../src/components/faq/json-ld.ts"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-5QJQAFTP.cjs","sourcesContent":[null,"\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { Faq } from '../../types/faq'\nimport { FaqAccordion, type FaqItem } from '../faq-accordion'\nimport { useSelfFetch } from '../../hooks/use-self-fetch'\nimport { buildSuggestionUrl } from '../../utils/suggestion-url'\nimport { serializeJsonLd } from '../../utils/common'\nimport { scrollElementIntoView } from '../../utils/scroll-into-view'\nimport { cn } from '../../utils/cn'\nimport { buildFaqJsonLdFromFaqs, type FaqSchemaOptions } from './json-ld'\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading'\n\nexport interface FaqSectionProps {\n /**\n * SSR hydrate. When provided, the hook skips the first client fetch (per\n * useSelfFetch contract). The consuming server page resolves FAQs then drills\n * them into this prop — the lib never re-fetches what the host already gated on.\n */\n initialFaqs?: Faq[]\n /** Both required together for entity-attached FAQs; partial → bare /api/faqs. */\n entityType?: string\n entityId?: number | string\n /**\n * Heading node above the grouped list. `undefined` → default\n * `<h2>`\"Frequently Asked Questions\". `null` → no heading (the host page\n * owns the `<h1>`, as the standalone /faqs surface does). A React node lets a\n * platform drill its own <PageHeading> without the lib referencing platform\n * state. Also drives category nesting so the document outline stays correct:\n * `null` → categories render `<h2>` (directly under the page `<h1>`);\n * otherwise categories render `<h3>` beneath this heading.\n */\n heading?: React.ReactNode | null\n /** Inject FAQPage schema.org JSON-LD as a <script>. Off by default so embeds\n * don't emit duplicate schema. */\n emitJsonLd?: boolean\n /** Overrides for the JSON-LD's name/description/url. */\n jsonLd?: FaqSchemaOptions\n className?: string\n /** Maps to /api/faqs `?count=` (the 5-tier fill target). Absent → param\n * not sent (server default applies). */\n minResults?: number\n /** Fetch-URL prefix for third-party embeds / reverse proxies\n * ('' = same-origin relative). */\n apiBaseUrl?: string\n}\n\nconst DEFAULT_HEADING_TEXT = 'Frequently Asked Questions'\n\n/** URL composition shared with RelatedContentSection (`buildSuggestionUrl`)\n * — byte-identical to the historical `buildFaqsUrl` output when\n * `minResults`/`apiBaseUrl` are absent. */\nfunction buildFaqsUrl(\n entityType?: string,\n entityId?: number | string,\n minResults?: number,\n apiBaseUrl = '',\n): string {\n return buildSuggestionUrl('/api/faqs', { apiBaseUrl, entityType, entityId, count: minResults })\n}\n\n/** Stable, URL-safe anchor id for a category. Prefixed so it can't collide\n * with other in-page ids, and so a bare numeric/blank section still yields a\n * valid id. */\nfunction sectionSlug(section: string): string {\n return (\n 'faq-' +\n section\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n )\n}\n\ninterface FaqGroup {\n /** null → the uncategorized bucket: no heading, no jump pill, rendered last. */\n section: string | null\n slug: string | null\n items: FaqItem[]\n}\n\n/** Group FAQs by `faq.section`, preserving the server's first-seen\n * (display_order) order for BOTH the section order and the rows within each\n * section. The uncategorized bucket (blank/missing section) sinks to the end\n * since it renders without a heading. Items carry NO badge here — the `<h2>`\n * IS the category, so a per-row chip would be redundant. */\nfunction groupFaqsBySection(faqs: Faq[]): FaqGroup[] {\n const order: string[] = []\n const byName = new Map<string, FaqGroup>()\n let uncategorized: FaqGroup | null = null\n for (const faq of faqs) {\n const item: FaqItem = { id: faq.id, question: faq.question, answer: faq.answer }\n const name = faq.section?.trim()\n if (!name) {\n if (!uncategorized) uncategorized = { section: null, slug: null, items: [] }\n uncategorized.items.push(item)\n continue\n }\n let group = byName.get(name)\n if (!group) {\n group = { section: name, slug: sectionSlug(name), items: [] }\n byName.set(name, group)\n order.push(name)\n }\n group.items.push(item)\n }\n const groups = order.map((name) => byName.get(name)!)\n if (uncategorized) groups.push(uncategorized)\n return groups\n}\n\n/** The standard hub sticky-header height — same offset `useNavLink`'s hash\n * scroll uses, so a category jump lands below the header, not under it. */\nconst FAQ_NAV_HEADER_OFFSET = 96\n\n/**\n * Grouped FAQ layout: a category jump-nav above stacked `<h2>` category\n * sections (each its own accordion). Isolated into its own component so the\n * scroll-spy hooks only mount in grouped mode — `FaqSection`'s own hooks stay\n * unconditional.\n *\n * The pills are real `<a href=\"#slug\">` anchors (crawlable in-page links,\n * deep-linkable, work without JS); the click handler upgrades the jump to the\n * cancellation-proof `scrollElementIntoView` tween and syncs the URL hash.\n */\nfunction GroupedFaqList({\n groups,\n categoryHeadingAs,\n}: {\n groups: FaqGroup[]\n /** Heading tag for each category, so the document outline nests correctly\n * under whatever owns the heading above this block: `h2` on the standalone\n * page (the page owns the `<h1>`), `h3` beneath an embed's `<h2>` title.\n * The VISUAL is `SECTION_HEADING_CLASS` either way, so categories look\n * identical on every surface. */\n categoryHeadingAs: 'h2' | 'h3'\n}) {\n const CategoryHeading = categoryHeadingAs\n const navGroups = useMemo(() => groups.filter((g) => g.slug), [groups])\n const [activeSlug, setActiveSlug] = useState<string | null>(navGroups[0]?.slug ?? null)\n // Identity-stable key for the section set so the observer re-binds only when\n // the categories actually change (not on every parent re-render).\n const slugKey = navGroups.map((g) => g.slug).join('|')\n\n // Scroll-spy: mark the pill for the category currently at the top of the\n // viewport. rootMargin drops the trigger line just below the sticky header\n // and ignores the bottom ~55% so \"active\" is the section being read, not the\n // next one peeking in.\n useEffect(() => {\n if (navGroups.length < 2) return\n const tops = new Map<string, number>()\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).id\n if (entry.isIntersecting) tops.set(id, entry.boundingClientRect.top)\n else tops.delete(id)\n }\n let bestId: string | null = null\n let bestTop = Number.POSITIVE_INFINITY\n for (const [id, top] of tops) {\n if (top < bestTop) {\n bestTop = top\n bestId = id\n }\n }\n if (bestId) setActiveSlug(bestId)\n },\n { rootMargin: `-${FAQ_NAV_HEADER_OFFSET}px 0px -55% 0px`, threshold: 0 },\n )\n for (const group of navGroups) {\n const el = group.slug ? document.getElementById(group.slug) : null\n if (el) observer.observe(el)\n }\n return () => observer.disconnect()\n // slugKey encodes the section set; re-observe only when it changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slugKey])\n\n const handleJump = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>, slug: string) => {\n e.preventDefault()\n setActiveSlug(slug)\n scrollElementIntoView(document.getElementById(slug), {\n headerOffset: FAQ_NAV_HEADER_OFFSET,\n })\n if (typeof history !== 'undefined') history.replaceState(null, '', `#${slug}`)\n },\n [],\n )\n\n return (\n <div className=\"space-y-8\">\n {navGroups.length > 1 && (\n <nav aria-label=\"FAQ categories\" className=\"flex flex-wrap gap-2\">\n {navGroups.map((group) => {\n const isActive = group.slug === activeSlug\n return (\n <a\n key={group.slug}\n href={`#${group.slug}`}\n aria-current={isActive ? 'true' : undefined}\n onClick={(e) => handleJump(e, group.slug as string)}\n className={cn(\n \"rounded-full border px-4 py-2 text-sm font-medium font-['DM_Sans'] transition-colors\",\n isActive\n ? 'border-ods-text-primary bg-ods-card text-ods-text-primary'\n : 'border-ods-border bg-ods-card text-ods-text-secondary hover:border-ods-text-secondary hover:text-ods-text-primary',\n )}\n >\n {group.section}\n </a>\n )\n })}\n </nav>\n )}\n <div className=\"space-y-10\">\n {groups.map((group) => (\n <section\n key={group.slug ?? 'faq-uncategorized'}\n id={group.slug ?? undefined}\n className=\"scroll-mt-24 space-y-4\"\n >\n {group.section && (\n <CategoryHeading className={SECTION_HEADING_CLASS}>{group.section}</CategoryHeading>\n )}\n <FaqAccordion items={group.items} />\n </section>\n ))}\n </div>\n </div>\n )\n}\n\nfunction FaqSkeleton() {\n return (\n <div className=\"space-y-8 animate-pulse\">\n <div className=\"h-12 md:h-14 w-2/3 rounded bg-ods-border\" />\n <div className=\"rounded-3xl border border-ods-border overflow-hidden bg-ods-card divide-y divide-ods-border w-full\">\n {Array.from({ length: 8 }).map((_, idx) => (\n <div key={idx} className=\"flex items-center justify-between px-6 md:px-8 py-6\">\n <div className=\"h-6 w-5/6 rounded bg-ods-border\" />\n <div className=\"h-10 w-10 rounded-md bg-ods-border\" />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\ninterface FaqsResponse {\n faqs: Faq[]\n}\n\n/**\n * The FAQ display surface — ONE rendering on every host: the list grouped by\n * `faq.section` (each category a heading + its own accordion, with a category\n * jump-nav once there are 2+ categories). There is no flat/ungrouped mode and\n * no page-vs-embedded shell fork; the standalone /faqs page and every embed\n * render through this single path, so they cannot drift.\n *\n * - Standalone /faqs page: pass `initialFaqs` (SSR) + `heading={null}` (the\n * page owns the <h1>) + `emitJsonLd` with `jsonLd` overrides for SEO.\n * - Per-entity embed: pass `entityType` + `entityId` (no `initialFaqs`); the\n * hook self-fetches `GET /api/faqs`, and `heading` is this block's own <h2>.\n *\n * CONTRACT: the consuming app MUST implement `GET /api/faqs`. On a fetch error\n * (or zero FAQs) the component renders nothing so the host page isn't\n * disfigured. The host always supplies the page shell — this renders a bare\n * <section>.\n */\nexport function FaqSection({\n initialFaqs,\n entityType,\n entityId,\n heading,\n emitJsonLd = false,\n jsonLd,\n className,\n minResults,\n apiBaseUrl = '',\n}: FaqSectionProps) {\n const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl)\n // Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render\n // wrapper object would setState-loop under re-rendering parents.\n const initialData = useMemo<FaqsResponse | undefined>(\n () => (initialFaqs ? { faqs: initialFaqs } : undefined),\n [initialFaqs],\n )\n const { data, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData })\n\n const faqs = data?.faqs ?? []\n // Grouped before the early returns so the hook order stays stable.\n const groups = useMemo(() => (faqs.length > 0 ? groupFaqsBySection(faqs) : []), [faqs])\n\n // `undefined` → default <h2> title; `null` → the host page owns the <h1>, so\n // no title renders here. `heading === null` also makes the category headings\n // <h2> (directly under the page <h1>); otherwise they nest as <h3>.\n const headingNode =\n heading === undefined ? <h2 className={SECTION_HEADING_CLASS}>{DEFAULT_HEADING_TEXT}</h2> : heading\n\n // Degrade silently — never show an error banner or an empty section shell\n // where FAQs would be (host pages and the standalone surface both rely on it).\n if (error) return null\n if (!isLoading && faqs.length === 0) return null\n if (isLoading && faqs.length === 0) {\n return (\n <div className={className}>\n <FaqSkeleton />\n </div>\n )\n }\n\n const schema = emitJsonLd ? buildFaqJsonLdFromFaqs(faqs, jsonLd) : null\n\n return (\n <>\n <section className={className ?? 'space-y-10'}>\n {headingNode}\n <GroupedFaqList groups={groups} categoryHeadingAs={heading === null ? 'h2' : 'h3'} />\n </section>\n {schema && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n // serializeJsonLd, NOT raw JSON.stringify — FAQ answers are\n // admin-entered; an embedded \"</script>\" must not break the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(schema) }}\n />\n )}\n </>\n )\n}\n","\"use client\"\n\nimport React, { useRef, useState, useEffect, useCallback } from 'react'\nimport { ChevronButton } from './ui/chevron-button'\nimport { cn } from \"../utils/cn\"\n\nexport interface FaqItem {\n id: number | string\n question: string\n answer: string\n}\n\ninterface FaqAccordionProps {\n items: FaqItem[]\n defaultOpenIds?: (number | string)[]\n}\n\n// Utility to measure scrollHeight outside render cycle\nconst useMeasuredHeight = (isOpen: boolean) => {\n const ref = useRef<HTMLDivElement | null>(null)\n const [maxHeight, setMaxHeight] = useState<string>('0px')\n\n const measure = useCallback(() => {\n if (ref.current) {\n const height = ref.current.scrollHeight\n setMaxHeight(`${height}px`)\n }\n }, [])\n\n // Update height only when section is open\n useEffect(() => {\n if (isOpen) {\n measure()\n } else {\n setMaxHeight('0px')\n }\n }, [isOpen, measure])\n\n return { ref, maxHeight }\n}\n\nexport function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps) {\n const [openSet, setOpenSet] = useState<Set<string | number>>(new Set(defaultOpenIds))\n\n const toggle = (id: string | number) => {\n setOpenSet(prev => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }\n\n return (\n <div className=\"rounded-3xl border border-ods-border divide-y divide-ods-border bg-ods-card overflow-hidden\">\n {items.map(item => {\n const isOpen = openSet.has(item.id)\n const { ref, maxHeight } = useMeasuredHeight(isOpen)\n\n return (\n <div\n key={item.id}\n className={cn('group transition-colors hover:bg-[#1E1E1E]', isOpen ? 'bg-ods-bg' : 'bg-transparent')}\n >\n {/* Header */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => toggle(item.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggle(item.id);\n }\n }}\n aria-expanded={isOpen}\n className=\"flex w-full items-center justify-between px-6 md:px-8 py-6 text-left focus:outline-none transition-colors cursor-pointer\"\n >\n <div className=\"min-w-0 pr-4\">\n <h3>\n {item.question}\n </h3>\n </div>\n <div className=\"flex-shrink-0\">\n <ChevronButton\n aria-label={isOpen ? 'Collapse question' : 'Expand question'}\n size=\"md\"\n isExpanded={isOpen}\n backgroundColor=\"transparent\"\n borderColor=\"#3A3A3A\"\n />\n </div>\n </div>\n {/* Content wrapper with max-height animation */}\n <div\n style={{ maxHeight, transition: 'max-height 0.35s ease-in-out, opacity 0.35s ease-in-out', opacity: isOpen ? 1 : 0 }}\n className=\"overflow-hidden group-hover:bg-[#1E1E1E]/30\"\n >\n <div ref={ref} className=\"px-6 md:px-8 pb-6 text-ods-text-primary text-h4\">\n {item.answer}\n </div>\n </div>\n </div>\n )\n })}\n </div>\n )\n} ","/**\n * Pure FAQ JSON-LD builder. No React, no client-only deps — safe to import from\n * Server Components via the `./components/faq` subpath export. (Do NOT import\n * through the root `./components` barrel — that barrel is `\"use client\"` and\n * dragging it into a Server Component would force the client graph into the\n * server.)\n *\n * The hub used to harcode `name`/`description` here for OpenMSP; in the lib we\n * accept overrides so every embedder can supply its own platform branding.\n */\nimport type { Faq } from '../../types/faq'\n\nexport interface FaqSchemaOptions {\n name?: string\n description?: string\n url?: string\n}\n\nconst DEFAULT_NAME = 'Frequently Asked Questions'\nconst DEFAULT_DESCRIPTION =\n 'Answers to common questions.'\n\nexport function baseFaqSchema(opts: FaqSchemaOptions = {}) {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n name: opts.name ?? DEFAULT_NAME,\n description: opts.description ?? DEFAULT_DESCRIPTION,\n ...(opts.url ? { url: opts.url } : {}),\n } as const\n}\n\nexport function buildFaqJsonLdFromFaqs(faqs: Faq[], opts: FaqSchemaOptions = {}) {\n return {\n ...baseFaqSchema(opts),\n mainEntity: faqs.map((faq) => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n } as const\n}\n"]}
@@ -0,0 +1,29 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
2
+
3
+ // src/components/layout/page-heading.tsx
4
+ var _jsxruntime = require('react/jsx-runtime');
5
+ var PAGE_HEADING_CLASS = "text-h1 text-ods-text-primary";
6
+ var SECTION_HEADING_CLASS = "text-h2 text-ods-text-primary";
7
+ var DESCRIPTION_CLASS = "mt-6 max-w-[640px] font-['DM_Sans'] text-[16px] md:text-[18px] leading-[24px] md:leading-[28px] text-ods-text-secondary";
8
+ function PageHeading({
9
+ children,
10
+ as: Tag = "h1",
11
+ description,
12
+ className,
13
+ descriptionClassName
14
+ }) {
15
+ const headingClass = className ? `${PAGE_HEADING_CLASS} ${className}` : PAGE_HEADING_CLASS;
16
+ const descClass = descriptionClassName ? `${DESCRIPTION_CLASS} ${descriptionClassName}` : DESCRIPTION_CLASS;
17
+ const hasDescription = description != null && description !== "" && typeof description !== "boolean";
18
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
19
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Tag, { className: headingClass, children }),
20
+ hasDescription && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: descClass, children: description })
21
+ ] });
22
+ }
23
+
24
+
25
+
26
+
27
+
28
+ exports.PAGE_HEADING_CLASS = PAGE_HEADING_CLASS; exports.SECTION_HEADING_CLASS = SECTION_HEADING_CLASS; exports.PageHeading = PageHeading;
29
+ //# sourceMappingURL=chunk-722TTRIJ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-722TTRIJ.cjs","../src/components/layout/page-heading.tsx"],"names":[],"mappings":"AAAA,qFAAY;AACZ;AACA;AC6DI,+CAAA;AAnDG,IAAM,mBAAA,EAAqB,+BAAA;AAS3B,IAAM,sBAAA,EAAwB,+BAAA;AAErC,IAAM,kBAAA,EACJ,yHAAA;AAwBK,SAAS,WAAA,CAAY;AAAA,EAC1B,QAAA;AAAA,EACA,EAAA,EAAI,IAAA,EAAM,IAAA;AAAA,EACV,WAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAAqB;AACnB,EAAA,MAAM,aAAA,EAAe,UAAA,EAAY,CAAA,EAAA;AACf,EAAA;AAKhB,EAAA;AAEA,EAAA;AACO,oBAAA;AACc,IAAA;AACrB,EAAA;AAEJ;AD9CoC;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-722TTRIJ.cjs","sourcesContent":[null,"import type { ReactNode } from 'react'\n\n/**\n * THE page-title style — the ODS `text-h1` token (`--font-size-h1-title` =\n * 40 / 48 / 56px, Azeret Mono semibold), which already owns the responsive\n * letter-spacing (-0.02em). Mirrors the global `h1 { @apply text-h1 }` base, so\n * every page heading is the SAME size across the lib and every consuming app.\n *\n * Single source of truth — do NOT hardcode a px size or re-assert the token's\n * tracking; render `<PageHeading>` (or, for the rare non-h1 caller, apply\n * `PAGE_HEADING_CLASS`).\n */\nexport const PAGE_HEADING_CLASS = 'text-h1 text-ods-text-primary'\n\n/**\n * THE section-heading style — the ODS `text-h2` sub-title token\n * (`--font-size-h2-sub-title` = 24 / 32 / 32px), one step below the page\n * title so a section `<h2>`/`<h3>` is always visually distinguishable from\n * the page's `<h1>`. Same single-source rule as PAGE_HEADING_CLASS: never\n * hardcode a px ramp or re-assert the token's tracking on a section heading.\n */\nexport const SECTION_HEADING_CLASS = 'text-h2 text-ods-text-primary'\n\nconst DESCRIPTION_CLASS =\n \"mt-6 max-w-[640px] font-['DM_Sans'] text-[16px] md:text-[18px] leading-[24px] md:leading-[28px] text-ods-text-secondary\"\n\nexport interface PageHeadingProps {\n /** Heading content — plain text or nodes (e.g. an accent <span>). */\n children: ReactNode\n /**\n * Heading level. Defaults to 'h1' (exactly one per page). Pass 'h2' where the\n * page already renders an <h1> above (e.g. a hero/featured item).\n */\n as?: 'h1' | 'h2'\n /** Optional supporting copy rendered as a <p> beneath the heading. */\n description?: ReactNode\n /** Extra classes merged onto the heading (margins, width, truncate, etc.). */\n className?: string\n /** Extra classes merged onto the description <p>. */\n descriptionClassName?: string\n}\n\n/**\n * Unified page heading. Renders the canonical page-title <h1> (or <h2>) plus an\n * optional description, so every page shares one consistent style instead of\n * duplicating the class string. Layout (PageContainer, margins, surrounding\n * sections) stays with the caller — this owns only the heading + description.\n */\nexport function PageHeading({\n children,\n as: Tag = 'h1',\n description,\n className,\n descriptionClassName,\n}: PageHeadingProps) {\n const headingClass = className ? `${PAGE_HEADING_CLASS} ${className}` : PAGE_HEADING_CLASS\n const descClass = descriptionClassName ? `${DESCRIPTION_CLASS} ${descriptionClassName}` : DESCRIPTION_CLASS\n // `description` is a ReactNode, so `description={cond && '...'}` can pass a\n // boolean `false` — exclude it (and empty string) so we never render an empty\n // <p> that adds phantom vertical gap beneath the heading.\n const hasDescription =\n description != null && description !== '' && typeof description !== 'boolean'\n return (\n <>\n <Tag className={headingClass}>{children}</Tag>\n {hasDescription && <p className={descClass}>{description}</p>}\n </>\n )\n}\n"]}
@@ -9,7 +9,7 @@
9
9
 
10
10
 
11
11
 
12
- var _chunk7MNBE7XAcjs = require('./chunk-7MNBE7XA.cjs');
12
+ var _chunkCICK3FUUcjs = require('./chunk-CICK3FUU.cjs');
13
13
 
14
14
 
15
15
  var _chunkDAAFVOTLcjs = require('./chunk-DAAFVOTL.cjs');
@@ -47,7 +47,7 @@ function DocSearchResultRow({
47
47
  }) {
48
48
  const docType = _optionalChain([result, 'access', _2 => _2.metadata, 'optionalAccess', _3 => _3.documentType]) || void 0;
49
49
  const sourceRepo = _optionalChain([result, 'access', _4 => _4.metadata, 'optionalAccess', _5 => _5.sourceRepo]) || void 0;
50
- const { Icon: SourceIcon, label: iconLabel } = _chunk7MNBE7XAcjs.resolveSourceIcon.call(void 0, {
50
+ const { Icon: SourceIcon, label: iconLabel } = _chunkCICK3FUUcjs.resolveSourceIcon.call(void 0, {
51
51
  sourceRepo,
52
52
  documentType: docType
53
53
  });
@@ -90,7 +90,7 @@ function DocSearchBar({
90
90
  renderResult
91
91
  }) {
92
92
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
93
- _chunk7MNBE7XAcjs.SearchInput,
93
+ _chunkCICK3FUUcjs.SearchInput,
94
94
  {
95
95
  placeholder,
96
96
  value: query,
@@ -199,7 +199,7 @@ function resolveSearchResultAction(result, source, runtimeMode) {
199
199
  const externalUrl = meta.externalUrl;
200
200
  if (externalUrl) {
201
201
  const targetPlatform = meta.targetPlatform;
202
- const isNewTab = _chunk7MNBE7XAcjs.decideNewTab.call(void 0, {
202
+ const isNewTab = _chunkCICK3FUUcjs.decideNewTab.call(void 0, {
203
203
  href: externalUrl,
204
204
  targetPlatform,
205
205
  surface: "useUnifiedNav",
@@ -300,7 +300,7 @@ function useDocSearch(config) {
300
300
  if (_optionalChain([runtime, 'optionalAccess', _13 => _13.navigation, 'access', _14 => _14.mode]) === "embed") {
301
301
  setKeepOpen(true);
302
302
  const targetPlatform = _nullishCoalesce(_optionalChain([result, 'access', _15 => _15.metadata, 'optionalAccess', _16 => _16.targetPlatform]), () => ( null));
303
- _chunk7MNBE7XAcjs.resolveExternalNavigation.call(void 0, {
303
+ _chunkCICK3FUUcjs.resolveExternalNavigation.call(void 0, {
304
304
  href: action.href,
305
305
  targetPlatform,
306
306
  runtime
@@ -309,18 +309,18 @@ function useDocSearch(config) {
309
309
  }
310
310
  if (wantsNewTab) {
311
311
  setKeepOpen(true);
312
- window.open(action.href, "_blank", _chunk7MNBE7XAcjs.NEW_TAB_FEATURES);
312
+ window.open(action.href, "_blank", _chunkCICK3FUUcjs.NEW_TAB_FEATURES);
313
313
  return;
314
314
  }
315
315
  setKeepOpen(false);
316
316
  const path = baseRoute && action.href.startsWith(`${baseRoute}/`) ? action.href.slice(baseRoute.length + 1) : null;
317
317
  if (path && _optionalChain([onInPageSwap, 'optionalCall', _17 => _17(path)])) return;
318
- router.push(_chunk7MNBE7XAcjs.stripSameOriginToPath.call(void 0, action.href));
318
+ router.push(_chunkCICK3FUUcjs.stripSameOriginToPath.call(void 0, action.href));
319
319
  return;
320
320
  }
321
321
  case "navigate-new-tab":
322
322
  setKeepOpen(true);
323
- window.open(action.href, "_blank", _chunk7MNBE7XAcjs.NEW_TAB_FEATURES);
323
+ window.open(action.href, "_blank", _chunkCICK3FUUcjs.NEW_TAB_FEATURES);
324
324
  return;
325
325
  case "ask-ai":
326
326
  setKeepOpen(false);
@@ -358,7 +358,7 @@ function DetailPageSkeleton({
358
358
  showImageGallery = true
359
359
  } = {}) {
360
360
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
361
- _chunk7MNBE7XAcjs.PageContainer,
361
+ _chunkCICK3FUUcjs.PageContainer,
362
362
  {
363
363
  as: "article",
364
364
  backgroundClassName: "bg-ods-bg",
@@ -419,7 +419,7 @@ function ArticleAuthorByline({
419
419
  }) {
420
420
  const runtime = _chunkMEAR2WHScjs.useChatRuntime.call(void 0, );
421
421
  if (!author) return null;
422
- const proxiedAvatar = avatar ? proxyImageUrl ? proxyImageUrl(avatar) : _nullishCoalesce(_chunk7MNBE7XAcjs.getProxiedImageUrl.call(void 0, avatar, {
422
+ const proxiedAvatar = avatar ? proxyImageUrl ? proxyImageUrl(avatar) : _nullishCoalesce(_chunkCICK3FUUcjs.getProxiedImageUrl.call(void 0, avatar, {
423
423
  proxyPrefix: _optionalChain([runtime, 'optionalAccess', _18 => _18.endpoints, 'access', _19 => _19.imageProxyUrlPrefix]),
424
424
  skipDomains: _optionalChain([runtime, 'optionalAccess', _20 => _20.endpoints, 'access', _21 => _21.imageProxySkipDomains]),
425
425
  directHttps: true
@@ -427,7 +427,7 @@ function ArticleAuthorByline({
427
427
  const avatarSizeClass = size === "lg" ? "w-16 h-16" : "w-14 h-14";
428
428
  const avatarIconClass = size === "lg" ? "w-8 h-8" : "w-7 h-7";
429
429
  const avatarDim = size === "lg" ? 64 : 56;
430
- const formattedBio = _chunk7MNBE7XAcjs.formatBioText.call(void 0, _nullishCoalesce(bio, () => ( null)));
430
+ const formattedBio = _chunkCICK3FUUcjs.formatBioText.call(void 0, _nullishCoalesce(bio, () => ( null)));
431
431
  const dateLabel = publishedAt ? formatDate(publishedAt) : "";
432
432
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
433
433
  "div",
@@ -483,4 +483,4 @@ function ArticleAuthorByline({
483
483
 
484
484
 
485
485
  exports.formatRelativePath = formatRelativePath; exports.DocSearchResultRow = DocSearchResultRow; exports.DocSearchBar = DocSearchBar; exports.mapDocSearchResults = mapDocSearchResults; exports.resolveSearchResultAction = resolveSearchResultAction; exports.useDocSearch = useDocSearch; exports.DetailPageSkeleton = DetailPageSkeleton; exports.ArticleAuthorByline = ArticleAuthorByline;
486
- //# sourceMappingURL=chunk-A56L7UQG.cjs.map
486
+ //# sourceMappingURL=chunk-AA77CC2Y.cjs.map