@flamingo-stack/openframe-frontend-core 0.0.736 → 0.0.738
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/build/export-map.json +1 -1
- package/dist/{chunk-AZL332TK.cjs → chunk-2QMBXGMQ.cjs} +15 -2
- package/dist/{chunk-7V2KP2TB.cjs → chunk-2SBHPHLS.cjs} +4 -4
- package/dist/{chunk-LIH5OQLY.cjs → chunk-2UZS3ZH7.cjs} +8 -8
- package/dist/{chunk-MGW5DO6P.cjs → chunk-3BBOAMWJ.cjs} +7 -7
- package/dist/{chunk-HZJRERJW.cjs → chunk-3RFQPQ7Z.cjs} +3 -3
- package/dist/{chunk-T27CVQBO.cjs → chunk-4T6ETBL4.cjs} +151 -83
- package/dist/{chunk-JM54CNZZ.cjs → chunk-4YQNJJWM.cjs} +95 -95
- package/dist/{chunk-UBQSOB3X.cjs → chunk-5BKAUXLR.cjs} +4 -4
- package/dist/{chunk-YADG5Q7C.cjs → chunk-63N4FN5V.cjs} +3 -3
- package/dist/{chunk-URX2YTCL.cjs → chunk-66R4NGGU.cjs} +77 -71
- package/dist/{chunk-EJOXXJJB.cjs → chunk-7F3V45AH.cjs} +29 -29
- package/dist/{chunk-BDF3FNOC.cjs → chunk-7GETNFZ5.cjs} +49 -49
- package/dist/{chunk-L4ZNR5V7.cjs → chunk-7LTG6CJ3.cjs} +67 -67
- package/dist/{chunk-MJGX357L.cjs → chunk-B27QEHYR.cjs} +2 -2
- package/dist/{chunk-OV6BGMEL.cjs → chunk-BRKTN6UR.cjs} +340 -328
- package/dist/{chunk-VRNNIV5Y.cjs → chunk-EG545WGV.cjs} +4 -4
- package/dist/{chunk-ZJNYWNCB.cjs → chunk-FG7HTGT3.cjs} +24 -24
- package/dist/{chunk-77SD7RXY.cjs → chunk-GAF4L3JW.cjs} +7 -7
- package/dist/{chunk-FTGVG6FW.cjs → chunk-GQZOUVXC.cjs} +34 -34
- package/dist/{chunk-ZEX5WU35.cjs → chunk-HNYLTEIS.cjs} +48 -69
- package/dist/{chunk-FCGLSWKR.cjs → chunk-IFS4AH77.cjs} +19 -1
- package/dist/{chunk-L535OD7V.cjs → chunk-JZNESLJX.cjs} +2 -2
- package/dist/{chunk-SZFA2LQR.cjs → chunk-KQ4IKALJ.cjs} +35 -35
- package/dist/{chunk-EX2GYIGW.cjs → chunk-LY5VOKV7.cjs} +6 -6
- package/dist/{chunk-XLSVQJBA.cjs → chunk-MLKL2YVE.cjs} +44 -44
- package/dist/{chunk-BL5GOCHP.cjs → chunk-MTNNYSP3.cjs} +26 -18
- package/dist/{chunk-ZRYKDN2J.cjs → chunk-N2CISBJJ.cjs} +15 -15
- package/dist/chunk-N5NDPULP.cjs +57 -0
- package/dist/{chunk-P6Z3EBPJ.cjs → chunk-R36NZTUJ.cjs} +26 -26
- package/dist/{chunk-OJUMVOMP.cjs → chunk-U5BRSRXA.cjs} +923 -759
- package/dist/{chunk-DUXLZPMJ.cjs → chunk-UMO55S55.cjs} +2 -2
- package/dist/{chunk-JGGSRCRD.cjs → chunk-XGHZIYQN.cjs} +24 -24
- package/dist/{chunk-NONQUGRP.cjs → chunk-XMNLPQ75.cjs} +12 -12
- package/dist/{cloud-tenants-M55HMW6F.cjs → cloud-tenants-FTQGWXHS.cjs} +16 -16
- package/dist/components/agent-identity.d.ts +8 -1
- package/dist/components/agent-identity.d.ts.map +1 -1
- package/dist/components/agent-identity.js +6 -1
- package/dist/components/calendar.cjs +4 -4
- package/dist/components/case-studies/index.cjs +28 -28
- package/dist/components/chat/chat-turn.d.ts +23 -0
- package/dist/components/chat/chat-turn.d.ts.map +1 -0
- package/dist/components/chat/chat-turn.js +30 -0
- package/dist/components/chat/entity-cards/index.d.ts +1 -0
- package/dist/components/chat/entity-cards/index.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/index.js +2 -0
- package/dist/components/chat/hooks/use-ask-prompts.d.ts +1 -1
- package/dist/components/chat/hooks/use-ask-prompts.js +1 -1
- package/dist/components/chat/index.cjs +16 -12
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +1 -0
- package/dist/components/chat/mingo-info-card.d.ts +17 -1
- package/dist/components/chat/mingo-info-card.d.ts.map +1 -1
- package/dist/components/chat/mingo-info-card.js +15 -3
- package/dist/components/contact/index.cjs +17 -17
- package/dist/components/docs/index.cjs +20 -20
- package/dist/components/embeds/index.cjs +17 -17
- package/dist/components/faq/faq-ask-card.d.ts +5 -4
- package/dist/components/faq/faq-ask-card.d.ts.map +1 -1
- package/dist/components/faq/faq-document-page.d.ts +3 -1
- package/dist/components/faq/faq-document-page.d.ts.map +1 -1
- package/dist/components/faq/faq-document-page.js +2 -0
- package/dist/components/faq/faq-section.d.ts +37 -5
- package/dist/components/faq/faq-section.d.ts.map +1 -1
- package/dist/components/faq/faq-section.js +36 -9
- package/dist/components/faq/index.cjs +23 -17
- package/dist/components/faq/index.d.ts +1 -1
- package/dist/components/faq/index.d.ts.map +1 -1
- package/dist/components/faq/index.js +4 -2
- package/dist/components/features/auth/auth-benefits-panel.d.ts +3 -5
- package/dist/components/features/auth/auth-benefits-panel.d.ts.map +1 -1
- package/dist/components/features/auth/auth-benefits-panel.js +3 -19
- package/dist/components/features/conversation-card.d.ts +12 -4
- package/dist/components/features/conversation-card.d.ts.map +1 -1
- package/dist/components/features/conversation-card.js +30 -38
- package/dist/components/features/index.cjs +16 -16
- package/dist/components/help-center-pages/delivery-page.d.ts +8 -1
- package/dist/components/help-center-pages/delivery-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/delivery-page.js +16 -4
- package/dist/components/help-center-pages/download-apps-page.d.ts +8 -1
- package/dist/components/help-center-pages/download-apps-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/download-apps-page.js +11 -6
- package/dist/components/help-center-pages/index.cjs +216 -161
- package/dist/components/help-center-pages/onboarding-guides-catalog-page.d.ts +8 -1
- package/dist/components/help-center-pages/onboarding-guides-catalog-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/onboarding-guides-catalog-page.js +26 -14
- package/dist/components/help-center-pages/product-releases-list-page.d.ts +8 -1
- package/dist/components/help-center-pages/product-releases-list-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/product-releases-list-page.js +24 -12
- package/dist/components/help-center-pages/roadmap-page.d.ts +8 -1
- package/dist/components/help-center-pages/roadmap-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/roadmap-page.js +25 -13
- package/dist/components/index.cjs +103 -79
- package/dist/components/meeting-scheduler/index.cjs +49 -49
- package/dist/components/navigation/index.cjs +19 -19
- package/dist/components/onboarding-guides/index.cjs +19 -19
- package/dist/components/platform/ToolBadge.d.ts.map +1 -1
- package/dist/components/platform/ToolBadge.js +4 -3
- package/dist/components/product-demo/index.cjs +8 -8
- package/dist/components/related-content/index.cjs +8 -8
- package/dist/components/shared/dev-section/dev-section-page.d.ts +8 -1
- package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -1
- package/dist/components/shared/dev-section/dev-section-page.js +8 -3
- package/dist/components/shared/legal-document/legal-document-page.d.ts +8 -1
- package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -1
- package/dist/components/shared/legal-document/legal-document-page.js +4 -1
- package/dist/components/tickets/index.cjs +22 -21
- package/dist/components/ui/app-window-frame.d.ts +7 -1
- package/dist/components/ui/app-window-frame.d.ts.map +1 -1
- package/dist/components/ui/app-window-frame.js +10 -2
- package/dist/components/ui/benefit-list.d.ts +42 -0
- package/dist/components/ui/benefit-list.d.ts.map +1 -0
- package/dist/components/ui/benefit-list.js +62 -0
- package/dist/components/ui/button/button.d.ts +1 -0
- package/dist/components/ui/button/button.d.ts.map +1 -1
- package/dist/components/ui/button/button.js +15 -2
- package/dist/components/ui/dropdown-button.d.ts +20 -2
- package/dist/components/ui/dropdown-button.d.ts.map +1 -1
- package/dist/components/ui/dropdown-button.js +57 -1
- package/dist/components/ui/file-manager/index.cjs +11 -11
- package/dist/components/ui/index.cjs +32 -16
- package/dist/components/ui/index.d.ts +3 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +3 -0
- package/dist/components/ui/leader-row.d.ts +17 -0
- package/dist/components/ui/leader-row.d.ts.map +1 -0
- package/dist/components/ui/leader-row.js +14 -0
- package/dist/components/ui/strip-card.d.ts +23 -0
- package/dist/components/ui/strip-card.d.ts.map +1 -0
- package/dist/components/ui/strip-card.js +56 -0
- package/dist/contexts/assistant-runtime-context.d.ts +5 -5
- package/dist/{devices-WWJZFG6E.cjs → devices-MPI5HUQS.cjs} +17 -17
- package/dist/{embeddable-chat-76IJ6TQZ.cjs → embeddable-chat-7WLUDBGK.cjs} +84 -84
- package/dist/index.cjs +46 -20
- package/dist/{knowledge-F4YFQT3G.cjs → knowledge-BS663XXL.cjs} +17 -17
- package/dist/{logs-GAYEGEZM.cjs → logs-KS2A2VN5.cjs} +25 -25
- package/dist/{policies-ZOVJCJWX.cjs → policies-H3OK6H4Q.cjs} +20 -20
- package/dist/{remote-session-Z4YZFG5E.cjs → remote-session-EHKXU4HI.cjs} +7 -7
- package/dist/{software-update-H4YE5GMJ.cjs → software-update-HMMMWIL3.cjs} +20 -20
- package/dist/{tickets-board-JOSYLOS7.cjs → tickets-board-3YJKOOZE.cjs} +10 -10
- package/dist/types/faq.d.ts +17 -0
- package/dist/types/faq.d.ts.map +1 -1
- package/dist/types/faq.js +4 -0
- package/dist/types/index.cjs +4 -0
- package/dist/utils/demo-clock.d.ts +22 -0
- package/dist/utils/demo-clock.d.ts.map +1 -0
- package/dist/utils/demo-clock.js +40 -0
- package/dist/utils/identity-tint.d.ts +13 -0
- package/dist/utils/identity-tint.d.ts.map +1 -0
- package/dist/utils/identity-tint.js +14 -0
- package/dist/utils/index.cjs +54 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/{video-file-player-6NWGXZ3W.cjs → video-file-player-ZH2ILAPO.cjs} +12 -12
- package/package.json +1 -1
- package/src/components/agent-identity.tsx +12 -1
- package/src/components/chat/chat-turn.tsx +52 -0
- package/src/components/chat/entity-cards/index.ts +1 -0
- package/src/components/chat/hooks/use-ask-prompts.ts +2 -2
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/mingo-info-card.tsx +39 -4
- package/src/components/faq/faq-ask-card.tsx +5 -4
- package/src/components/faq/faq-document-page.tsx +4 -0
- package/src/components/faq/faq-section.tsx +86 -14
- package/src/components/faq/index.ts +1 -1
- package/src/components/features/auth/auth-benefits-panel.tsx +5 -32
- package/src/components/features/conversation-card.tsx +48 -46
- package/src/components/help-center-pages/delivery-page.tsx +16 -1
- package/src/components/help-center-pages/download-apps-page.tsx +9 -0
- package/src/components/help-center-pages/onboarding-guides-catalog-page.tsx +16 -1
- package/src/components/help-center-pages/product-releases-list-page.tsx +16 -1
- package/src/components/help-center-pages/roadmap-page.tsx +16 -1
- package/src/components/platform/ToolBadge.tsx +4 -3
- package/src/components/shared/dev-section/dev-section-page.tsx +9 -0
- package/src/components/shared/legal-document/legal-document-page.tsx +9 -0
- package/src/components/ui/app-window-frame.tsx +18 -2
- package/src/components/ui/benefit-list.tsx +97 -0
- package/src/components/ui/button/button.tsx +14 -1
- package/src/components/ui/dropdown-button.tsx +73 -1
- package/src/components/ui/index.ts +3 -0
- package/src/components/ui/leader-row.tsx +28 -0
- package/src/components/ui/strip-card.tsx +68 -0
- package/src/contexts/assistant-runtime-context.tsx +5 -5
- package/src/types/faq.ts +18 -0
- package/src/utils/demo-clock.ts +62 -0
- package/src/utils/identity-tint.ts +23 -0
- package/src/utils/index.ts +2 -0
|
@@ -27,6 +27,15 @@ export interface FaqSectionProps {
|
|
|
27
27
|
/** Both required together for entity-attached FAQs; partial → bare /api/faqs. */
|
|
28
28
|
entityType?: string;
|
|
29
29
|
entityId?: number | string;
|
|
30
|
+
/**
|
|
31
|
+
* What the page this FAQ sits on is about: ANY string the host's FAQ
|
|
32
|
+
* endpoint knows. ONE topic for the whole block: the questions are the FAQs
|
|
33
|
+
* written for it (`/api/faqs?topic=`; `initialFaqs` are then the host's read
|
|
34
|
+
* of the same topic) and the "ask the assistant" card offers the questions
|
|
35
|
+
* written for it. Whoever renders the FAQ states it; the lib names none.
|
|
36
|
+
* Ignored for the questions when the FAQ is attached to an entity.
|
|
37
|
+
*/
|
|
38
|
+
topic?: string;
|
|
30
39
|
/**
|
|
31
40
|
* Heading node above the grouped list. `undefined` → default
|
|
32
41
|
* `<h2>`"Frequently Asked Questions". `null` → no heading (the host page
|
|
@@ -43,6 +52,14 @@ export interface FaqSectionProps {
|
|
|
43
52
|
/** Overrides for the JSON-LD's name/description/url. */
|
|
44
53
|
jsonLd?: FaqSchemaOptions;
|
|
45
54
|
className?: string;
|
|
55
|
+
/** The `id` of the rendered section: what an in-page link to the FAQ points at (`PAGE_FAQ_SECTION_ID` for a page's own block). */
|
|
56
|
+
id?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Print the topic's bare name in small muted text under the heading
|
|
59
|
+
* (`pricing`), so whoever edits the FAQs can see which topic a page asks
|
|
60
|
+
* for. Default off. The section always carries it as `data-faq-topic` too.
|
|
61
|
+
*/
|
|
62
|
+
showTopic?: boolean;
|
|
46
63
|
/** Maps to /api/faqs `?count=` (the 5-tier fill target). Absent → param
|
|
47
64
|
* not sent (server default applies). */
|
|
48
65
|
minResults?: number;
|
|
@@ -60,10 +77,10 @@ export interface FaqSectionProps {
|
|
|
60
77
|
* The "ask the assistant" card beside the questions. Shown wherever the
|
|
61
78
|
* host's assistant runtime says a chat is there to open
|
|
62
79
|
* (`AssistantRuntimeContext`: its name, glyph, questions endpoint and
|
|
63
|
-
* opener) AND the FAQ has a topic: `
|
|
64
|
-
*
|
|
65
|
-
* No runtime or no topic: the FAQ is the single column it
|
|
66
|
-
* lib names no topic of its own. `false` switches the card off; the object
|
|
80
|
+
* opener) AND the FAQ has a topic: the block's `topic`, else the
|
|
81
|
+
* `entityType` the FAQ is attached to (`ask.topic` states another one for
|
|
82
|
+
* the card alone). No runtime or no topic: the FAQ is the single column it
|
|
83
|
+
* always was; the lib names no topic of its own. `false` switches the card off; the object
|
|
67
84
|
* also states the questions to leave out, the wording and the chat it opens
|
|
68
85
|
* (`onOpen`). An `aside` takes the card's place.
|
|
69
86
|
*/
|
|
@@ -72,11 +89,47 @@ export interface FaqSectionProps {
|
|
|
72
89
|
|
|
73
90
|
const DEFAULT_HEADING_TEXT = 'Frequently Asked Questions';
|
|
74
91
|
|
|
92
|
+
/** The id of a page's own FAQ block: what a page's "FAQ" link or section nav points at. */
|
|
93
|
+
export const PAGE_FAQ_SECTION_ID = 'faq';
|
|
94
|
+
|
|
95
|
+
/** A page's own FAQ block inside a page that already provides the container: vertical rhythm only. */
|
|
96
|
+
const PAGE_FAQ_IN_PAGE_CLASS = 'space-y-10 pt-12 md:pt-20';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* What a shared page of the lib takes as its `faq` prop: the topic the page is
|
|
100
|
+
* about (REQUIRED, stated by the host, never read from the page's address) and
|
|
101
|
+
* where the host serves the FAQs. The page renders `FaqSection` with it, inside
|
|
102
|
+
* its own container, after its content (`pageFaqProps`).
|
|
103
|
+
*/
|
|
104
|
+
export type PageFaqSlot = Pick<
|
|
105
|
+
FaqSectionProps,
|
|
106
|
+
'apiBaseUrl' | 'initialFaqs' | 'heading' | 'ask' | 'emitJsonLd' | 'jsonLd' | 'showTopic'
|
|
107
|
+
> & { topic: string };
|
|
108
|
+
|
|
109
|
+
/** The `FaqSection` props of a shared page's own FAQ block: the host's slot, as the page's `#faq` section. */
|
|
110
|
+
export function pageFaqProps(faq: PageFaqSlot): FaqSectionProps {
|
|
111
|
+
return { ...faq, id: PAGE_FAQ_SECTION_ID, className: PAGE_FAQ_IN_PAGE_CLASS };
|
|
112
|
+
}
|
|
113
|
+
|
|
75
114
|
/** URL composition shared with RelatedContentSection (`buildSuggestionUrl`)
|
|
76
115
|
* — byte-identical to the historical `buildFaqsUrl` output when
|
|
77
116
|
* `minResults`/`apiBaseUrl` are absent. */
|
|
78
|
-
function buildFaqsUrl(
|
|
79
|
-
|
|
117
|
+
function buildFaqsUrl(
|
|
118
|
+
entityType?: string,
|
|
119
|
+
entityId?: number | string,
|
|
120
|
+
minResults?: number,
|
|
121
|
+
apiBaseUrl = '',
|
|
122
|
+
topic?: string,
|
|
123
|
+
): string {
|
|
124
|
+
// An entity's FAQs are the entity's: a topic narrows only the platform's own list.
|
|
125
|
+
const entityScoped = !!entityType && entityId !== undefined && entityId !== null && entityId !== '';
|
|
126
|
+
return buildSuggestionUrl('/api/faqs', {
|
|
127
|
+
apiBaseUrl,
|
|
128
|
+
entityType,
|
|
129
|
+
entityId,
|
|
130
|
+
count: minResults,
|
|
131
|
+
extraParams: entityScoped ? undefined : { topic },
|
|
132
|
+
});
|
|
80
133
|
}
|
|
81
134
|
|
|
82
135
|
interface FaqGroup {
|
|
@@ -390,6 +443,8 @@ interface FaqsResponse {
|
|
|
390
443
|
* page owns the <h1>) + `emitJsonLd` with `jsonLd` overrides for SEO.
|
|
391
444
|
* - Per-entity embed: pass `entityType` + `entityId` (no `initialFaqs`); the
|
|
392
445
|
* hook self-fetches `GET /api/faqs`, and `heading` is this block's own <h2>.
|
|
446
|
+
* - A page's own FAQ: pass `topic` (what the page is about); the hook
|
|
447
|
+
* self-fetches `GET /api/faqs?topic=`, and the ask card takes the same topic.
|
|
393
448
|
*
|
|
394
449
|
* CONTRACT: the consuming app MUST implement `GET /api/faqs`. On a fetch error
|
|
395
450
|
* (or zero FAQs) the component renders nothing so the host page isn't
|
|
@@ -400,10 +455,13 @@ export function FaqSection({
|
|
|
400
455
|
initialFaqs,
|
|
401
456
|
entityType,
|
|
402
457
|
entityId,
|
|
458
|
+
topic,
|
|
403
459
|
heading,
|
|
404
460
|
emitJsonLd = false,
|
|
405
461
|
jsonLd,
|
|
406
462
|
className,
|
|
463
|
+
id,
|
|
464
|
+
showTopic = false,
|
|
407
465
|
minResults,
|
|
408
466
|
apiBaseUrl = '',
|
|
409
467
|
aside: asideProp,
|
|
@@ -412,30 +470,44 @@ export function FaqSection({
|
|
|
412
470
|
const assistant = useAssistantRuntime();
|
|
413
471
|
// The host's own block wins; else the card, where a chat is there to open and the FAQ has a topic
|
|
414
472
|
// (an empty topic is no topic, as the questions request reads it).
|
|
415
|
-
const askTopic = ask === false ? undefined : ask?.topic || entityType;
|
|
473
|
+
const askTopic = ask === false ? undefined : ask?.topic || topic || entityType;
|
|
416
474
|
const aside =
|
|
417
475
|
asideProp ??
|
|
418
476
|
(ask !== false && askTopic && faqAskCardShown(assistant) ? <FaqAskCard {...ask} topic={askTopic} /> : undefined);
|
|
419
|
-
const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl);
|
|
477
|
+
const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl, topic);
|
|
420
478
|
// Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render
|
|
421
479
|
// wrapper object would setState-loop under re-rendering parents.
|
|
422
480
|
const initialData = useMemo<FaqsResponse | undefined>(
|
|
423
481
|
() => (initialFaqs ? { faqs: initialFaqs } : undefined),
|
|
424
482
|
[initialFaqs],
|
|
425
483
|
);
|
|
426
|
-
const { data, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData });
|
|
484
|
+
const { data, dataUrl, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData });
|
|
427
485
|
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
|
|
486
|
+
// Only the answer for THIS url: while another topic's questions load, the
|
|
487
|
+
// previous topic's are not this page's. Memoised because the `??` fallback
|
|
488
|
+
// minted a NEW array on every render, which made the memo below re-run
|
|
489
|
+
// every time — i.e. do nothing.
|
|
490
|
+
const held = dataUrl === url ? data?.faqs : undefined;
|
|
491
|
+
const faqs = useMemo(() => held ?? [], [held]);
|
|
431
492
|
// Grouped before the early returns so the hook order stays stable.
|
|
432
493
|
const groups = useMemo(() => (faqs.length > 0 ? groupFaqsBySection(faqs) : []), [faqs]);
|
|
433
494
|
|
|
434
495
|
// `undefined` → default <h2> title; `null` → the host page owns the <h1>, so
|
|
435
496
|
// no title renders here. `heading === null` also makes the category headings
|
|
436
497
|
// <h2> (directly under the page <h1>); otherwise they nest as <h3>.
|
|
498
|
+
const titleNode = heading === undefined ? <h2 className={SECTION_HEADING_CLASS}>{DEFAULT_HEADING_TEXT}</h2> : heading;
|
|
437
499
|
const headingNode =
|
|
438
|
-
|
|
500
|
+
showTopic && topic ? (
|
|
501
|
+
// The host's heading may arrive from a server component, still streaming: React cannot check such a
|
|
502
|
+
// node when it sits in a list of siblings and warns about a missing key. So it is the ONLY child of its
|
|
503
|
+
// own wrapper, and the two siblings here are this component's own elements.
|
|
504
|
+
<div className="space-y-1">
|
|
505
|
+
<div>{titleNode}</div>
|
|
506
|
+
<p className="text-ods-text-muted text-h6">{topic}</p>
|
|
507
|
+
</div>
|
|
508
|
+
) : (
|
|
509
|
+
titleNode
|
|
510
|
+
);
|
|
439
511
|
|
|
440
512
|
// Degrade silently — never show an error banner or an empty section shell
|
|
441
513
|
// where FAQs would be (host pages and the standalone surface both rely on it).
|
|
@@ -453,7 +525,7 @@ export function FaqSection({
|
|
|
453
525
|
|
|
454
526
|
return (
|
|
455
527
|
<>
|
|
456
|
-
<section className={className ?? 'space-y-10'}>
|
|
528
|
+
<section id={id} data-faq-topic={topic} className={className ?? 'space-y-10'}>
|
|
457
529
|
{!aside && headingNode}
|
|
458
530
|
<GroupedFaqList
|
|
459
531
|
groups={groups}
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
//
|
|
10
10
|
// (Server-safe subpath is wired in tsup.config.ts under the server/universal
|
|
11
11
|
// block and exposed via "./components/faq/json-ld" in package.json#exports.)
|
|
12
|
-
export { FaqSection, type FaqSectionProps } from './faq-section';
|
|
12
|
+
export { FaqSection, PAGE_FAQ_SECTION_ID, pageFaqProps, type FaqSectionProps, type PageFaqSlot } from './faq-section';
|
|
13
13
|
export { FaqDocumentPage, type FaqDocumentPageProps } from './faq-document-page';
|
|
14
14
|
export { FaqAskCard, type FaqAskOptions } from './faq-ask-card';
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
4
3
|
import { cn } from '../../../utils/cn';
|
|
5
|
-
import { trialLabels } from '../../../utils/trial';
|
|
6
|
-
import { FileOffIcon } from '../../icons-v2-generated/documents/file-off-icon';
|
|
7
|
-
import { CreditCardXmarkIcon } from '../../icons-v2-generated/finance/credit-card-xmark-icon';
|
|
8
4
|
import { OpenframeLogoIcon } from '../../icons-v2-generated/logos/openframe-logo-icon';
|
|
9
|
-
import {
|
|
10
|
-
import { XmarkAltIcon } from '../../icons-v2-generated/signs-and-symbols/xmark-alt-icon';
|
|
5
|
+
import { BenefitList, trialBenefits, type BenefitItem } from '../../ui/benefit-list';
|
|
11
6
|
import { Button } from '../../ui/button';
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
label: string;
|
|
16
|
-
}
|
|
8
|
+
/** One benefit of the panel: the lib's benefit item. */
|
|
9
|
+
export type AuthBenefit = BenefitItem;
|
|
17
10
|
|
|
18
11
|
export interface AuthBenefitsPanelProps {
|
|
19
12
|
title?: string;
|
|
@@ -31,17 +24,6 @@ const DEFAULT_TITLE = 'The All-in-One Open Platform for MSPs';
|
|
|
31
24
|
const DEFAULT_DESCRIPTION =
|
|
32
25
|
'All your core ops in one place - built for MSPs who are done duct-taping tools together. Unified stack, AI-ready, no vendor tax. Just solid software that lets you run lean and fast.';
|
|
33
26
|
|
|
34
|
-
/** The default chips: the trial's terms, and its length when the host knows it. */
|
|
35
|
-
function defaultBenefits(trialDays: number | null | undefined): AuthBenefit[] {
|
|
36
|
-
const labels = trialLabels(trialDays);
|
|
37
|
-
return [
|
|
38
|
-
{ icon: <CreditCardXmarkIcon />, label: labels.noCard },
|
|
39
|
-
{ icon: <XmarkAltIcon />, label: labels.cancel },
|
|
40
|
-
...(labels.length ? [{ icon: <FlaskVialIcon />, label: labels.length }] : []),
|
|
41
|
-
{ icon: <FileOffIcon />, label: labels.noContract },
|
|
42
|
-
];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
27
|
/**
|
|
46
28
|
* Marketing panel shown alongside the auth forms — title, blurb, benefit chips
|
|
47
29
|
* and a "learn more" link. Presentational; content is overridable via props.
|
|
@@ -50,7 +32,7 @@ export function AuthBenefitsPanel({
|
|
|
50
32
|
title = DEFAULT_TITLE,
|
|
51
33
|
description = DEFAULT_DESCRIPTION,
|
|
52
34
|
trialDays,
|
|
53
|
-
benefits =
|
|
35
|
+
benefits = trialBenefits(trialDays),
|
|
54
36
|
learnMoreLabel = 'Learn More About OpenFrame',
|
|
55
37
|
learnMoreUrl = 'https://www.flamingo.run/',
|
|
56
38
|
className,
|
|
@@ -65,16 +47,7 @@ export function AuthBenefitsPanel({
|
|
|
65
47
|
<h2 className="tracking-[-0.64px] text-ods-text-primary text-h2">{title}</h2>
|
|
66
48
|
<p className="text-ods-text-primary text-h4">{description}</p>
|
|
67
49
|
|
|
68
|
-
<
|
|
69
|
-
{benefits.map(benefit => (
|
|
70
|
-
<div key={benefit.label} className="flex items-center gap-[var(--spacing-system-xs)]">
|
|
71
|
-
<span className="flex size-4 shrink-0 items-center justify-center text-ods-text-secondary [&_svg]:size-4">
|
|
72
|
-
{benefit.icon}
|
|
73
|
-
</span>
|
|
74
|
-
<span className="whitespace-nowrap text-ods-text-primary text-h4">{benefit.label}</span>
|
|
75
|
-
</div>
|
|
76
|
-
))}
|
|
77
|
-
</div>
|
|
50
|
+
<BenefitList items={benefits} />
|
|
78
51
|
|
|
79
52
|
<Button
|
|
80
53
|
variant="outline"
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import type { ReactNode } from 'react';
|
|
2
4
|
import { cn } from '../../utils/cn';
|
|
5
|
+
import { useAgentTint } from '../agent-identity';
|
|
6
|
+
import { ChatTurn } from '../chat/chat-turn';
|
|
3
7
|
import { CheckCircleIcon } from '../icons-v2-generated/signs-and-symbols/check-circle-icon';
|
|
8
|
+
import { Tag } from '../ui/tag';
|
|
4
9
|
|
|
5
10
|
export interface ConversationCardSpeaker {
|
|
6
11
|
/** Rendered as given (an avatar, an agent mark). */
|
|
@@ -8,11 +13,13 @@ export interface ConversationCardSpeaker {
|
|
|
8
13
|
name: string;
|
|
9
14
|
/** A quieter word after the name ("on her laptop", "this computer"). */
|
|
10
15
|
meta?: string;
|
|
16
|
+
/** When they spoke, as shown ("2:47 PM"). */
|
|
17
|
+
time?: string;
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
export interface ConversationCardProps {
|
|
14
21
|
requester: ConversationCardSpeaker;
|
|
15
|
-
/** What they said
|
|
22
|
+
/** What they said. */
|
|
16
23
|
request: string;
|
|
17
24
|
responder: ConversationCardSpeaker;
|
|
18
25
|
/** What was done about it. */
|
|
@@ -25,22 +32,19 @@ export interface ConversationCardProps {
|
|
|
25
32
|
footerValue: string;
|
|
26
33
|
/** `attention` paints the answer in the warning colour: it needs a person. */
|
|
27
34
|
footerTone?: 'default' | 'attention';
|
|
35
|
+
/**
|
|
36
|
+
* The slug of the agent that answered. The card takes that agent's tint from
|
|
37
|
+
* its identity, so it reads as that agent's at a glance. A card names its
|
|
38
|
+
* agent, never a colour.
|
|
39
|
+
*/
|
|
40
|
+
agent?: string | null;
|
|
28
41
|
className?: string;
|
|
29
42
|
}
|
|
30
43
|
|
|
31
|
-
function Who({ speaker }: { speaker: ConversationCardSpeaker }) {
|
|
32
|
-
return (
|
|
33
|
-
<div className="flex items-baseline gap-2 text-ods-text-primary text-h4">
|
|
34
|
-
{speaker.name}
|
|
35
|
-
{speaker.meta && <span className="text-ods-text-secondary text-h6">{speaker.meta}</span>}
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
44
|
/**
|
|
41
45
|
* A request and what was done about it, as two turns and a footer: who asked
|
|
42
|
-
* and what they said, who answered and the result
|
|
43
|
-
* the reader's own part in it.
|
|
46
|
+
* and what they said, who answered and the result (a command block with its
|
|
47
|
+
* check), then one line that sums up the reader's own part in it.
|
|
44
48
|
*/
|
|
45
49
|
export function ConversationCard({
|
|
46
50
|
requester,
|
|
@@ -51,47 +55,45 @@ export function ConversationCard({
|
|
|
51
55
|
footerLabel,
|
|
52
56
|
footerValue,
|
|
53
57
|
footerTone = 'default',
|
|
58
|
+
agent,
|
|
54
59
|
className,
|
|
55
60
|
}: ConversationCardProps) {
|
|
61
|
+
const tint = useAgentTint(agent);
|
|
56
62
|
return (
|
|
57
63
|
<div
|
|
58
|
-
className={cn(
|
|
64
|
+
className={cn(
|
|
65
|
+
'flex h-full flex-col gap-[var(--spacing-system-mf)] overflow-hidden rounded-md border border-ods-border bg-ods-bg p-[var(--spacing-system-mf)]',
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
style={tint}
|
|
59
69
|
>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
{/* Every part keeps a fixed number of lines, so cards in a row or a carousel line up part by part. */}
|
|
71
|
+
<ChatTurn avatar={requester.avatar} name={requester.name} meta={requester.meta} time={requester.time}>
|
|
72
|
+
<p className="m-0 line-clamp-2 min-h-[2lh] text-ods-text-primary text-h3" title={request}>
|
|
73
|
+
“{request}”
|
|
74
|
+
</p>
|
|
75
|
+
</ChatTurn>
|
|
76
|
+
{/* The answer is a command block: who did it, what was done, and its check. */}
|
|
77
|
+
<ChatTurn
|
|
78
|
+
avatar={responder.avatar}
|
|
79
|
+
name={responder.name}
|
|
80
|
+
meta={responder.meta}
|
|
81
|
+
time={responder.time}
|
|
82
|
+
className="rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-sf)]"
|
|
83
|
+
>
|
|
84
|
+
<div className="flex items-start gap-[var(--spacing-system-xsf)] text-ods-text-secondary text-h4">
|
|
85
|
+
<span className="line-clamp-3 min-h-[3lh] min-w-0 flex-1" title={outcome}>
|
|
86
|
+
{outcome}
|
|
87
|
+
</span>
|
|
88
|
+
{outcomeAside}
|
|
89
|
+
<span className="flex h-[1lh] shrink-0 items-center">
|
|
90
|
+
<CheckCircleIcon size={16} className="text-ods-success" aria-hidden />
|
|
91
|
+
</span>
|
|
81
92
|
</div>
|
|
82
|
-
</
|
|
83
|
-
<div className="mt-auto flex items-center justify-between gap-
|
|
93
|
+
</ChatTurn>
|
|
94
|
+
<div className="mt-auto flex items-center justify-between gap-[var(--spacing-system-sf)] text-ods-text-secondary text-h6">
|
|
84
95
|
<span>{footerLabel}</span>
|
|
85
|
-
<span
|
|
86
|
-
className={cn(
|
|
87
|
-
'inline-flex whitespace-nowrap rounded-full border px-3 py-1',
|
|
88
|
-
footerTone === 'attention'
|
|
89
|
-
? 'border-ods-warning text-ods-warning'
|
|
90
|
-
: 'border-ods-border text-ods-text-secondary',
|
|
91
|
-
)}
|
|
92
|
-
>
|
|
93
|
-
{footerValue}
|
|
94
|
-
</span>
|
|
96
|
+
<Tag as="span" label={footerValue} variant={footerTone === 'attention' ? 'warning' : 'badge'} />
|
|
95
97
|
</div>
|
|
96
98
|
</div>
|
|
97
99
|
);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ReactNode } from 'react';
|
|
11
|
+
import type { PageFaqSlot } from '../faq/faq-section';
|
|
11
12
|
import { DeliveryLists } from '../shared/delivery';
|
|
12
13
|
import { DevSectionPage } from '../shared/dev-section';
|
|
13
14
|
|
|
@@ -22,6 +23,12 @@ export interface DeliveryPageProps {
|
|
|
22
23
|
subtitle?: string;
|
|
23
24
|
/** Optional slot rendered below the lists, inside the page chrome. */
|
|
24
25
|
belowContent?: ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* The page's own FAQ block: the topic the page is about (stated by the host,
|
|
28
|
+
* never read from its address) and where the host serves the FAQs. Rendered
|
|
29
|
+
* after the page's content, inside its container. Absent: no FAQ block.
|
|
30
|
+
*/
|
|
31
|
+
faq?: PageFaqSlot;
|
|
25
32
|
/** Render the standalone `<PageShell>`. Default true. Pass false when the host
|
|
26
33
|
* layout already provides the page container (forwarded to `DevSectionPage`). */
|
|
27
34
|
shell?: boolean;
|
|
@@ -34,10 +41,18 @@ export function DeliveryPage({
|
|
|
34
41
|
title,
|
|
35
42
|
subtitle,
|
|
36
43
|
belowContent,
|
|
44
|
+
faq,
|
|
37
45
|
shell,
|
|
38
46
|
}: DeliveryPageProps) {
|
|
39
47
|
return (
|
|
40
|
-
<DevSectionPage
|
|
48
|
+
<DevSectionPage
|
|
49
|
+
sectionKey="delivery"
|
|
50
|
+
backButton={backButton}
|
|
51
|
+
title={title}
|
|
52
|
+
subtitle={subtitle}
|
|
53
|
+
faq={faq}
|
|
54
|
+
shell={shell}
|
|
55
|
+
>
|
|
41
56
|
<DeliveryLists completedApiEndpoint={completedEndpoint} inProgressApiEndpoint={inProgressEndpoint} />
|
|
42
57
|
{belowContent}
|
|
43
58
|
</DevSectionPage>
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
import { cn } from '../../utils/cn';
|
|
53
53
|
import { formatUrlForDisplay } from '../../utils/format';
|
|
54
54
|
import { DESKTOP_OSES, type DesktopOs } from '../../utils/visitor-os';
|
|
55
|
+
import { FaqSection, pageFaqProps, type PageFaqSlot } from '../faq/faq-section';
|
|
55
56
|
import { CommandBox } from '../features/command-box';
|
|
56
57
|
import { AppleLogoIcon } from '../icons-v2-generated/brand-logos/apple-logo-icon';
|
|
57
58
|
import { WindowsLogoGreyIcon } from '../icons-v2-generated/brand-logos/windows-logo-grey-icon';
|
|
@@ -86,6 +87,12 @@ export interface DownloadAppsPageProps {
|
|
|
86
87
|
onDownloadAction?: (event: DownloadActionEvent) => void;
|
|
87
88
|
/** The closing card, under the mobile app: using OpenFrame without installing anything. Default: none. */
|
|
88
89
|
web?: DownloadAppsWebOption;
|
|
90
|
+
/**
|
|
91
|
+
* The page's own FAQ block: the topic the page is about (stated by the host,
|
|
92
|
+
* never read from its address) and where the host serves the FAQs. Rendered
|
|
93
|
+
* after the page's content, inside its container. Absent: no FAQ block.
|
|
94
|
+
*/
|
|
95
|
+
faq?: PageFaqSlot;
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
/** The "no install" card: the host's words and its own button (the page holds no signup link). */
|
|
@@ -281,6 +288,7 @@ export function DownloadAppsPage({
|
|
|
281
288
|
openStoresInNewTab = true,
|
|
282
289
|
onDownloadAction,
|
|
283
290
|
web,
|
|
291
|
+
faq,
|
|
284
292
|
}: DownloadAppsPageProps) {
|
|
285
293
|
const { data, isLoading, error, reload } = useDownloads({ endpoint, initialData });
|
|
286
294
|
// The page adapts to the server's answer: a section exists only for what it names.
|
|
@@ -362,6 +370,7 @@ export function DownloadAppsPage({
|
|
|
362
370
|
{body}
|
|
363
371
|
{web && <WebCard web={web} />}
|
|
364
372
|
</div>
|
|
373
|
+
{faq && <FaqSection {...pageFaqProps(faq)} />}
|
|
365
374
|
</DownloadAppsChrome>
|
|
366
375
|
);
|
|
367
376
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ReactNode } from 'react';
|
|
12
12
|
import type { OnboardingGuide } from '../chat/types/entities/onboarding-guide';
|
|
13
|
+
import type { PageFaqSlot } from '../faq/faq-section';
|
|
13
14
|
import { OnboardingGuidesCatalogView } from '../onboarding-guides';
|
|
14
15
|
import { DevSectionPage } from '../shared/dev-section';
|
|
15
16
|
|
|
@@ -32,6 +33,12 @@ export interface OnboardingGuidesCatalogPageProps {
|
|
|
32
33
|
subtitle?: string;
|
|
33
34
|
/** Optional slot rendered below the catalog, inside the page chrome. */
|
|
34
35
|
belowContent?: ReactNode;
|
|
36
|
+
/**
|
|
37
|
+
* The page's own FAQ block: the topic the page is about (stated by the host,
|
|
38
|
+
* never read from its address) and where the host serves the FAQs. Rendered
|
|
39
|
+
* after the page's content, inside its container. Absent: no FAQ block.
|
|
40
|
+
*/
|
|
41
|
+
faq?: PageFaqSlot;
|
|
35
42
|
/** Render the standalone `<PageShell>`. Default true. Pass false when the host
|
|
36
43
|
* layout already provides the page container (forwarded to `DevSectionPage`). */
|
|
37
44
|
shell?: boolean;
|
|
@@ -48,10 +55,18 @@ export function OnboardingGuidesCatalogPage({
|
|
|
48
55
|
title,
|
|
49
56
|
subtitle,
|
|
50
57
|
belowContent,
|
|
58
|
+
faq,
|
|
51
59
|
shell,
|
|
52
60
|
}: OnboardingGuidesCatalogPageProps) {
|
|
53
61
|
return (
|
|
54
|
-
<DevSectionPage
|
|
62
|
+
<DevSectionPage
|
|
63
|
+
sectionKey="onboarding"
|
|
64
|
+
backButton={backButton}
|
|
65
|
+
title={title}
|
|
66
|
+
subtitle={subtitle}
|
|
67
|
+
faq={faq}
|
|
68
|
+
shell={shell}
|
|
69
|
+
>
|
|
55
70
|
<OnboardingGuidesCatalogView
|
|
56
71
|
guidesEndpoint={guidesEndpoint}
|
|
57
72
|
sectionsEndpoint={sectionsEndpoint}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ReactNode } from 'react';
|
|
12
12
|
import type { ProductRelease, ProductReleaseListResponse } from '../../types/product-release';
|
|
13
|
+
import type { PageFaqSlot } from '../faq/faq-section';
|
|
13
14
|
import { DevSectionPage } from '../shared/dev-section';
|
|
14
15
|
import { ProductReleasesView, type ProductReleaseCardExtras } from '../shared/product-release';
|
|
15
16
|
|
|
@@ -28,6 +29,12 @@ export interface ProductReleasesListPageProps {
|
|
|
28
29
|
subtitle?: string;
|
|
29
30
|
/** Optional slot rendered below the list, inside the page chrome. */
|
|
30
31
|
belowContent?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* The page's own FAQ block: the topic the page is about (stated by the host,
|
|
34
|
+
* never read from its address) and where the host serves the FAQs. Rendered
|
|
35
|
+
* after the page's content, inside its container. Absent: no FAQ block.
|
|
36
|
+
*/
|
|
37
|
+
faq?: PageFaqSlot;
|
|
31
38
|
/** Render the standalone `<PageShell>`. Default true. Pass false when the host
|
|
32
39
|
* layout already provides the page container (forwarded to `DevSectionPage`). */
|
|
33
40
|
shell?: boolean;
|
|
@@ -42,10 +49,18 @@ export function ProductReleasesListPage({
|
|
|
42
49
|
title,
|
|
43
50
|
subtitle,
|
|
44
51
|
belowContent,
|
|
52
|
+
faq,
|
|
45
53
|
shell,
|
|
46
54
|
}: ProductReleasesListPageProps) {
|
|
47
55
|
return (
|
|
48
|
-
<DevSectionPage
|
|
56
|
+
<DevSectionPage
|
|
57
|
+
sectionKey="releases"
|
|
58
|
+
backButton={backButton}
|
|
59
|
+
title={title}
|
|
60
|
+
subtitle={subtitle}
|
|
61
|
+
faq={faq}
|
|
62
|
+
shell={shell}
|
|
63
|
+
>
|
|
49
64
|
<ProductReleasesView
|
|
50
65
|
endpoint={releasesEndpoint}
|
|
51
66
|
basePath={basePath}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ReactNode } from 'react';
|
|
12
12
|
import type { RoadmapItem } from '../chat/types/entities/roadmap-item';
|
|
13
|
+
import type { PageFaqSlot } from '../faq/faq-section';
|
|
13
14
|
import { DevSectionPage } from '../shared/dev-section';
|
|
14
15
|
import { RoadmapView } from '../shared/roadmap';
|
|
15
16
|
import type { UseRoadmapVotingOptions } from '../shared/roadmap';
|
|
@@ -35,6 +36,12 @@ export interface RoadmapPageProps {
|
|
|
35
36
|
/** Optional slot rendered below the list, inside the page chrome — e.g. the
|
|
36
37
|
* hub's related-content rail. */
|
|
37
38
|
belowContent?: ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* The page's own FAQ block: the topic the page is about (stated by the host,
|
|
41
|
+
* never read from its address) and where the host serves the FAQs. Rendered
|
|
42
|
+
* after the page's content, inside its container. Absent: no FAQ block.
|
|
43
|
+
*/
|
|
44
|
+
faq?: PageFaqSlot;
|
|
38
45
|
/** Render the standalone `<PageShell>`. Default true. Pass false when the host
|
|
39
46
|
* layout already provides the page container (forwarded to `DevSectionPage`). */
|
|
40
47
|
shell?: boolean;
|
|
@@ -51,10 +58,18 @@ export function RoadmapPage({
|
|
|
51
58
|
title,
|
|
52
59
|
subtitle,
|
|
53
60
|
belowContent,
|
|
61
|
+
faq,
|
|
54
62
|
shell,
|
|
55
63
|
}: RoadmapPageProps) {
|
|
56
64
|
return (
|
|
57
|
-
<DevSectionPage
|
|
65
|
+
<DevSectionPage
|
|
66
|
+
sectionKey="roadmap"
|
|
67
|
+
backButton={backButton}
|
|
68
|
+
title={title}
|
|
69
|
+
subtitle={subtitle}
|
|
70
|
+
faq={faq}
|
|
71
|
+
shell={shell}
|
|
72
|
+
>
|
|
58
73
|
<RoadmapView
|
|
59
74
|
endpoint={roadmapEndpoint}
|
|
60
75
|
initialItems={initialItems}
|
|
@@ -85,13 +85,14 @@ export const ToolBadge: React.FC<ToolBadgeProps> = ({
|
|
|
85
85
|
return linked(
|
|
86
86
|
<span
|
|
87
87
|
className={cn(
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
// The house tag (Figma `tag`): 32px, on the card surface, the caption face.
|
|
89
|
+
'inline-flex h-8 items-center gap-[var(--spacing-system-xs)] rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-xsf)] text-ods-text-primary text-h5',
|
|
90
|
+
href && 'transition-colors hover:border-ods-border-hover',
|
|
90
91
|
className,
|
|
91
92
|
)}
|
|
92
93
|
>
|
|
93
94
|
{mark && (
|
|
94
|
-
<span className="flex h-
|
|
95
|
+
<span className="flex h-4 w-4 shrink-0 items-center justify-center [&_img]:h-full [&_img]:w-full [&_img]:rounded-sm [&_img]:object-contain [&_svg]:h-full [&_svg]:w-full">
|
|
95
96
|
{mark}
|
|
96
97
|
</span>
|
|
97
98
|
)}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
OPENFRAME_DEV_SECTIONS,
|
|
23
23
|
type OpenframeDevSectionKey,
|
|
24
24
|
} from '../../../utils/dev-sections/openframe-dev-sections';
|
|
25
|
+
import { FaqSection, pageFaqProps, type PageFaqSlot } from '../../faq/faq-section';
|
|
25
26
|
import { PageShell, PageLayout } from '../../ui';
|
|
26
27
|
import { DevSectionView } from './dev-section-view';
|
|
27
28
|
|
|
@@ -51,6 +52,12 @@ export interface DevSectionPageProps {
|
|
|
51
52
|
* `false` when the host layout already provides the page container (e.g.
|
|
52
53
|
* openframe-frontend's `AppLayout` `<main>`): then only the
|
|
53
54
|
* `page-shell-content` padding box is rendered, avoiding a nested `<main>`. */
|
|
55
|
+
/**
|
|
56
|
+
* The page's own FAQ block: the topic the page is about (stated by the host,
|
|
57
|
+
* never read from its address) and where the host serves the FAQs. Rendered
|
|
58
|
+
* after the page's content, inside its container. Absent: no FAQ block.
|
|
59
|
+
*/
|
|
60
|
+
faq?: PageFaqSlot;
|
|
54
61
|
shell?: boolean;
|
|
55
62
|
}
|
|
56
63
|
|
|
@@ -61,6 +68,7 @@ export function DevSectionPage({
|
|
|
61
68
|
backButton,
|
|
62
69
|
title,
|
|
63
70
|
subtitle,
|
|
71
|
+
faq,
|
|
64
72
|
shell = true,
|
|
65
73
|
}: DevSectionPageProps) {
|
|
66
74
|
const router = useRouter();
|
|
@@ -95,6 +103,7 @@ export function DevSectionPage({
|
|
|
95
103
|
<DevSectionView sectionKey={sectionKey} showHeading={false} preControls={preControls}>
|
|
96
104
|
{children}
|
|
97
105
|
</DevSectionView>
|
|
106
|
+
{faq && <FaqSection {...pageFaqProps(faq)} />}
|
|
98
107
|
</PageLayout>
|
|
99
108
|
);
|
|
100
109
|
|