@flamingo-stack/openframe-frontend-core 0.0.280 → 0.0.281

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 (50) hide show
  1. package/dist/{chunk-PE5AMPHQ.js → chunk-3QMJ6OX6.js} +2 -2
  2. package/dist/{chunk-ADJKXYCZ.cjs → chunk-DEDJY6TG.cjs} +35 -3
  3. package/dist/chunk-DEDJY6TG.cjs.map +1 -0
  4. package/dist/{chunk-23DPR7HU.cjs → chunk-DKHWVF6S.cjs} +10 -10
  5. package/dist/{chunk-23DPR7HU.cjs.map → chunk-DKHWVF6S.cjs.map} +1 -1
  6. package/dist/{chunk-CBVCOXC5.cjs → chunk-FAKJMITR.cjs} +25 -25
  7. package/dist/{chunk-CBVCOXC5.cjs.map → chunk-FAKJMITR.cjs.map} +1 -1
  8. package/dist/{chunk-KLBB2J7W.js → chunk-GQS4ZRL3.js} +2 -2
  9. package/dist/{chunk-U72ACGRR.cjs → chunk-HIFAGJQU.cjs} +6 -6
  10. package/dist/{chunk-U72ACGRR.cjs.map → chunk-HIFAGJQU.cjs.map} +1 -1
  11. package/dist/{chunk-IIS4LQEO.cjs → chunk-LAXKEA5S.cjs} +34 -34
  12. package/dist/{chunk-IIS4LQEO.cjs.map → chunk-LAXKEA5S.cjs.map} +1 -1
  13. package/dist/{chunk-5ZOWHCFN.js → chunk-ODKT35W6.js} +2 -2
  14. package/dist/{chunk-Y2ZFRZ5Q.js → chunk-PWPRJKCY.js} +2 -2
  15. package/dist/{chunk-QHTJTSHT.js → chunk-Z4RUSQES.js} +35 -3
  16. package/dist/chunk-Z4RUSQES.js.map +1 -0
  17. package/dist/components/chat/chat-ticket-list.d.ts +2 -0
  18. package/dist/components/chat/chat-ticket-list.d.ts.map +1 -1
  19. package/dist/components/chat/entity-cards/chat-ticket-item.d.ts +4 -1
  20. package/dist/components/chat/entity-cards/chat-ticket-item.d.ts.map +1 -1
  21. package/dist/components/chat/index.cjs +2 -2
  22. package/dist/components/chat/index.js +1 -1
  23. package/dist/components/contact/index.cjs +3 -3
  24. package/dist/components/contact/index.js +2 -2
  25. package/dist/components/faq/index.cjs +3 -3
  26. package/dist/components/faq/index.js +2 -2
  27. package/dist/components/features/index.cjs +2 -2
  28. package/dist/components/features/index.js +1 -1
  29. package/dist/components/index.cjs +53 -53
  30. package/dist/components/index.js +4 -4
  31. package/dist/components/navigation/index.cjs +2 -2
  32. package/dist/components/navigation/index.js +1 -1
  33. package/dist/components/related-content/index.cjs +3 -3
  34. package/dist/components/related-content/index.js +2 -2
  35. package/dist/components/tickets/index.cjs +52 -52
  36. package/dist/components/tickets/index.js +3 -3
  37. package/dist/components/ui/index.cjs +2 -2
  38. package/dist/components/ui/index.js +1 -1
  39. package/dist/index.cjs +2 -2
  40. package/dist/index.js +1 -1
  41. package/package.json +1 -1
  42. package/src/components/chat/chat-ticket-list.tsx +20 -1
  43. package/src/components/chat/entity-cards/chat-ticket-item.tsx +32 -2
  44. package/src/stories/ChatTicketList.stories.tsx +9 -0
  45. package/dist/chunk-ADJKXYCZ.cjs.map +0 -1
  46. package/dist/chunk-QHTJTSHT.js.map +0 -1
  47. /package/dist/{chunk-PE5AMPHQ.js.map → chunk-3QMJ6OX6.js.map} +0 -0
  48. /package/dist/{chunk-KLBB2J7W.js.map → chunk-GQS4ZRL3.js.map} +0 -0
  49. /package/dist/{chunk-5ZOWHCFN.js.map → chunk-ODKT35W6.js.map} +0 -0
  50. /package/dist/{chunk-Y2ZFRZ5Q.js.map → chunk-PWPRJKCY.js.map} +0 -0
@@ -30,7 +30,7 @@ import {
30
30
  getContentRefLabelOrTitleCase,
31
31
  init_pagination,
32
32
  orderContentRefTypes
33
- } from "./chunk-QHTJTSHT.js";
33
+ } from "./chunk-Z4RUSQES.js";
34
34
  import {
35
35
  OnboardingGuideCard,
36
36
  OnboardingGuideCardSkeleton,
@@ -361,4 +361,4 @@ export {
361
361
  GROUP_PAGE_SIZE,
362
362
  RelatedContentSection
363
363
  };
364
- //# sourceMappingURL=chunk-KLBB2J7W.js.map
364
+ //# sourceMappingURL=chunk-GQS4ZRL3.js.map
@@ -7,7 +7,7 @@ var _chunkGVSTRN36cjs = require('./chunk-GVSTRN36.cjs');
7
7
 
8
8
 
9
9
 
10
- var _chunkADJKXYCZcjs = require('./chunk-ADJKXYCZ.cjs');
10
+ var _chunkDEDJY6TGcjs = require('./chunk-DEDJY6TG.cjs');
11
11
 
12
12
 
13
13
 
@@ -82,7 +82,7 @@ function FaqAccordion({ items, defaultOpenIds = [] }) {
82
82
  children: [
83
83
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "min-w-0 pr-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { children: item.question }) }),
84
84
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex-shrink-0", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
85
- _chunkADJKXYCZcjs.ChevronButton,
85
+ _chunkDEDJY6TGcjs.ChevronButton,
86
86
  {
87
87
  "aria-label": isOpen ? "Collapse question" : "Expand question",
88
88
  size: "md",
@@ -142,7 +142,7 @@ function buildFaqJsonLdFromFaqs(faqs, opts = {}) {
142
142
 
143
143
  var DEFAULT_HEADING_TEXT = "Frequently Asked Questions";
144
144
  function buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl = "") {
145
- return _chunkADJKXYCZcjs.buildSuggestionUrl.call(void 0, "/api/faqs", { apiBaseUrl, entityType, entityId, count: minResults });
145
+ return _chunkDEDJY6TGcjs.buildSuggestionUrl.call(void 0, "/api/faqs", { apiBaseUrl, entityType, entityId, count: minResults });
146
146
  }
147
147
  function sectionSlug(section) {
148
148
  return "faq-" + section.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -212,7 +212,7 @@ function GroupedFaqList({
212
212
  (e, slug) => {
213
213
  e.preventDefault();
214
214
  setActiveSlug(slug);
215
- _chunkADJKXYCZcjs.scrollElementIntoView.call(void 0, document.getElementById(slug), {
215
+ _chunkDEDJY6TGcjs.scrollElementIntoView.call(void 0, document.getElementById(slug), {
216
216
  headerOffset: FAQ_NAV_HEADER_OFFSET
217
217
  });
218
218
  if (typeof history !== "undefined") history.replaceState(null, "", `#${slug}`);
@@ -295,7 +295,7 @@ function FaqSection({
295
295
  "script",
296
296
  {
297
297
  type: "application/ld+json",
298
- dangerouslySetInnerHTML: { __html: _chunkADJKXYCZcjs.serializeJsonLd.call(void 0, schema) }
298
+ dangerouslySetInnerHTML: { __html: _chunkDEDJY6TGcjs.serializeJsonLd.call(void 0, schema) }
299
299
  }
300
300
  )
301
301
  ] });
@@ -335,4 +335,4 @@ function FaqDocumentPage({
335
335
 
336
336
 
337
337
  exports.FaqAccordion = FaqAccordion; exports.FaqSection = FaqSection; exports.FaqDocumentPage = FaqDocumentPage;
338
- //# sourceMappingURL=chunk-U72ACGRR.cjs.map
338
+ //# sourceMappingURL=chunk-HIFAGJQU.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-U72ACGRR.cjs","../src/components/faq/faq-section.tsx","../src/components/faq-accordion.tsx","../src/components/faq/json-ld.ts","../src/components/faq/faq-document-page.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-U72ACGRR.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","'use client';\n\n/**\n * FaqDocumentPage — the full `/faqs` page with chrome, so embedders drop in\n * ONE component instead of hand-assembling `PageShell` + `PageLayout` around the\n * bare `<FaqSection>`. Mirrors `LegalDocumentPage` / `DevSectionPage`: the\n * page-level layout lives in the lib, the host passes only config + a back button.\n *\n * `<FaqSection heading={null}>` lets `PageLayout`'s `TitleBlock` own the heading +\n * back button; it self-fetches `${apiBaseUrl}/api/faqs` via the authed\n * `useSelfFetch`, and renders nothing on a fetch error or zero FAQs.\n */\n\nimport { PageShell, PageLayout } from '../ui';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport { FaqSection } from './faq-section';\n\nexport interface FaqDocumentPageProps {\n /** Page title (PageLayout TitleBlock). Default \"FAQs\". */\n title?: string;\n /** Subtitle under the title. */\n subtitle?: string;\n /** Back-button config — same pattern as `DevSectionPage` / `LegalDocumentPage`.\n * Pass `false` to hide. Default `{ label: 'Back to home', href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n /** Base URL `FaqSection` appends `/api/faqs` to (reverse-proxy embedders). */\n apiBaseUrl?: string;\n /** Optional entity scoping forwarded to `FaqSection`. */\n entityType?: string;\n entityId?: number | string;\n /** Minimum FAQ count before the section renders (forwarded to `FaqSection`). */\n minResults?: number;\n}\n\nexport function FaqDocumentPage({\n title = 'FAQs',\n subtitle,\n backButton,\n apiBaseUrl,\n entityType,\n entityId,\n minResults,\n}: FaqDocumentPageProps) {\n const router = useRouter();\n\n // Back-button config — mirrors LegalDocumentPage/DevSectionPage. Hide entirely\n // when the caller passes `false` (embed-mode where the host owns nav chrome).\n const backCfg =\n backButton === false\n ? undefined\n : {\n label: backButton?.label ?? 'Back to home',\n onClick: () => router.push(backButton?.href ?? '/'),\n };\n\n return (\n <PageShell>\n <PageLayout title={title} subtitle={subtitle} backButton={backCfg}>\n <FaqSection\n heading={null}\n apiBaseUrl={apiBaseUrl}\n entityType={entityType}\n entityId={entityId}\n minResults={minResults}\n />\n </PageLayout>\n </PageShell>\n );\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-HIFAGJQU.cjs","../src/components/faq/faq-section.tsx","../src/components/faq-accordion.tsx","../src/components/faq/json-ld.ts","../src/components/faq/faq-document-page.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-HIFAGJQU.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","'use client';\n\n/**\n * FaqDocumentPage — the full `/faqs` page with chrome, so embedders drop in\n * ONE component instead of hand-assembling `PageShell` + `PageLayout` around the\n * bare `<FaqSection>`. Mirrors `LegalDocumentPage` / `DevSectionPage`: the\n * page-level layout lives in the lib, the host passes only config + a back button.\n *\n * `<FaqSection heading={null}>` lets `PageLayout`'s `TitleBlock` own the heading +\n * back button; it self-fetches `${apiBaseUrl}/api/faqs` via the authed\n * `useSelfFetch`, and renders nothing on a fetch error or zero FAQs.\n */\n\nimport { PageShell, PageLayout } from '../ui';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport { FaqSection } from './faq-section';\n\nexport interface FaqDocumentPageProps {\n /** Page title (PageLayout TitleBlock). Default \"FAQs\". */\n title?: string;\n /** Subtitle under the title. */\n subtitle?: string;\n /** Back-button config — same pattern as `DevSectionPage` / `LegalDocumentPage`.\n * Pass `false` to hide. Default `{ label: 'Back to home', href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n /** Base URL `FaqSection` appends `/api/faqs` to (reverse-proxy embedders). */\n apiBaseUrl?: string;\n /** Optional entity scoping forwarded to `FaqSection`. */\n entityType?: string;\n entityId?: number | string;\n /** Minimum FAQ count before the section renders (forwarded to `FaqSection`). */\n minResults?: number;\n}\n\nexport function FaqDocumentPage({\n title = 'FAQs',\n subtitle,\n backButton,\n apiBaseUrl,\n entityType,\n entityId,\n minResults,\n}: FaqDocumentPageProps) {\n const router = useRouter();\n\n // Back-button config — mirrors LegalDocumentPage/DevSectionPage. Hide entirely\n // when the caller passes `false` (embed-mode where the host owns nav chrome).\n const backCfg =\n backButton === false\n ? undefined\n : {\n label: backButton?.label ?? 'Back to home',\n onClick: () => router.push(backButton?.href ?? '/'),\n };\n\n return (\n <PageShell>\n <PageLayout title={title} subtitle={subtitle} backButton={backCfg}>\n <FaqSection\n heading={null}\n apiBaseUrl={apiBaseUrl}\n entityType={entityType}\n entityId={entityId}\n minResults={minResults}\n />\n </PageLayout>\n </PageShell>\n );\n}\n"]}
@@ -30,7 +30,7 @@ var _chunkGVSTRN36cjs = require('./chunk-GVSTRN36.cjs');
30
30
 
31
31
 
32
32
 
33
- var _chunkADJKXYCZcjs = require('./chunk-ADJKXYCZ.cjs');
33
+ var _chunkDEDJY6TGcjs = require('./chunk-DEDJY6TG.cjs');
34
34
 
35
35
 
36
36
 
@@ -39,7 +39,7 @@ var _chunk6QZ656YEcjs = require('./chunk-6QZ656YE.cjs');
39
39
 
40
40
  // src/components/related-content/related-content-section.tsx
41
41
  var _react = require('react');
42
- _chunkADJKXYCZcjs.init_pagination.call(void 0, );
42
+ _chunkDEDJY6TGcjs.init_pagination.call(void 0, );
43
43
  var _jsxruntime = require('react/jsx-runtime');
44
44
  function DefaultLinkPropsProvider({ href, targetPlatform, children }) {
45
45
  if (!href) return children(null);
@@ -55,29 +55,29 @@ function renderSkeletonForType(type, size, adminCampaignCard) {
55
55
  const legacySize = size === "sm" ? "sm" : "default";
56
56
  switch (type) {
57
57
  case "blog_post_existing":
58
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.BlogCardSkeleton, { size: legacySize });
58
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.BlogCardSkeleton, { size: legacySize });
59
59
  case "case_study":
60
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.CaseStudyCardSkeleton, { size: legacySize });
60
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.CaseStudyCardSkeleton, { size: legacySize });
61
61
  case "customer_interview":
62
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.CustomerInterviewCardSkeleton, { size: legacySize });
62
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.CustomerInterviewCardSkeleton, { size: legacySize });
63
63
  case "product_release":
64
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.ProductReleaseCardSkeleton, { size: size === "sm" ? "sm" : "lg" });
64
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.ProductReleaseCardSkeleton, { size: size === "sm" ? "sm" : "lg" });
65
65
  case "podcast":
66
66
  case "webinar":
67
67
  case "event":
68
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.ProgramCardSkeleton, { size: legacySize });
68
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.ProgramCardSkeleton, { size: legacySize });
69
69
  case "investor_update":
70
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.InvestorUpdateCardSkeleton, { size: legacySize });
70
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.InvestorUpdateCardSkeleton, { size: legacySize });
71
71
  case "onboarding_guide":
72
72
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6QZ656YEcjs.OnboardingGuideCardSkeleton, { size: size === "sm" ? "sm" : "catalog" });
73
73
  case "what_i_shipped":
74
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.WhatIShippedCardSkeleton, {});
74
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.WhatIShippedCardSkeleton, {});
75
75
  case "marketing_campaign":
76
76
  return adminCampaignCard ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, adminCampaignCard.Skeleton, { size: legacySize }) : null;
77
77
  case "roadmap_item":
78
78
  case "delivery_item":
79
79
  case "internal_task":
80
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.RoadmapCardSkeleton, { size: legacySize });
80
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.RoadmapCardSkeleton, { size: legacySize });
81
81
  default:
82
82
  return null;
83
83
  }
@@ -97,17 +97,17 @@ function CardForType({
97
97
  const anchorAttrs = linkProps ? { target: linkProps.target, rel: linkProps.rel } : {};
98
98
  switch (type) {
99
99
  case "blog_post_existing":
100
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.BlogCard, { post: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
100
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.BlogCard, { post: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
101
101
  case "case_study":
102
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.CaseStudyCard, { study: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
102
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.CaseStudyCard, { study: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
103
103
  case "customer_interview":
104
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.CustomerInterviewCard, { interview: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
104
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.CustomerInterviewCard, { interview: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
105
105
  case "product_release": {
106
106
  const releaseSize = size === "sm" ? "sm" : "lg";
107
- const buildReleaseProps = _nullishCoalesce(_optionalChain([extras, 'optionalAccess', _5 => _5.buildProductReleaseCardProps]), () => ( _chunkADJKXYCZcjs.buildProductReleaseCardProps));
107
+ const buildReleaseProps = _nullishCoalesce(_optionalChain([extras, 'optionalAccess', _5 => _5.buildProductReleaseCardProps]), () => ( _chunkDEDJY6TGcjs.buildProductReleaseCardProps));
108
108
  const releaseProps = buildReleaseProps(item);
109
109
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
110
- _chunkADJKXYCZcjs.ProductReleaseCard,
110
+ _chunkDEDJY6TGcjs.ProductReleaseCard,
111
111
  {
112
112
  size: releaseSize,
113
113
  title: item.title,
@@ -119,18 +119,18 @@ function CardForType({
119
119
  );
120
120
  }
121
121
  case "podcast":
122
- return _optionalChain([extras, 'optionalAccess', _6 => _6.programConfigs, 'optionalAccess', _7 => _7.podcast]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.ProgramCard, { config: extras.programConfigs.podcast, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
122
+ return _optionalChain([extras, 'optionalAccess', _6 => _6.programConfigs, 'optionalAccess', _7 => _7.podcast]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.ProgramCard, { config: extras.programConfigs.podcast, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
123
123
  case "webinar":
124
- return _optionalChain([extras, 'optionalAccess', _8 => _8.programConfigs, 'optionalAccess', _9 => _9.webinar]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.ProgramCard, { config: extras.programConfigs.webinar, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
124
+ return _optionalChain([extras, 'optionalAccess', _8 => _8.programConfigs, 'optionalAccess', _9 => _9.webinar]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.ProgramCard, { config: extras.programConfigs.webinar, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
125
125
  case "event":
126
- return _optionalChain([extras, 'optionalAccess', _10 => _10.programConfigs, 'optionalAccess', _11 => _11.event]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.ProgramCard, { config: extras.programConfigs.event, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
126
+ return _optionalChain([extras, 'optionalAccess', _10 => _10.programConfigs, 'optionalAccess', _11 => _11.event]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.ProgramCard, { config: extras.programConfigs.event, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
127
127
  case "investor_update":
128
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.InvestorUpdateCard, { update: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
128
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.InvestorUpdateCard, { update: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
129
129
  case "onboarding_guide":
130
130
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6QZ656YEcjs.OnboardingGuideCard, { guide: item, size: size === "sm" ? "sm" : "catalog", href, targetPlatform, placeholderUrl, ...anchorAttrs });
131
131
  case "what_i_shipped":
132
132
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
133
- _chunkADJKXYCZcjs.WhatIShippedCard,
133
+ _chunkDEDJY6TGcjs.WhatIShippedCard,
134
134
  {
135
135
  entry: item,
136
136
  placeholderUrl,
@@ -143,7 +143,7 @@ function CardForType({
143
143
  case "delivery_item":
144
144
  case "internal_task":
145
145
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
146
- _chunkADJKXYCZcjs.RoadmapCard,
146
+ _chunkDEDJY6TGcjs.RoadmapCard,
147
147
  {
148
148
  item,
149
149
  href: _nullishCoalesce(href, () => ( "")),
@@ -164,14 +164,14 @@ function useGroupItems(type, refs, buildUrl) {
164
164
  const ids = refs.map((r) => r.id);
165
165
  const url = ids.length > 0 ? buildUrl(type, ids) : null;
166
166
  const { data, isLoading } = _chunkGVSTRN36cjs.useSelfFetch.call(void 0, url);
167
- const items = data != null ? _chunkADJKXYCZcjs.extractItems.call(void 0, data) : null;
167
+ const items = data != null ? _chunkDEDJY6TGcjs.extractItems.call(void 0, data) : null;
168
168
  return { items, isLoading };
169
169
  }
170
170
  function gridClassFor(columns) {
171
171
  return columns === 3 ? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" : "grid grid-cols-1 sm:grid-cols-2 gap-6";
172
172
  }
173
173
  function resolveGroupConfig(type) {
174
- return _nullishCoalesce(_chunkADJKXYCZcjs.CONTENT_REF_GROUPS[type], () => ( {
174
+ return _nullishCoalesce(_chunkDEDJY6TGcjs.CONTENT_REF_GROUPS[type], () => ( {
175
175
  label: type,
176
176
  order: 999,
177
177
  layout: "grid",
@@ -206,7 +206,7 @@ function ContentGroup({
206
206
  const totalGroupPages = Math.max(1, Math.ceil(refs.length / GROUP_PAGE_SIZE));
207
207
  const safePage = Math.min(page, totalGroupPages);
208
208
  const visibleGroupRefs = refs.length > GROUP_PAGE_SIZE ? refs.slice((safePage - 1) * GROUP_PAGE_SIZE, safePage * GROUP_PAGE_SIZE) : refs;
209
- const groupPagination = totalGroupPages > 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkADJKXYCZcjs.Pagination, { currentPage: safePage, totalPages: totalGroupPages, onPageChange: setPage }) : null;
209
+ const groupPagination = totalGroupPages > 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDEDJY6TGcjs.Pagination, { currentPage: safePage, totalPages: totalGroupPages, onPageChange: setPage }) : null;
210
210
  if (isLoading && !items) {
211
211
  const skeletons = visibleGroupRefs.map((r) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { children: renderSkeletonForType(type, cardSize, adminCampaignCard) }, r.id));
212
212
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-4", children: [
@@ -216,7 +216,7 @@ function ContentGroup({
216
216
  }
217
217
  if (!items || items.length === 0) return null;
218
218
  const itemById = new Map(
219
- items.map((it) => [_nullishCoalesce(_chunkADJKXYCZcjs.extractItemId.call(void 0, type, it), () => ( String(_optionalChain([it, 'optionalAccess', _12 => _12.id])))), it])
219
+ items.map((it) => [_nullishCoalesce(_chunkDEDJY6TGcjs.extractItemId.call(void 0, type, it), () => ( String(_optionalChain([it, 'optionalAccess', _12 => _12.id])))), it])
220
220
  );
221
221
  const cards = visibleGroupRefs.map((contentRef) => {
222
222
  const itemId = String(contentRef.id);
@@ -281,11 +281,11 @@ function RelatedContentSection({
281
281
  types: includeTypes !== void 0 ? includeTypes.join(",") : void 0,
282
282
  excludeTypes: excludeTypes && excludeTypes.length > 0 ? excludeTypes.join(",") : void 0
283
283
  };
284
- const authorUrl = contentRefs === void 0 && authorId && !suggestionsDisabled ? _chunkADJKXYCZcjs.buildSuggestionUrl.call(void 0, "/api/related-content", {
284
+ const authorUrl = contentRefs === void 0 && authorId && !suggestionsDisabled ? _chunkDEDJY6TGcjs.buildSuggestionUrl.call(void 0, "/api/related-content", {
285
285
  apiBaseUrl,
286
286
  extraParams: { authorId, ...typeFilterParams }
287
287
  }) : null;
288
- const suggestUrl = _nullishCoalesce(authorUrl, () => ( (contentRefs === void 0 && entityType && entityId !== void 0 && entityId !== null && entityId !== "" && !suggestionsDisabled ? _chunkADJKXYCZcjs.buildSuggestionUrl.call(void 0, "/api/related-content", {
288
+ const suggestUrl = _nullishCoalesce(authorUrl, () => ( (contentRefs === void 0 && entityType && entityId !== void 0 && entityId !== null && entityId !== "" && !suggestionsDisabled ? _chunkDEDJY6TGcjs.buildSuggestionUrl.call(void 0, "/api/related-content", {
289
289
  apiBaseUrl,
290
290
  entityType,
291
291
  entityId,
@@ -304,7 +304,7 @@ function RelatedContentSection({
304
304
  );
305
305
  const { data, isLoading } = _chunkGVSTRN36cjs.useSelfFetch.call(void 0, suggestUrl, { initialData });
306
306
  const effectiveBuildListUrl = _react.useMemo.call(void 0,
307
- () => _nullishCoalesce(buildListUrl2, () => ( ((type, ids) => _chunkADJKXYCZcjs.buildListUrl.call(void 0, type, ids, apiBaseUrl)))),
307
+ () => _nullishCoalesce(buildListUrl2, () => ( ((type, ids) => _chunkDEDJY6TGcjs.buildListUrl.call(void 0, type, ids, apiBaseUrl)))),
308
308
  [buildListUrl2, apiBaseUrl]
309
309
  );
310
310
  const refs = _nullishCoalesce(_nullishCoalesce(contentRefs, () => ( _optionalChain([data, 'optionalAccess', _14 => _14.refs]))), () => ( []));
@@ -329,12 +329,12 @@ function RelatedContentSection({
329
329
  if (!grouped[ref.type]) grouped[ref.type] = [];
330
330
  grouped[ref.type].push(ref);
331
331
  }
332
- let orderedTypes = _chunkADJKXYCZcjs.orderContentRefTypes.call(void 0, Object.keys(grouped));
332
+ let orderedTypes = _chunkDEDJY6TGcjs.orderContentRefTypes.call(void 0, Object.keys(grouped));
333
333
  if (entityType) {
334
- const canonicalEntityType = _chunkADJKXYCZcjs.canonicalContentRefType.call(void 0, entityType);
335
- const sameType = orderedTypes.filter((t) => _chunkADJKXYCZcjs.canonicalContentRefType.call(void 0, t) === canonicalEntityType);
334
+ const canonicalEntityType = _chunkDEDJY6TGcjs.canonicalContentRefType.call(void 0, entityType);
335
+ const sameType = orderedTypes.filter((t) => _chunkDEDJY6TGcjs.canonicalContentRefType.call(void 0, t) === canonicalEntityType);
336
336
  if (sameType.length > 0) {
337
- orderedTypes = [...sameType, ...orderedTypes.filter((t) => _chunkADJKXYCZcjs.canonicalContentRefType.call(void 0, t) !== canonicalEntityType)];
337
+ orderedTypes = [...sameType, ...orderedTypes.filter((t) => _chunkDEDJY6TGcjs.canonicalContentRefType.call(void 0, t) !== canonicalEntityType)];
338
338
  }
339
339
  }
340
340
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-8", children: [
@@ -350,7 +350,7 @@ function RelatedContentSection({
350
350
  LinkProvider,
351
351
  extras,
352
352
  adminCampaignCard,
353
- heading: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "font-['Azeret_Mono'] text-[14px] font-semibold uppercase text-ods-text-secondary tracking-wider", children: _chunkADJKXYCZcjs.getContentRefLabelOrTitleCase.call(void 0, type) })
353
+ heading: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "font-['Azeret_Mono'] text-[14px] font-semibold uppercase text-ods-text-secondary tracking-wider", children: _chunkDEDJY6TGcjs.getContentRefLabelOrTitleCase.call(void 0, type) })
354
354
  },
355
355
  type
356
356
  ))
@@ -361,4 +361,4 @@ function RelatedContentSection({
361
361
 
362
362
 
363
363
  exports.GROUP_PAGE_SIZE = GROUP_PAGE_SIZE; exports.RelatedContentSection = RelatedContentSection;
364
- //# sourceMappingURL=chunk-IIS4LQEO.cjs.map
364
+ //# sourceMappingURL=chunk-LAXKEA5S.cjs.map