@syntrologie/adapt-faq 2.16.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +7 -0
- package/dist/chunk-S6WIENQP.js +578 -0
- package/dist/chunk-S6WIENQP.js.map +7 -0
- package/dist/editor.d.ts +35 -33
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +4821 -308
- package/dist/editor.js.map +7 -0
- package/dist/runtime.d.ts +3 -5
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +848 -91
- package/dist/runtime.js.map +7 -0
- package/dist/schema.d.ts +609 -77
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +444 -206
- package/dist/schema.js.map +7 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -20
- package/dist/FAQWidget.d.ts +0 -33
- package/dist/FAQWidget.d.ts.map +0 -1
- package/dist/FAQWidget.js +0 -375
- package/dist/FAQWidgetLit.js +0 -534
- package/dist/cdn.d.ts +0 -70
- package/dist/cdn.d.ts.map +0 -1
- package/dist/cdn.js +0 -46
- package/dist/editor-lit.d.ts +0 -37
- package/dist/editor-lit.d.ts.map +0 -1
- package/dist/editor-lit.js +0 -195
- package/dist/executors.js +0 -150
- package/dist/faq-styles.js +0 -204
- package/dist/faq-types.js +0 -7
- package/dist/runtime-lit.d.ts +0 -85
- package/dist/runtime-lit.d.ts.map +0 -1
- package/dist/runtime-lit.js +0 -94
- package/dist/state.js +0 -132
- package/dist/summarize.js +0 -62
- package/dist/types.js +0 -17
- package/node_modules/@syntrologie/sdk-contracts/dist/index.d.ts +0 -129
- package/node_modules/@syntrologie/sdk-contracts/dist/index.js +0 -17
- package/node_modules/@syntrologie/sdk-contracts/dist/schemas.d.ts +0 -2296
- package/node_modules/@syntrologie/sdk-contracts/dist/schemas.js +0 -361
- package/node_modules/@syntrologie/sdk-contracts/package.json +0 -33
package/dist/FAQWidget.js
DELETED
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Adaptive FAQ - FAQWidget Component
|
|
4
|
-
*
|
|
5
|
-
* React component that renders a collapsible Q&A accordion with per-item
|
|
6
|
-
* conditional visibility based on triggerWhen decision strategies.
|
|
7
|
-
*
|
|
8
|
-
* Demonstrates the compositional action pattern where child actions
|
|
9
|
-
* (faq:question) serve as configuration data for the parent widget.
|
|
10
|
-
*/
|
|
11
|
-
import { purple } from '@syntro/design-system/tokens';
|
|
12
|
-
import { Marked } from 'marked';
|
|
13
|
-
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
|
|
14
|
-
import { createRoot } from 'react-dom/client';
|
|
15
|
-
const marked = new Marked({ async: false, gfm: true, breaks: true });
|
|
16
|
-
import { baseStyles, themeStyles } from './faq-styles';
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Helpers
|
|
19
|
-
// ============================================================================
|
|
20
|
-
/** Extract plain text from an FAQAnswer for search matching */
|
|
21
|
-
function getAnswerText(answer) {
|
|
22
|
-
if (typeof answer === 'string')
|
|
23
|
-
return answer;
|
|
24
|
-
if (answer.type === 'rich')
|
|
25
|
-
return answer.html;
|
|
26
|
-
return answer.content;
|
|
27
|
-
}
|
|
28
|
-
/** Render an FAQAnswer based on its type */
|
|
29
|
-
function renderAnswer(answer) {
|
|
30
|
-
if (typeof answer === 'string') {
|
|
31
|
-
const html = marked.parse(answer);
|
|
32
|
-
return (
|
|
33
|
-
// biome-ignore lint/security/noDangerouslySetInnerHtml: content is CMS/config content, not user-controlled input
|
|
34
|
-
_jsx("div", { style: { margin: 0 }, "data-faq-markdown": "", dangerouslySetInnerHTML: { __html: html } }));
|
|
35
|
-
}
|
|
36
|
-
if (answer.type === 'rich') {
|
|
37
|
-
// biome-ignore lint/security/noDangerouslySetInnerHtml: content is pre-sanitized by backend — FAQAnswer.html is CMS/config content, not user-controlled input
|
|
38
|
-
return _jsx("div", { style: { margin: 0 }, dangerouslySetInnerHTML: { __html: answer.html } });
|
|
39
|
-
}
|
|
40
|
-
// markdown — parse to HTML and render
|
|
41
|
-
const html = marked.parse(answer.content);
|
|
42
|
-
return (
|
|
43
|
-
// biome-ignore lint/security/noDangerouslySetInnerHtml: markdown content is CMS/config content, not user-controlled input
|
|
44
|
-
_jsx("div", { style: { margin: 0 }, "data-faq-markdown": "", dangerouslySetInnerHTML: { __html: html } }));
|
|
45
|
-
}
|
|
46
|
-
/** Resolve feedback config into a normalized FeedbackConfig or null */
|
|
47
|
-
function resolveFeedbackConfig(feedback) {
|
|
48
|
-
if (!feedback)
|
|
49
|
-
return null;
|
|
50
|
-
if (feedback === true) {
|
|
51
|
-
return { style: 'thumbs' };
|
|
52
|
-
}
|
|
53
|
-
return feedback;
|
|
54
|
-
}
|
|
55
|
-
/** Get the feedback prompt text */
|
|
56
|
-
function getFeedbackPrompt(feedbackConfig) {
|
|
57
|
-
return feedbackConfig.prompt || 'Was this helpful?';
|
|
58
|
-
}
|
|
59
|
-
function FAQItem({ item, isExpanded, isHighlighted, isLast, onToggle, theme, feedbackConfig, feedbackValue, onFeedback, }) {
|
|
60
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
61
|
-
const colors = themeStyles[theme];
|
|
62
|
-
const { question, answer } = item.config;
|
|
63
|
-
const itemStyle = {
|
|
64
|
-
...baseStyles.item,
|
|
65
|
-
...colors.item,
|
|
66
|
-
...(isExpanded ? colors.itemExpanded : {}),
|
|
67
|
-
...(isHighlighted
|
|
68
|
-
? {
|
|
69
|
-
// purple[4] = #6a59ce — design system primary purple
|
|
70
|
-
boxShadow: `0 0 0 2px ${purple[4]}, 0 0 12px rgba(106, 89, 206, 0.4)`,
|
|
71
|
-
transition: 'box-shadow 0.3s ease',
|
|
72
|
-
}
|
|
73
|
-
: {}),
|
|
74
|
-
...(!isLast ? { borderBottom: 'var(--sc-content-item-divider, none)' } : {}),
|
|
75
|
-
};
|
|
76
|
-
const questionStyle = {
|
|
77
|
-
...baseStyles.question,
|
|
78
|
-
...colors.question,
|
|
79
|
-
...(isHovered ? colors.questionHover : {}),
|
|
80
|
-
};
|
|
81
|
-
const chevronStyle = {
|
|
82
|
-
...baseStyles.chevron,
|
|
83
|
-
transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
|
|
84
|
-
};
|
|
85
|
-
const answerStyle = {
|
|
86
|
-
...baseStyles.answer,
|
|
87
|
-
...colors.answer,
|
|
88
|
-
maxHeight: isExpanded ? '500px' : '0',
|
|
89
|
-
paddingBottom: isExpanded ? '16px' : '0',
|
|
90
|
-
};
|
|
91
|
-
const feedbackStyle = {
|
|
92
|
-
...baseStyles.feedback,
|
|
93
|
-
...colors.feedbackPrompt,
|
|
94
|
-
};
|
|
95
|
-
return (_jsxs("div", { style: itemStyle, "data-faq-item-id": item.config.id, children: [_jsxs("button", { type: "button", style: questionStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [_jsx("span", { children: question }), _jsx("span", { style: chevronStyle, children: '\u203A' })] }), _jsxs("div", { style: answerStyle, "aria-hidden": !isExpanded, children: [renderAnswer(answer), isExpanded && feedbackConfig && (_jsxs("div", { style: feedbackStyle, children: [_jsx("span", { children: getFeedbackPrompt(feedbackConfig) }), _jsx("button", { type: "button", style: {
|
|
96
|
-
...baseStyles.feedbackButton,
|
|
97
|
-
...(feedbackValue === 'up' ? baseStyles.feedbackButtonSelected : {}),
|
|
98
|
-
}, "aria-label": "Thumbs up", onClick: () => onFeedback(item.config.id, question, 'up'), children: '\uD83D\uDC4D' }), _jsx("button", { type: "button", style: {
|
|
99
|
-
...baseStyles.feedbackButton,
|
|
100
|
-
...(feedbackValue === 'down' ? baseStyles.feedbackButtonSelected : {}),
|
|
101
|
-
}, "aria-label": "Thumbs down", onClick: () => onFeedback(item.config.id, question, 'down'), children: '\uD83D\uDC4E' })] }))] })] }));
|
|
102
|
-
}
|
|
103
|
-
// ============================================================================
|
|
104
|
-
// FAQWidget Component
|
|
105
|
-
// ============================================================================
|
|
106
|
-
/**
|
|
107
|
-
* FAQWidget - Renders a collapsible Q&A accordion with per-item activation.
|
|
108
|
-
*
|
|
109
|
-
* This component demonstrates the compositional action pattern:
|
|
110
|
-
* - Parent (FAQWidget) receives `config.actions` array
|
|
111
|
-
* - Each action has optional `triggerWhen` for per-item visibility
|
|
112
|
-
* - Parent evaluates triggerWhen and filters visible questions
|
|
113
|
-
* - Parent manages expand state and re-rendering on context changes
|
|
114
|
-
*/
|
|
115
|
-
export function FAQWidget({ config, runtime, instanceId }) {
|
|
116
|
-
// Force re-render when context/accumulator changes.
|
|
117
|
-
// renderTick is used as a useMemo dependency to invalidate cached triggerWhen evaluations.
|
|
118
|
-
const [renderTick, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
119
|
-
// Track expanded question IDs
|
|
120
|
-
const [expandedIds, setExpandedIds] = useState(new Set());
|
|
121
|
-
// Track which item is flash-highlighted from a deep-link
|
|
122
|
-
const [highlightId, setHighlightId] = useState(null);
|
|
123
|
-
// Search query state
|
|
124
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
125
|
-
// Track feedback state per item
|
|
126
|
-
const [feedbackState, setFeedbackState] = useState(new Map());
|
|
127
|
-
// Resolve feedback config
|
|
128
|
-
const feedbackConfig = useMemo(() => resolveFeedbackConfig(config.feedback), [config.feedback]);
|
|
129
|
-
// Subscribe to context changes for reactive updates
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
const unsubscribe = runtime.context.subscribe(() => {
|
|
132
|
-
forceUpdate();
|
|
133
|
-
});
|
|
134
|
-
return unsubscribe;
|
|
135
|
-
}, [runtime.context]);
|
|
136
|
-
// Subscribe to accumulator changes for event_count-based triggerWhen
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
if (!runtime.accumulator?.subscribe)
|
|
139
|
-
return;
|
|
140
|
-
return runtime.accumulator.subscribe(() => {
|
|
141
|
-
forceUpdate();
|
|
142
|
-
});
|
|
143
|
-
}, [runtime.accumulator]);
|
|
144
|
-
// Subscribe to faq:open:* events from overlay CTA clicks
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
if (!runtime.events.subscribe)
|
|
147
|
-
return;
|
|
148
|
-
// Check EventBus history for pending faq:open events
|
|
149
|
-
// (may have fired before this widget mounted, e.g. when canvas was closed)
|
|
150
|
-
if (runtime.events.getRecent) {
|
|
151
|
-
const recentEvents = runtime.events.getRecent({ patterns: ['^action\\.tooltip_cta_clicked$', '^action\\.modal_cta_clicked$'] }, 10);
|
|
152
|
-
const pendingEvent = recentEvents
|
|
153
|
-
.filter((e) => {
|
|
154
|
-
const actionId = e.props?.actionId;
|
|
155
|
-
return typeof actionId === 'string' && actionId.startsWith('faq:open:');
|
|
156
|
-
})
|
|
157
|
-
.pop(); // Most recent
|
|
158
|
-
if (pendingEvent && Date.now() - pendingEvent.ts < 10000) {
|
|
159
|
-
const questionId = pendingEvent.props.actionId.replace('faq:open:', '');
|
|
160
|
-
setExpandedIds(new Set([questionId]));
|
|
161
|
-
// Scroll into view after render
|
|
162
|
-
requestAnimationFrame(() => {
|
|
163
|
-
const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
|
|
164
|
-
if (el)
|
|
165
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// Subscribe to future CTA events
|
|
170
|
-
const unsubscribe = runtime.events.subscribe({ patterns: ['^action\\.tooltip_cta_clicked$', '^action\\.modal_cta_clicked$'] }, (event) => {
|
|
171
|
-
const actionId = event.props?.actionId;
|
|
172
|
-
if (typeof actionId !== 'string' || !actionId.startsWith('faq:open:'))
|
|
173
|
-
return;
|
|
174
|
-
const questionId = actionId.replace('faq:open:', '');
|
|
175
|
-
setExpandedIds(new Set([questionId]));
|
|
176
|
-
// Scroll the question into view
|
|
177
|
-
requestAnimationFrame(() => {
|
|
178
|
-
const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
|
|
179
|
-
if (el)
|
|
180
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
181
|
-
});
|
|
182
|
-
// Request canvas open (for future tile-based FAQ rendering)
|
|
183
|
-
runtime.events.publish('canvas.requestOpen');
|
|
184
|
-
});
|
|
185
|
-
return unsubscribe;
|
|
186
|
-
}, [runtime]);
|
|
187
|
-
// Subscribe to notification.deep_link events (from insertHtml deepLink clicks + notification toasts)
|
|
188
|
-
useEffect(() => {
|
|
189
|
-
if (!runtime.events.subscribe)
|
|
190
|
-
return;
|
|
191
|
-
const handleDeepLink = (event) => {
|
|
192
|
-
const tileId = event.props?.tileId;
|
|
193
|
-
const itemId = event.props?.itemId;
|
|
194
|
-
// Only handle if this deep link targets our tile
|
|
195
|
-
if (tileId !== instanceId)
|
|
196
|
-
return;
|
|
197
|
-
if (!itemId)
|
|
198
|
-
return;
|
|
199
|
-
// Expand the target item
|
|
200
|
-
setExpandedIds(new Set([itemId]));
|
|
201
|
-
// Flash-highlight the item
|
|
202
|
-
setHighlightId(itemId);
|
|
203
|
-
setTimeout(() => setHighlightId(null), 1500);
|
|
204
|
-
// Scroll into view after render
|
|
205
|
-
requestAnimationFrame(() => {
|
|
206
|
-
const el = document.querySelector(`[data-faq-item-id="${itemId}"]`);
|
|
207
|
-
if (el)
|
|
208
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
209
|
-
});
|
|
210
|
-
};
|
|
211
|
-
// Check recent events (may have fired before widget mounted, e.g. canvas was closed)
|
|
212
|
-
if (runtime.events.getRecent) {
|
|
213
|
-
const recent = runtime.events.getRecent({ names: ['notification.deep_link'] }, 5);
|
|
214
|
-
const pending = recent
|
|
215
|
-
.filter((e) => e.props?.tileId === instanceId && e.props?.itemId)
|
|
216
|
-
.pop();
|
|
217
|
-
if (pending && Date.now() - pending.ts < 10000) {
|
|
218
|
-
handleDeepLink(pending);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// Subscribe to future events
|
|
222
|
-
const unsubscribe = runtime.events.subscribe({ names: ['notification.deep_link'] }, handleDeepLink);
|
|
223
|
-
return unsubscribe;
|
|
224
|
-
}, [runtime, instanceId]);
|
|
225
|
-
// Filter visible questions based on per-item triggerWhen
|
|
226
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: renderTick is intentionally included to force re-evaluation when the runtime's mutable context changes (subscribed above via forceUpdate)
|
|
227
|
-
const visibleQuestions = useMemo(() => (config.actions ?? []).filter((q) => {
|
|
228
|
-
// No triggerWhen = always visible
|
|
229
|
-
if (!q.triggerWhen)
|
|
230
|
-
return true;
|
|
231
|
-
// Evaluate the decision strategy
|
|
232
|
-
const result = runtime.evaluateSync(q.triggerWhen);
|
|
233
|
-
return result.value;
|
|
234
|
-
}), [config.actions, runtime, renderTick]);
|
|
235
|
-
// NOTE: faq:question_revealed is now published by useNotifyWatcher in
|
|
236
|
-
// ShadowCanvasOverlay (always mounted), so it fires even with drawer closed.
|
|
237
|
-
// Apply priority ordering
|
|
238
|
-
const orderedQuestions = useMemo(() => {
|
|
239
|
-
if (config.ordering === 'priority') {
|
|
240
|
-
return [...visibleQuestions].sort((a, b) => (b.config.priority ?? 0) - (a.config.priority ?? 0));
|
|
241
|
-
}
|
|
242
|
-
// 'static' or undefined — preserve config order
|
|
243
|
-
return visibleQuestions;
|
|
244
|
-
}, [visibleQuestions, config.ordering]);
|
|
245
|
-
// Apply search filter
|
|
246
|
-
const filteredQuestions = useMemo(() => {
|
|
247
|
-
if (!config.searchable || !searchQuery.trim()) {
|
|
248
|
-
return orderedQuestions;
|
|
249
|
-
}
|
|
250
|
-
const query = searchQuery.toLowerCase();
|
|
251
|
-
return orderedQuestions.filter((q) => q.config.question.toLowerCase().includes(query) ||
|
|
252
|
-
getAnswerText(q.config.answer).toLowerCase().includes(query) ||
|
|
253
|
-
q.config.category?.toLowerCase().includes(query));
|
|
254
|
-
}, [orderedQuestions, searchQuery, config.searchable]);
|
|
255
|
-
// Group by category
|
|
256
|
-
const categoryGroups = useMemo(() => {
|
|
257
|
-
const groups = new Map();
|
|
258
|
-
for (const q of filteredQuestions) {
|
|
259
|
-
const cat = q.config.category;
|
|
260
|
-
if (!groups.has(cat)) {
|
|
261
|
-
groups.set(cat, []);
|
|
262
|
-
}
|
|
263
|
-
groups.get(cat).push(q);
|
|
264
|
-
}
|
|
265
|
-
return groups;
|
|
266
|
-
}, [filteredQuestions]);
|
|
267
|
-
// Check if any items have categories
|
|
268
|
-
const hasCategories = useMemo(() => filteredQuestions.some((q) => q.config.category), [filteredQuestions]);
|
|
269
|
-
// Resolve theme (auto -> detect system preference)
|
|
270
|
-
const resolvedTheme = useMemo(() => {
|
|
271
|
-
if (config.theme && config.theme !== 'auto')
|
|
272
|
-
return config.theme;
|
|
273
|
-
// Check system preference (SSR-safe)
|
|
274
|
-
if (typeof window !== 'undefined') {
|
|
275
|
-
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
276
|
-
}
|
|
277
|
-
return 'light';
|
|
278
|
-
}, [config.theme]);
|
|
279
|
-
// Handle question toggle
|
|
280
|
-
const handleToggle = useCallback((id) => {
|
|
281
|
-
setExpandedIds((prev) => {
|
|
282
|
-
const next = new Set(prev);
|
|
283
|
-
if (config.expandBehavior === 'single') {
|
|
284
|
-
// Single mode: collapse all others
|
|
285
|
-
if (prev.has(id)) {
|
|
286
|
-
return new Set();
|
|
287
|
-
}
|
|
288
|
-
return new Set([id]);
|
|
289
|
-
}
|
|
290
|
-
// Multiple mode: toggle this one
|
|
291
|
-
if (prev.has(id)) {
|
|
292
|
-
next.delete(id);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
next.add(id);
|
|
296
|
-
}
|
|
297
|
-
return next;
|
|
298
|
-
});
|
|
299
|
-
// Publish toggle event for analytics
|
|
300
|
-
runtime.events.publish('faq:toggled', {
|
|
301
|
-
instanceId,
|
|
302
|
-
questionId: id,
|
|
303
|
-
expanded: !expandedIds.has(id),
|
|
304
|
-
timestamp: Date.now(),
|
|
305
|
-
});
|
|
306
|
-
}, [config.expandBehavior, runtime.events, instanceId, expandedIds]);
|
|
307
|
-
// Handle feedback
|
|
308
|
-
const handleFeedback = useCallback((itemId, question, value) => {
|
|
309
|
-
setFeedbackState((prev) => {
|
|
310
|
-
const next = new Map(prev);
|
|
311
|
-
next.set(itemId, value);
|
|
312
|
-
return next;
|
|
313
|
-
});
|
|
314
|
-
runtime.events.publish('faq:feedback', {
|
|
315
|
-
itemId,
|
|
316
|
-
question,
|
|
317
|
-
value,
|
|
318
|
-
});
|
|
319
|
-
}, [runtime.events]);
|
|
320
|
-
// Compute styles
|
|
321
|
-
const containerStyle = {
|
|
322
|
-
...baseStyles.container,
|
|
323
|
-
...themeStyles[resolvedTheme].container,
|
|
324
|
-
};
|
|
325
|
-
const searchInputStyle = {
|
|
326
|
-
...baseStyles.searchInput,
|
|
327
|
-
...themeStyles[resolvedTheme].searchInput,
|
|
328
|
-
};
|
|
329
|
-
const emptyStateStyle = {
|
|
330
|
-
...baseStyles.emptyState,
|
|
331
|
-
...themeStyles[resolvedTheme].emptyState,
|
|
332
|
-
};
|
|
333
|
-
const categoryHeaderStyle = {
|
|
334
|
-
...baseStyles.categoryHeader,
|
|
335
|
-
...themeStyles[resolvedTheme].categoryHeader,
|
|
336
|
-
};
|
|
337
|
-
// Render a list of FAQ items
|
|
338
|
-
const renderItems = (items) => items.map((q, index) => (_jsx(FAQItem, { item: q, isExpanded: expandedIds.has(q.config.id), isHighlighted: highlightId === q.config.id, isLast: index === items.length - 1, onToggle: () => handleToggle(q.config.id), theme: resolvedTheme, feedbackConfig: feedbackConfig, feedbackValue: feedbackState.get(q.config.id), onFeedback: handleFeedback }, q.config.id)));
|
|
339
|
-
// Empty state (no visible questions at all)
|
|
340
|
-
if (visibleQuestions.length === 0) {
|
|
341
|
-
return (_jsx("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: _jsx("div", { style: emptyStateStyle, children: "You're all set for now! We'll surface answers here when they're relevant to what you're doing." }) }));
|
|
342
|
-
}
|
|
343
|
-
return (_jsxs("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: [config.searchable && (_jsxs("div", { style: baseStyles.searchWrapper, children: [_jsx("style", { children: `[data-adaptive-id="${instanceId}"] input::placeholder { color: var(--sc-content-search-color, inherit); opacity: 0.7; }` }), _jsx("input", { type: "text", placeholder: "Search questions...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: searchInputStyle })] })), _jsx("div", { style: baseStyles.accordion, children: hasCategories
|
|
344
|
-
? Array.from(categoryGroups.entries()).map(([category, items]) => (_jsxs(React.Fragment, { children: [category && (_jsx("div", { style: categoryHeaderStyle, "data-category-header": category, children: category })), renderItems(items)] }, category ?? '__ungrouped')))
|
|
345
|
-
: renderItems(filteredQuestions) }), config.searchable && filteredQuestions.length === 0 && searchQuery && (_jsxs("div", { style: { ...baseStyles.noResults, ...themeStyles[resolvedTheme].emptyState }, children: ["No questions found matching \"", searchQuery, "\""] }))] }));
|
|
346
|
-
}
|
|
347
|
-
// ============================================================================
|
|
348
|
-
// Mountable Widget Interface
|
|
349
|
-
// ============================================================================
|
|
350
|
-
/**
|
|
351
|
-
* Mountable widget interface for the runtime's WidgetRegistry.
|
|
352
|
-
*/
|
|
353
|
-
export const FAQMountableWidget = {
|
|
354
|
-
mount(container, config) {
|
|
355
|
-
const { runtime, instanceId = 'faq-widget', ...faqConfig } = config || {
|
|
356
|
-
expandBehavior: 'single',
|
|
357
|
-
searchable: false,
|
|
358
|
-
theme: 'auto',
|
|
359
|
-
actions: [],
|
|
360
|
-
};
|
|
361
|
-
// React rendering when runtime + ReactDOM are available
|
|
362
|
-
if (runtime && typeof createRoot === 'function') {
|
|
363
|
-
const root = createRoot(container);
|
|
364
|
-
root.render(React.createElement(FAQWidget, {
|
|
365
|
-
config: faqConfig,
|
|
366
|
-
runtime: runtime,
|
|
367
|
-
instanceId,
|
|
368
|
-
}));
|
|
369
|
-
return () => {
|
|
370
|
-
root.unmount();
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
},
|
|
374
|
-
};
|
|
375
|
-
export default FAQWidget;
|