@flamingo-stack/openframe-frontend-core 0.0.239 → 0.0.241
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-PN64LWNT.js → chunk-2GMHLKEO.js} +8 -8
- package/dist/{chunk-36G2QXYZ.cjs → chunk-3TDLFCCS.cjs} +37 -23
- package/dist/chunk-3TDLFCCS.cjs.map +1 -0
- package/dist/{chunk-ORJREQ2W.js → chunk-4XLJWX2N.js} +4 -4
- package/dist/chunk-5KAQW6Q3.js +285 -0
- package/dist/chunk-5KAQW6Q3.js.map +1 -0
- package/dist/{chunk-7LLS3TWN.js → chunk-6IFN7F3C.js} +5 -5
- package/dist/{chunk-5VU66BD2.cjs → chunk-EFT34DLB.cjs} +498 -461
- package/dist/chunk-EFT34DLB.cjs.map +1 -0
- package/dist/{chunk-Y6463TD6.js → chunk-FEZSQQTH.js} +2 -2
- package/dist/{chunk-IJOJWBME.cjs → chunk-GRD7M6IZ.cjs} +27 -27
- package/dist/{chunk-IJOJWBME.cjs.map → chunk-GRD7M6IZ.cjs.map} +1 -1
- package/dist/{chunk-ELMYRGWG.cjs → chunk-JEPYFYQP.cjs} +12 -12
- package/dist/{chunk-ELMYRGWG.cjs.map → chunk-JEPYFYQP.cjs.map} +1 -1
- package/dist/{chunk-Y7DCQUNL.js → chunk-KOHE4F6Q.js} +91 -54
- package/dist/chunk-KOHE4F6Q.js.map +1 -0
- package/dist/{chunk-JDQY7JCV.js → chunk-MKVKVJQQ.js} +36 -22
- package/dist/chunk-MKVKVJQQ.js.map +1 -0
- package/dist/{chunk-MTROCWO4.cjs → chunk-QJULZ4PB.cjs} +7 -7
- package/dist/{chunk-MTROCWO4.cjs.map → chunk-QJULZ4PB.cjs.map} +1 -1
- package/dist/{chunk-FSS2MIEA.cjs → chunk-SGJBUL2Y.cjs} +20 -3
- package/dist/chunk-SGJBUL2Y.cjs.map +1 -0
- package/dist/{chunk-SZ3BBI6H.cjs → chunk-SWXWKVCC.cjs} +12 -12
- package/dist/{chunk-SZ3BBI6H.cjs.map → chunk-SWXWKVCC.cjs.map} +1 -1
- package/dist/{chunk-LJGBFD52.js → chunk-VAUI3CND.js} +18 -1
- package/dist/chunk-VAUI3CND.js.map +1 -0
- package/dist/{chunk-OB45JHDY.cjs → chunk-VFKQMAUF.cjs} +3 -3
- package/dist/{chunk-OB45JHDY.cjs.map → chunk-VFKQMAUF.cjs.map} +1 -1
- package/dist/chunk-WKZS5HWH.cjs +285 -0
- package/dist/chunk-WKZS5HWH.cjs.map +1 -0
- package/dist/{chunk-G6YZ7O27.js → chunk-Z3657YGL.js} +6 -6
- package/dist/components/chat/hooks/use-chat-card-item.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +6 -6
- package/dist/components/chat/index.js +5 -5
- package/dist/components/chat/types/entities/content-ref.d.ts +5 -20
- package/dist/components/chat/types/entities/content-ref.d.ts.map +1 -1
- package/dist/components/chevron-button.d.ts +3 -1
- package/dist/components/chevron-button.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +7 -7
- package/dist/components/contact/index.js +6 -6
- package/dist/components/faq/faq-section.d.ts +8 -2
- package/dist/components/faq/faq-section.d.ts.map +1 -1
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/faq/json-ld.cjs +1 -1
- package/dist/components/faq/json-ld.cjs.map +1 -1
- package/dist/components/faq/{faq-json-ld.d.ts → json-ld.d.ts} +1 -1
- package/dist/components/faq/json-ld.d.ts.map +1 -0
- package/dist/components/faq/json-ld.js +1 -1
- package/dist/components/faq/json-ld.js.map +1 -1
- package/dist/components/faq-accordion.d.ts.map +1 -1
- package/dist/components/features/index.cjs +6 -6
- package/dist/components/features/index.js +5 -5
- package/dist/components/index.cjs +89 -85
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +31 -27
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +30 -30
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +11 -11
- package/dist/components/related-content/index.cjs +26 -0
- package/dist/components/related-content/index.cjs.map +1 -0
- package/dist/components/related-content/index.d.ts +2 -0
- package/dist/components/related-content/index.d.ts.map +1 -0
- package/dist/components/related-content/index.js +26 -0
- package/dist/components/related-content/index.js.map +1 -0
- package/dist/components/related-content/related-content-section.d.ts +141 -0
- package/dist/components/related-content/related-content-section.d.ts.map +1 -0
- package/dist/components/tickets/index.cjs +73 -73
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +15 -15
- package/dist/components/ui/index.cjs +11 -11
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +13 -13
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +5 -5
- package/dist/index.cjs +27 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +55 -39
- package/dist/types/content-ref.d.ts +35 -0
- package/dist/types/content-ref.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/content-ref-groups.d.ts +58 -0
- package/dist/utils/content-ref-groups.d.ts.map +1 -0
- package/dist/utils/extract-items.d.ts +18 -0
- package/dist/utils/extract-items.d.ts.map +1 -0
- package/dist/utils/index.cjs +81 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +4 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +74 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/list-url.d.ts +6 -0
- package/dist/utils/list-url.d.ts.map +1 -1
- package/dist/utils/suggestion-url.d.ts +26 -0
- package/dist/utils/suggestion-url.d.ts.map +1 -0
- package/package.json +7 -1
- package/src/components/chat/hooks/use-chat-card-item.ts +3 -38
- package/src/components/chat/types/entities/content-ref.ts +5 -21
- package/src/components/chevron-button.tsx +5 -1
- package/src/components/faq/faq-section.tsx +71 -26
- package/src/components/faq-accordion.tsx +2 -1
- package/src/components/index.ts +1 -0
- package/src/components/related-content/index.ts +9 -0
- package/src/components/related-content/related-content-section.tsx +603 -0
- package/src/types/content-ref.ts +34 -0
- package/src/types/index.ts +1 -0
- package/src/utils/content-ref-groups.ts +89 -0
- package/src/utils/extract-items.ts +48 -0
- package/src/utils/index.ts +16 -1
- package/src/utils/list-url.ts +9 -0
- package/src/utils/suggestion-url.ts +39 -0
- package/dist/chunk-36G2QXYZ.cjs.map +0 -1
- package/dist/chunk-5VU66BD2.cjs.map +0 -1
- package/dist/chunk-FSS2MIEA.cjs.map +0 -1
- package/dist/chunk-JDQY7JCV.js.map +0 -1
- package/dist/chunk-LJGBFD52.js.map +0 -1
- package/dist/chunk-Y7DCQUNL.js.map +0 -1
- package/dist/components/faq/faq-json-ld.d.ts.map +0 -1
- /package/dist/{chunk-PN64LWNT.js.map → chunk-2GMHLKEO.js.map} +0 -0
- /package/dist/{chunk-ORJREQ2W.js.map → chunk-4XLJWX2N.js.map} +0 -0
- /package/dist/{chunk-7LLS3TWN.js.map → chunk-6IFN7F3C.js.map} +0 -0
- /package/dist/{chunk-Y6463TD6.js.map → chunk-FEZSQQTH.js.map} +0 -0
- /package/dist/{chunk-G6YZ7O27.js.map → chunk-Z3657YGL.js.map} +0 -0
- /package/src/components/faq/{faq-json-ld.ts → json-ld.ts} +0 -0
package/dist/utils/list-url.d.ts
CHANGED
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
* Pure + server-safe (no React, no browser APIs) so the hub's server-side
|
|
28
28
|
* mappers can import it from `@flamingo-stack/openframe-frontend-core/utils`.
|
|
29
29
|
*/
|
|
30
|
+
/** Resolve a ContentRef rail-vocab type to its canonical `documentType`
|
|
31
|
+
* (registry vocabulary) — `blog_post_existing` → `blog_post`, everything
|
|
32
|
+
* else passes through. Exported so consumers comparing rail group keys to
|
|
33
|
+
* registry entityTypes (e.g. the related-content rail's same-type-first
|
|
34
|
+
* ordering) share THIS alias map instead of re-declaring it. */
|
|
35
|
+
export declare function canonicalContentRefType(contentRefType: string): string;
|
|
30
36
|
/**
|
|
31
37
|
* Build a list-API URL that returns full rows for the given ids, or `null`
|
|
32
38
|
* when the type has no list endpoint (the caller skips fetching rather than
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-url.d.ts","sourceRoot":"","sources":["../../src/utils/list-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;
|
|
1
|
+
{"version":3,"file":"list-url.d.ts","sourceRoot":"","sources":["../../src/utils/list-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAWH;;;;iEAIiE;AACjE,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAEtE;AA4BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,SAAK,GAAG,MAAM,GAAG,IAAI,CAa5F"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared fetch-URL composer for the two suggestion sections (FaqSection +
|
|
3
|
+
* RelatedContentSection) — ONE owner of the entityType/entityId/count query
|
|
4
|
+
* shape so the sections can never drift. Pure + server-safe.
|
|
5
|
+
*
|
|
6
|
+
* Byte-parity contract with the original `buildFaqsUrl`: when only
|
|
7
|
+
* entityType/entityId are set, the param order is `entityType`, `entityId`;
|
|
8
|
+
* with nothing set, the bare `path` is returned (no `?`).
|
|
9
|
+
*/
|
|
10
|
+
export interface SuggestionUrlOptions {
|
|
11
|
+
/** Fetch-URL prefix for third-party embeds / reverse proxies
|
|
12
|
+
* ('' = same-origin relative). */
|
|
13
|
+
apiBaseUrl?: string;
|
|
14
|
+
/** Both required together for entity scope; partial → bare path. */
|
|
15
|
+
entityType?: string;
|
|
16
|
+
entityId?: number | string;
|
|
17
|
+
/** Maps to the server's `count` param. Undefined → param not sent
|
|
18
|
+
* (server default applies). */
|
|
19
|
+
count?: number;
|
|
20
|
+
/** Extra query params appended after the shared trio (e.g. the
|
|
21
|
+
* related-content `excludeTypes` deny-list). Empty/undefined values
|
|
22
|
+
* are skipped. */
|
|
23
|
+
extraParams?: Record<string, string | undefined>;
|
|
24
|
+
}
|
|
25
|
+
export declare function buildSuggestionUrl(path: string, opts?: SuggestionUrlOptions): string;
|
|
26
|
+
//# sourceMappingURL=suggestion-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggestion-url.d.ts","sourceRoot":"","sources":["../../src/utils/suggestion-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC;uCACmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B;oCACgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;uBAEmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACjD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,MAAM,CAaxF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flamingo-stack/openframe-frontend-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.241",
|
|
4
4
|
"description": "Shared design system and components for all Flamingo platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,6 +60,12 @@
|
|
|
60
60
|
"require": "./dist/components/faq/index.cjs",
|
|
61
61
|
"default": "./dist/components/faq/index.js"
|
|
62
62
|
},
|
|
63
|
+
"./components/related-content": {
|
|
64
|
+
"types": "./dist/components/related-content/index.d.ts",
|
|
65
|
+
"import": "./dist/components/related-content/index.js",
|
|
66
|
+
"require": "./dist/components/related-content/index.cjs",
|
|
67
|
+
"default": "./dist/components/related-content/index.js"
|
|
68
|
+
},
|
|
63
69
|
"./components/faq/json-ld": {
|
|
64
70
|
"types": "./dist/components/faq/json-ld.d.ts",
|
|
65
71
|
"import": "./dist/components/faq/json-ld.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { useQuery } from '@tanstack/react-query'
|
|
23
23
|
import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context'
|
|
24
24
|
import { embedAuthedFetch } from '../../../utils/embed-authed-fetch'
|
|
25
|
+
import { extractItems, extractItemId } from '../../../utils/extract-items'
|
|
25
26
|
|
|
26
27
|
export interface UseChatCardItemResult<T = unknown> {
|
|
27
28
|
item: T | undefined
|
|
@@ -29,44 +30,8 @@ export interface UseChatCardItemResult<T = unknown> {
|
|
|
29
30
|
isError: boolean
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* point — callers always read `Item[]`. */
|
|
35
|
-
function extractItems(data: unknown): unknown[] {
|
|
36
|
-
if (!data || typeof data !== 'object') return []
|
|
37
|
-
const obj = data as Record<string, unknown>
|
|
38
|
-
if (Array.isArray(obj.items)) return obj.items
|
|
39
|
-
if (Array.isArray(obj.posts)) return obj.posts
|
|
40
|
-
if (Array.isArray(obj.campaigns)) return obj.campaigns
|
|
41
|
-
if (Array.isArray(obj.completed) || Array.isArray(obj.inProgress)) {
|
|
42
|
-
// Delivery endpoint splits into completed/inProgress arrays — flatten.
|
|
43
|
-
const completed = Array.isArray(obj.completed) ? obj.completed : []
|
|
44
|
-
const inProgress = Array.isArray(obj.inProgress) ? obj.inProgress : []
|
|
45
|
-
return [...completed, ...inProgress]
|
|
46
|
-
}
|
|
47
|
-
if (Array.isArray(obj.data)) return obj.data
|
|
48
|
-
if (Array.isArray(data)) return data
|
|
49
|
-
return []
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Extract a stable id from a fetched item — different shapes use
|
|
53
|
-
* `id` vs `external_id`. Used by the chat loader to match the fetched
|
|
54
|
-
* array element back to the marker's id. */
|
|
55
|
-
function extractItemId(type: string, item: unknown): string | null {
|
|
56
|
-
if (!item || typeof item !== 'object') return null
|
|
57
|
-
const obj = item as Record<string, unknown>
|
|
58
|
-
if (type === 'roadmap_item' || type === 'delivery_item' || type === 'internal_task') {
|
|
59
|
-
const ext = obj.external_id
|
|
60
|
-
if (typeof ext === 'string') return ext
|
|
61
|
-
}
|
|
62
|
-
// RoadmapItem-shaped responses use `id` to carry the external_id; the
|
|
63
|
-
// mapper renames external_id→id at the API boundary. Treat both id and
|
|
64
|
-
// external_id as primary-key candidates for these clickup-backed types.
|
|
65
|
-
const id = obj.id
|
|
66
|
-
if (typeof id === 'string') return id
|
|
67
|
-
if (typeof id === 'number') return String(id)
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
33
|
+
// `extractItems` / `extractItemId` hoisted to `src/utils/extract-items.ts`
|
|
34
|
+
// (shared with the related-content rail — react-query-free home).
|
|
70
35
|
|
|
71
36
|
export function useChatCardItem<T = unknown>(
|
|
72
37
|
type: string,
|
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Lifted from `lib/data/investor-update-utils-shared.ts` in the hub
|
|
7
|
-
* (the server-side data utils in the same file stay hub-side; only the
|
|
8
|
-
* TYPE moves here).
|
|
2
|
+
* RELOCATED — the canonical `ContentRef` now lives at `src/types/content-ref.ts`
|
|
3
|
+
* (server-safe home, shared by the related-content rail + chat entities).
|
|
4
|
+
* This stub keeps the chat entities barrel + `entities/investor-update.ts`
|
|
5
|
+
* compiling unchanged. One definition package-wide.
|
|
9
6
|
*/
|
|
10
|
-
|
|
11
|
-
export interface ContentRef {
|
|
12
|
-
type: string;
|
|
13
|
-
id: string;
|
|
14
|
-
slug: string;
|
|
15
|
-
url: string;
|
|
16
|
-
targetPlatform?: string | null;
|
|
17
|
-
title: string;
|
|
18
|
-
summary?: string;
|
|
19
|
-
image_url?: string;
|
|
20
|
-
image_bg_color?: string;
|
|
21
|
-
visibility: 'public' | 'internal';
|
|
22
|
-
display_order: number;
|
|
23
|
-
}
|
|
7
|
+
export type { ContentRef, ContentRefWithReason } from '../../../../types/content-ref';
|
|
@@ -10,6 +10,8 @@ interface ChevronButtonProps {
|
|
|
10
10
|
backgroundColor?: string;
|
|
11
11
|
borderColor?: string;
|
|
12
12
|
onClick?: (e: React.MouseEvent) => void;
|
|
13
|
+
/** Forwarded to the inner <button> for assistive tech. */
|
|
14
|
+
'aria-label'?: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export function ChevronButton({
|
|
@@ -18,7 +20,8 @@ export function ChevronButton({
|
|
|
18
20
|
size = 'icon',
|
|
19
21
|
backgroundColor = 'transparent',
|
|
20
22
|
borderColor = 'transparent',
|
|
21
|
-
onClick
|
|
23
|
+
onClick,
|
|
24
|
+
'aria-label': ariaLabel,
|
|
22
25
|
}: ChevronButtonProps) {
|
|
23
26
|
const [isHovered, setIsHovered] = React.useState(false);
|
|
24
27
|
|
|
@@ -37,6 +40,7 @@ export function ChevronButton({
|
|
|
37
40
|
variant="transparent"
|
|
38
41
|
size="icon"
|
|
39
42
|
onClick={onClick}
|
|
43
|
+
aria-label={ariaLabel}
|
|
40
44
|
onMouseEnter={() => setIsHovered(true)}
|
|
41
45
|
onMouseLeave={() => setIsHovered(false)}
|
|
42
46
|
className={cn(
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import React from 'react'
|
|
3
|
+
import React, { useMemo } from 'react'
|
|
4
4
|
import type { Faq } from '../../types/faq'
|
|
5
5
|
import { FaqAccordion, type FaqItem } from '../faq-accordion'
|
|
6
6
|
import { useSelfFetch } from '../../hooks/use-self-fetch'
|
|
7
|
-
import {
|
|
7
|
+
import { buildSuggestionUrl } from '../../utils/suggestion-url'
|
|
8
|
+
import { buildFaqJsonLdFromFaqs, type FaqSchemaOptions } from './json-ld'
|
|
8
9
|
|
|
9
10
|
export interface FaqSectionProps {
|
|
10
11
|
/**
|
|
@@ -28,16 +29,26 @@ export interface FaqSectionProps {
|
|
|
28
29
|
/** Overrides for the JSON-LD's name/description/url. */
|
|
29
30
|
jsonLd?: FaqSchemaOptions
|
|
30
31
|
className?: string
|
|
32
|
+
/** Maps to /api/faqs `?count=` (the 5-tier fill target). Absent → param
|
|
33
|
+
* not sent (server default applies). */
|
|
34
|
+
minResults?: number
|
|
35
|
+
/** Fetch-URL prefix for third-party embeds / reverse proxies
|
|
36
|
+
* ('' = same-origin relative). */
|
|
37
|
+
apiBaseUrl?: string
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
const DEFAULT_HEADING_TEXT = 'Frequently Asked Questions'
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
/** URL composition shared with RelatedContentSection (`buildSuggestionUrl`)
|
|
43
|
+
* — byte-identical to the historical `buildFaqsUrl` output when
|
|
44
|
+
* `minResults`/`apiBaseUrl` are absent. */
|
|
45
|
+
function buildFaqsUrl(
|
|
46
|
+
entityType?: string,
|
|
47
|
+
entityId?: number | string,
|
|
48
|
+
minResults?: number,
|
|
49
|
+
apiBaseUrl = '',
|
|
50
|
+
): string {
|
|
51
|
+
return buildSuggestionUrl('/api/faqs', { apiBaseUrl, entityType, entityId, count: minResults })
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
function groupBySection(faqs: Faq[]): Array<{ section: string | null; items: FaqItem[] }> {
|
|
@@ -60,7 +71,7 @@ function groupBySection(faqs: Faq[]): Array<{ section: string | null; items: Faq
|
|
|
60
71
|
function FaqSkeleton() {
|
|
61
72
|
return (
|
|
62
73
|
<div className="space-y-8 animate-pulse">
|
|
63
|
-
<div className="h-
|
|
74
|
+
<div className="h-12 md:h-14 w-2/3 rounded bg-ods-border" />
|
|
64
75
|
<div className="rounded-3xl border border-ods-border overflow-hidden bg-ods-card divide-y divide-ods-border w-full">
|
|
65
76
|
{Array.from({ length: 8 }).map((_, idx) => (
|
|
66
77
|
<div key={idx} className="flex items-center justify-between px-6 md:px-8 py-6">
|
|
@@ -97,9 +108,16 @@ export function FaqSection({
|
|
|
97
108
|
emitJsonLd = false,
|
|
98
109
|
jsonLd,
|
|
99
110
|
className,
|
|
111
|
+
minResults,
|
|
112
|
+
apiBaseUrl = '',
|
|
100
113
|
}: FaqSectionProps) {
|
|
101
|
-
const url = buildFaqsUrl(entityType, entityId)
|
|
102
|
-
|
|
114
|
+
const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl)
|
|
115
|
+
// Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render
|
|
116
|
+
// wrapper object would setState-loop under re-rendering parents.
|
|
117
|
+
const initialData = useMemo<FaqsResponse | undefined>(
|
|
118
|
+
() => (initialFaqs ? { faqs: initialFaqs } : undefined),
|
|
119
|
+
[initialFaqs],
|
|
120
|
+
)
|
|
103
121
|
const { data, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData })
|
|
104
122
|
|
|
105
123
|
const faqs = data?.faqs ?? []
|
|
@@ -116,7 +134,24 @@ export function FaqSection({
|
|
|
116
134
|
return null
|
|
117
135
|
}
|
|
118
136
|
|
|
137
|
+
// Embedded mode, zero FAQs after load: render nothing — an embedded host
|
|
138
|
+
// page (blog/case-study detail) must not show an empty section shell.
|
|
139
|
+
// The standalone /faqs page (heading set) keeps its existing behavior.
|
|
140
|
+
if (!isLoading && !error && faqs.length === 0 && !showHeading) {
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
|
|
119
144
|
if (isLoading && faqs.length === 0) {
|
|
145
|
+
// Embedded mode renders skeletons WITHOUT the standalone page shell —
|
|
146
|
+
// a host detail page already provides <main> + gutters; nesting them
|
|
147
|
+
// here would emit invalid markup and double padding.
|
|
148
|
+
if (!showHeading) {
|
|
149
|
+
return (
|
|
150
|
+
<div className={className}>
|
|
151
|
+
<FaqSkeleton />
|
|
152
|
+
</div>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
120
155
|
return (
|
|
121
156
|
<main className={className ?? 'bg-ods-bg'}>
|
|
122
157
|
<div className="max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto">
|
|
@@ -136,23 +171,33 @@ export function FaqSection({
|
|
|
136
171
|
|
|
137
172
|
const schema = emitJsonLd ? buildFaqJsonLdFromFaqs(faqs, jsonLd) : null
|
|
138
173
|
|
|
174
|
+
const groupNodes = groups.map(({ section, items }) => (
|
|
175
|
+
<div key={section || 'default'} className="space-y-4">
|
|
176
|
+
{section && (
|
|
177
|
+
<h2 className="text-h2 tracking-[-0.04em] text-ods-text-primary mb-3 md:mb-4">
|
|
178
|
+
{section}
|
|
179
|
+
</h2>
|
|
180
|
+
)}
|
|
181
|
+
<FaqAccordion items={items} />
|
|
182
|
+
</div>
|
|
183
|
+
))
|
|
184
|
+
|
|
185
|
+
// Embedded mode (heading === null): no <main> page shell, no page gutters —
|
|
186
|
+
// the host page owns layout. Standalone mode keeps the historical markup.
|
|
187
|
+
const body = showHeading ? (
|
|
188
|
+
<main className={className ?? 'bg-ods-bg'}>
|
|
189
|
+
<div className="max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto space-y-10">
|
|
190
|
+
{headingNode}
|
|
191
|
+
{groupNodes}
|
|
192
|
+
</div>
|
|
193
|
+
</main>
|
|
194
|
+
) : (
|
|
195
|
+
<section className={className ?? 'space-y-10'}>{groupNodes}</section>
|
|
196
|
+
)
|
|
197
|
+
|
|
139
198
|
return (
|
|
140
199
|
<>
|
|
141
|
-
|
|
142
|
-
<div className="max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto space-y-10">
|
|
143
|
-
{showHeading && headingNode}
|
|
144
|
-
{groups.map(({ section, items }) => (
|
|
145
|
-
<div key={section || 'default'} className="space-y-4">
|
|
146
|
-
{section && (
|
|
147
|
-
<h2 className="text-h2 tracking-[-0.04em] text-ods-text-primary mb-3 md:mb-4">
|
|
148
|
-
{section}
|
|
149
|
-
</h2>
|
|
150
|
-
)}
|
|
151
|
-
<FaqAccordion items={items} />
|
|
152
|
-
</div>
|
|
153
|
-
))}
|
|
154
|
-
</div>
|
|
155
|
-
</main>
|
|
200
|
+
{body}
|
|
156
201
|
{schema && (
|
|
157
202
|
<script
|
|
158
203
|
type="application/ld+json"
|
|
@@ -79,8 +79,9 @@ export function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps)
|
|
|
79
79
|
<h3>
|
|
80
80
|
{item.question}
|
|
81
81
|
</h3>
|
|
82
|
-
<div className="flex-shrink-0"
|
|
82
|
+
<div className="flex-shrink-0">
|
|
83
83
|
<ChevronButton
|
|
84
|
+
aria-label={isOpen ? 'Collapse question' : 'Expand question'}
|
|
84
85
|
size="md"
|
|
85
86
|
isExpanded={isOpen}
|
|
86
87
|
backgroundColor="transparent"
|
package/src/components/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export * from './faq-accordion'
|
|
|
26
26
|
// dedicated server-safe subpath "./components/faq/json-ld" (built without the
|
|
27
27
|
// client banner under the server/universal block of tsup.config.ts).
|
|
28
28
|
export * from './faq'
|
|
29
|
+
export * from './related-content'
|
|
29
30
|
export * from './filter-chip'
|
|
30
31
|
export * from './footer'
|
|
31
32
|
export * from './unified-filter-logic'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// CLIENT subpath barrel — `@flamingo-stack/openframe-frontend-core/components/related-content`
|
|
2
|
+
export {
|
|
3
|
+
RelatedContentSection,
|
|
4
|
+
type RelatedContentSectionProps,
|
|
5
|
+
type CardLinkProvider,
|
|
6
|
+
type CardLinkProviderProps,
|
|
7
|
+
type CardLinkAnchorProps,
|
|
8
|
+
type AdminCampaignCardSlot,
|
|
9
|
+
} from './related-content-section'
|