@feedvalue/react 0.1.9 → 0.1.10

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/index.cjs CHANGED
@@ -1,10 +1,14 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ var React2 = require('react');
4
4
  var core = require('@feedvalue/core');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
- var FeedValueContext = react.createContext(null);
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
10
+
11
+ var FeedValueContext = React2.createContext(null);
8
12
  var getServerSnapshot = () => ({
9
13
  isReady: false,
10
14
  isOpen: false,
@@ -24,10 +28,10 @@ function FeedValueProvider({
24
28
  onSubmit,
25
29
  onError
26
30
  }) {
27
- const instanceRef = react.useRef(null);
28
- const callbacksRef = react.useRef({ onReady, onOpen, onClose, onSubmit, onError });
31
+ const instanceRef = React2.useRef(null);
32
+ const callbacksRef = React2.useRef({ onReady, onOpen, onClose, onSubmit, onError });
29
33
  callbacksRef.current = { onReady, onOpen, onClose, onSubmit, onError };
30
- react.useEffect(() => {
34
+ React2.useEffect(() => {
31
35
  if (typeof window === "undefined") return;
32
36
  const instance = core.FeedValue.init({
33
37
  widgetId,
@@ -56,7 +60,7 @@ function FeedValueProvider({
56
60
  instanceRef.current = null;
57
61
  };
58
62
  }, [widgetId, apiBaseUrl, headless]);
59
- const state = react.useSyncExternalStore(
63
+ const state = React2.useSyncExternalStore(
60
64
  // Subscribe function
61
65
  (callback) => {
62
66
  const instance = instanceRef.current;
@@ -69,25 +73,25 @@ function FeedValueProvider({
69
73
  // getServerSnapshot - SSR
70
74
  getServerSnapshot
71
75
  );
72
- const open = react.useCallback(() => instanceRef.current?.open(), []);
73
- const close = react.useCallback(() => instanceRef.current?.close(), []);
74
- const toggle = react.useCallback(() => instanceRef.current?.toggle(), []);
75
- const show = react.useCallback(() => instanceRef.current?.show(), []);
76
- const hide = react.useCallback(() => instanceRef.current?.hide(), []);
77
- const submit = react.useCallback(
76
+ const open = React2.useCallback(() => instanceRef.current?.open(), []);
77
+ const close = React2.useCallback(() => instanceRef.current?.close(), []);
78
+ const toggle = React2.useCallback(() => instanceRef.current?.toggle(), []);
79
+ const show = React2.useCallback(() => instanceRef.current?.show(), []);
80
+ const hide = React2.useCallback(() => instanceRef.current?.hide(), []);
81
+ const submit = React2.useCallback(
78
82
  (feedback) => instanceRef.current?.submit(feedback) ?? Promise.reject(new Error("Not initialized")),
79
83
  []
80
84
  );
81
- const identify = react.useCallback(
85
+ const identify = React2.useCallback(
82
86
  (userId, traits) => instanceRef.current?.identify(userId, traits),
83
87
  []
84
88
  );
85
- const setData = react.useCallback(
89
+ const setData = React2.useCallback(
86
90
  (data) => instanceRef.current?.setData(data),
87
91
  []
88
92
  );
89
- const reset = react.useCallback(() => instanceRef.current?.reset(), []);
90
- const value = react.useMemo(
93
+ const reset = React2.useCallback(() => instanceRef.current?.reset(), []);
94
+ const value = React2.useMemo(
91
95
  () => ({
92
96
  instance: instanceRef.current,
93
97
  isReady: state.isReady,
@@ -111,7 +115,7 @@ function FeedValueProvider({
111
115
  return /* @__PURE__ */ jsxRuntime.jsx(FeedValueContext.Provider, { value, children });
112
116
  }
113
117
  function useFeedValue() {
114
- const context = react.useContext(FeedValueContext);
118
+ const context = React2.useContext(FeedValueContext);
115
119
  if (!context) {
116
120
  throw new Error(
117
121
  'useFeedValue must be used within a FeedValueProvider. Make sure to wrap your app with <FeedValueProvider widgetId="...">.'
@@ -120,15 +124,315 @@ function useFeedValue() {
120
124
  return context;
121
125
  }
122
126
  function useFeedValueOptional() {
123
- return react.useContext(FeedValueContext);
127
+ return React2.useContext(FeedValueContext);
128
+ }
129
+ function useReaction() {
130
+ const { instance, isReady } = useFeedValue();
131
+ const [showFollowUp, setShowFollowUp] = React2.useState(null);
132
+ const [submitted, setSubmitted] = React2.useState(null);
133
+ const [isSubmitting, setIsSubmitting] = React2.useState(false);
134
+ const [error, setError] = React2.useState(null);
135
+ const options = React2.useMemo(() => {
136
+ if (!instance || !isReady) return null;
137
+ return instance.getReactionOptions();
138
+ }, [instance, isReady]);
139
+ const reactionConfig = React2.useMemo(() => {
140
+ if (!instance || !isReady) return null;
141
+ return instance._widgetConfig?.config ?? null;
142
+ }, [instance, isReady]);
143
+ const showLabels = reactionConfig?.showLabels ?? true;
144
+ const buttonSize = reactionConfig?.buttonSize ?? "md";
145
+ const followUpTrigger = reactionConfig?.followUpTrigger ?? "negative";
146
+ const template = reactionConfig?.template;
147
+ const isReactionWidget = React2.useMemo(() => {
148
+ return instance?.isReaction() ?? false;
149
+ }, [instance]);
150
+ const shouldShowFollowUp = React2.useCallback(
151
+ (optionValue) => {
152
+ if (followUpTrigger === "none") return false;
153
+ if (followUpTrigger === "all") return true;
154
+ if (template && core.NEGATIVE_OPTIONS_MAP[template]) {
155
+ return core.NEGATIVE_OPTIONS_MAP[template].includes(optionValue);
156
+ }
157
+ const option = options?.find((o) => o.value === optionValue);
158
+ return option?.showFollowUp ?? false;
159
+ },
160
+ [followUpTrigger, template, options]
161
+ );
162
+ const react = React2.useCallback(
163
+ async (value, followUp) => {
164
+ if (!instance) {
165
+ throw new Error("FeedValue not initialized");
166
+ }
167
+ setIsSubmitting(true);
168
+ setError(null);
169
+ try {
170
+ await instance.react(value, followUp ? { followUp } : void 0);
171
+ setSubmitted(value);
172
+ setShowFollowUp(null);
173
+ } catch (err) {
174
+ const error2 = err instanceof Error ? err : new Error(String(err));
175
+ setError(error2);
176
+ throw error2;
177
+ } finally {
178
+ setIsSubmitting(false);
179
+ }
180
+ },
181
+ [instance]
182
+ );
183
+ const clearSubmitted = React2.useCallback(() => {
184
+ setSubmitted(null);
185
+ setError(null);
186
+ }, []);
187
+ return {
188
+ options,
189
+ isSubmitting,
190
+ submitted,
191
+ error,
192
+ showFollowUp,
193
+ setShowFollowUp,
194
+ react,
195
+ clearSubmitted,
196
+ isReactionWidget,
197
+ isReady,
198
+ showLabels,
199
+ buttonSize,
200
+ followUpTrigger,
201
+ shouldShowFollowUp
202
+ };
124
203
  }
125
204
  function FeedValueWidget(props) {
126
205
  return /* @__PURE__ */ jsxRuntime.jsx(FeedValueProvider, { ...props, children: null });
127
206
  }
207
+ var sizeStyles = {
208
+ sm: {
209
+ button: { padding: "6px 12px", fontSize: "12px", gap: "4px" },
210
+ icon: { fontSize: "16px" },
211
+ label: { fontSize: "12px" }
212
+ },
213
+ md: {
214
+ button: { padding: "8px 16px", fontSize: "14px", gap: "6px" },
215
+ icon: { fontSize: "18px" },
216
+ label: { fontSize: "14px" }
217
+ },
218
+ lg: {
219
+ button: { padding: "12px 20px", fontSize: "16px", gap: "8px" },
220
+ icon: { fontSize: "24px" },
221
+ label: { fontSize: "16px" }
222
+ }
223
+ };
224
+ var defaultStyles = {
225
+ container: {
226
+ display: "flex",
227
+ alignItems: "center",
228
+ gap: "8px",
229
+ flexWrap: "wrap"
230
+ },
231
+ button: {
232
+ display: "inline-flex",
233
+ alignItems: "center",
234
+ border: "1px solid #e0e0e0",
235
+ borderRadius: "20px",
236
+ background: "#fff",
237
+ cursor: "pointer",
238
+ transition: "all 0.15s ease"
239
+ },
240
+ buttonHover: {
241
+ borderColor: "#6366f1",
242
+ background: "#f5f3ff"
243
+ },
244
+ buttonDisabled: {
245
+ opacity: 0.6,
246
+ cursor: "not-allowed"
247
+ },
248
+ form: {
249
+ display: "flex",
250
+ flexDirection: "column",
251
+ gap: "8px",
252
+ marginTop: "8px",
253
+ width: "100%",
254
+ maxWidth: "400px"
255
+ },
256
+ input: {
257
+ padding: "8px 12px",
258
+ border: "1px solid #e0e0e0",
259
+ borderRadius: "8px",
260
+ fontSize: "14px",
261
+ resize: "none"
262
+ },
263
+ submitButton: {
264
+ padding: "8px 16px",
265
+ border: "none",
266
+ borderRadius: "8px",
267
+ background: "#6366f1",
268
+ color: "#fff",
269
+ cursor: "pointer",
270
+ fontSize: "14px",
271
+ fontWeight: 500,
272
+ alignSelf: "flex-start"
273
+ },
274
+ thankYou: {
275
+ color: "#059669",
276
+ fontSize: "14px",
277
+ fontWeight: 500
278
+ },
279
+ error: {
280
+ color: "#dc2626",
281
+ fontSize: "14px"
282
+ }
283
+ };
284
+ function ReactionButtonsInner({
285
+ className,
286
+ buttonClassName,
287
+ formClassName,
288
+ thankYouClassName,
289
+ onReact,
290
+ onError,
291
+ renderButton,
292
+ renderThankYou,
293
+ followUpMode = "inline",
294
+ hideAfterSubmit = false
295
+ }) {
296
+ const {
297
+ options,
298
+ react,
299
+ isSubmitting,
300
+ submitted,
301
+ error,
302
+ showFollowUp,
303
+ setShowFollowUp,
304
+ isReactionWidget,
305
+ isReady,
306
+ showLabels,
307
+ buttonSize,
308
+ shouldShowFollowUp
309
+ } = useReaction();
310
+ const [followUpText, setFollowUpText] = React2.useState("");
311
+ const [hoveredButton, setHoveredButton] = React2.useState(null);
312
+ const handleClick = React2.useCallback(
313
+ (option) => {
314
+ if (shouldShowFollowUp(option.value) && followUpMode === "inline") {
315
+ setShowFollowUp(option.value);
316
+ } else {
317
+ react(option.value).then(() => onReact?.(option.value)).catch((err) => onError?.(err));
318
+ }
319
+ },
320
+ [react, setShowFollowUp, followUpMode, onReact, onError, shouldShowFollowUp]
321
+ );
322
+ const handleFollowUpSubmit = React2.useCallback(
323
+ (e) => {
324
+ e.preventDefault();
325
+ if (!showFollowUp) return;
326
+ react(showFollowUp, followUpText.trim() || void 0).then(() => {
327
+ onReact?.(showFollowUp, followUpText.trim() || void 0);
328
+ setFollowUpText("");
329
+ }).catch((err) => onError?.(err));
330
+ },
331
+ [react, showFollowUp, followUpText, onReact, onError]
332
+ );
333
+ const handleCancelFollowUp = React2.useCallback(() => {
334
+ setShowFollowUp(null);
335
+ setFollowUpText("");
336
+ }, [setShowFollowUp]);
337
+ if (!isReady || !isReactionWidget || !options) {
338
+ return null;
339
+ }
340
+ if (submitted && hideAfterSubmit) {
341
+ return null;
342
+ }
343
+ if (submitted) {
344
+ if (renderThankYou) {
345
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderThankYou(submitted) });
346
+ }
347
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: thankYouClassName, style: thankYouClassName ? void 0 : defaultStyles.thankYou, children: "Thanks for your feedback!" });
348
+ }
349
+ const followUpOption = showFollowUp ? options.find((o) => o.value === showFollowUp) : null;
350
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: className ? void 0 : defaultStyles.container, children: [
351
+ !showFollowUp && options.map((option) => {
352
+ if (renderButton) {
353
+ return /* @__PURE__ */ jsxRuntime.jsx(React2__default.default.Fragment, { children: renderButton(option, () => handleClick(option), isSubmitting) }, option.value);
354
+ }
355
+ const isHovered = hoveredButton === option.value;
356
+ const sizeStyle = sizeStyles[buttonSize] || sizeStyles.md;
357
+ const buttonStyle = {
358
+ ...defaultStyles.button,
359
+ ...sizeStyle.button,
360
+ ...isHovered ? defaultStyles.buttonHover : {},
361
+ ...isSubmitting ? defaultStyles.buttonDisabled : {}
362
+ };
363
+ return /* @__PURE__ */ jsxRuntime.jsxs(
364
+ "button",
365
+ {
366
+ type: "button",
367
+ className: buttonClassName,
368
+ style: buttonClassName ? void 0 : buttonStyle,
369
+ onClick: () => handleClick(option),
370
+ onMouseEnter: () => setHoveredButton(option.value),
371
+ onMouseLeave: () => setHoveredButton(null),
372
+ disabled: isSubmitting,
373
+ "aria-label": option.label,
374
+ children: [
375
+ /* @__PURE__ */ jsxRuntime.jsx("span", { role: "img", "aria-hidden": "true", style: sizeStyle.icon, children: option.icon }),
376
+ showLabels && /* @__PURE__ */ jsxRuntime.jsx("span", { style: sizeStyle.label, children: option.label })
377
+ ]
378
+ },
379
+ option.value
380
+ );
381
+ }),
382
+ showFollowUp && followUpOption && /* @__PURE__ */ jsxRuntime.jsxs(
383
+ "form",
384
+ {
385
+ className: formClassName,
386
+ style: formClassName ? void 0 : defaultStyles.form,
387
+ onSubmit: handleFollowUpSubmit,
388
+ children: [
389
+ /* @__PURE__ */ jsxRuntime.jsx(
390
+ "textarea",
391
+ {
392
+ value: followUpText,
393
+ onChange: (e) => setFollowUpText(e.target.value),
394
+ placeholder: followUpOption.followUpPlaceholder ?? "Tell us more (optional)",
395
+ rows: 3,
396
+ style: defaultStyles.input,
397
+ disabled: isSubmitting
398
+ }
399
+ ),
400
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
401
+ /* @__PURE__ */ jsxRuntime.jsx(
402
+ "button",
403
+ {
404
+ type: "submit",
405
+ style: defaultStyles.submitButton,
406
+ disabled: isSubmitting,
407
+ children: isSubmitting ? "Sending..." : "Send"
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsxRuntime.jsx(
411
+ "button",
412
+ {
413
+ type: "button",
414
+ onClick: handleCancelFollowUp,
415
+ style: { ...defaultStyles.button, padding: "8px 16px" },
416
+ disabled: isSubmitting,
417
+ children: "Cancel"
418
+ }
419
+ )
420
+ ] })
421
+ ]
422
+ }
423
+ ),
424
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.error, children: error.message })
425
+ ] });
426
+ }
427
+ function ReactionButtons(props) {
428
+ return /* @__PURE__ */ jsxRuntime.jsx(ReactionButtonsInner, { ...props });
429
+ }
128
430
 
129
431
  exports.FeedValueProvider = FeedValueProvider;
130
432
  exports.FeedValueWidget = FeedValueWidget;
433
+ exports.ReactionButtons = ReactionButtons;
131
434
  exports.useFeedValue = useFeedValue;
132
435
  exports.useFeedValueOptional = useFeedValueOptional;
436
+ exports.useReaction = useReaction;
133
437
  //# sourceMappingURL=index.cjs.map
134
438
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.tsx","../src/components.tsx"],"names":["createContext","useRef","useEffect","FeedValue","useSyncExternalStore","useCallback","useMemo","jsx","useContext"],"mappings":"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","file":"index.cjs","sourcesContent":["'use client';\n\n/**\n * @feedvalue/react - Provider\n *\n * FeedValueProvider component for React applications.\n * Uses useSyncExternalStore for React 18+ concurrent mode compatibility.\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useSyncExternalStore,\n useMemo,\n type ReactNode,\n} from 'react';\nimport {\n FeedValue,\n type FeedValueConfig,\n type FeedValueState,\n type FeedbackData,\n type UserTraits,\n} from '@feedvalue/core';\n\n/**\n * Context value provided by FeedValueProvider\n */\nexport interface FeedValueContextValue {\n /** FeedValue instance (for advanced usage) */\n instance: FeedValue | null;\n /** Widget is ready (config loaded) */\n isReady: boolean;\n /** Modal is open */\n isOpen: boolean;\n /** Widget is visible */\n isVisible: boolean;\n /** Current error (if any) */\n error: Error | null;\n /** Submission in progress */\n isSubmitting: boolean;\n /** Running in headless mode (no default UI rendered) */\n isHeadless: boolean;\n /** Open the feedback modal */\n open: () => void;\n /** Close the feedback modal */\n close: () => void;\n /** Toggle the feedback modal */\n toggle: () => void;\n /** Show the trigger button */\n show: () => void;\n /** Hide the trigger button */\n hide: () => void;\n /** Submit feedback programmatically */\n submit: (feedback: Partial<FeedbackData>) => Promise<void>;\n /** Identify user */\n identify: (userId: string, traits?: UserTraits) => void;\n /** Set user data */\n setData: (data: Record<string, string>) => void;\n /** Reset user data */\n reset: () => void;\n}\n\n/**\n * FeedValue context (internal)\n */\nconst FeedValueContext = createContext<FeedValueContextValue | null>(null);\n\n/**\n * Props for FeedValueProvider\n */\nexport interface FeedValueProviderProps {\n /** Widget ID from FeedValue dashboard */\n widgetId: string;\n /** API base URL (for self-hosted) */\n apiBaseUrl?: string;\n /** Configuration overrides */\n config?: Partial<FeedValueConfig>;\n /**\n * Headless mode - disables all DOM rendering.\n * Use this when you want full control over the UI.\n * The SDK will still fetch config and provide all API methods\n * but won't render any trigger button or modal.\n *\n * @default false\n */\n headless?: boolean;\n /** Children */\n children: ReactNode;\n /** Callback when widget is ready */\n onReady?: () => void;\n /** Callback when modal opens */\n onOpen?: () => void;\n /** Callback when modal closes */\n onClose?: () => void;\n /** Callback when feedback is submitted */\n onSubmit?: (feedback: FeedbackData) => void;\n /** Callback when an error occurs */\n onError?: (error: Error) => void;\n}\n\n/**\n * Server snapshot for SSR - always returns initial state\n * This prevents hydration mismatches\n */\nconst getServerSnapshot = (): FeedValueState => ({\n isReady: false,\n isOpen: false,\n isVisible: false,\n error: null,\n isSubmitting: false,\n});\n\n/**\n * FeedValueProvider component\n *\n * Provides FeedValue context to child components.\n *\n * @example\n * ```tsx\n * // app/layout.tsx (Next.js App Router)\n * import { FeedValueProvider } from '@feedvalue/react';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * <FeedValueProvider\n * widgetId=\"your-widget-id\"\n * onSubmit={(feedback) => console.log('Submitted:', feedback)}\n * >\n * {children}\n * </FeedValueProvider>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport function FeedValueProvider({\n widgetId,\n apiBaseUrl,\n config,\n headless,\n children,\n onReady,\n onOpen,\n onClose,\n onSubmit,\n onError,\n}: FeedValueProviderProps): React.ReactElement {\n const instanceRef = useRef<FeedValue | null>(null);\n const callbacksRef = useRef({ onReady, onOpen, onClose, onSubmit, onError });\n\n // Keep callbacks ref updated\n callbacksRef.current = { onReady, onOpen, onClose, onSubmit, onError };\n\n // Initialize on mount\n useEffect(() => {\n // Only initialize on client side\n if (typeof window === 'undefined') return;\n\n const instance = FeedValue.init({\n widgetId,\n apiBaseUrl,\n config,\n headless,\n });\n instanceRef.current = instance;\n\n // Subscribe to events\n const handleReady = () => callbacksRef.current.onReady?.();\n const handleOpen = () => callbacksRef.current.onOpen?.();\n const handleClose = () => callbacksRef.current.onClose?.();\n const handleSubmit = (feedback: FeedbackData) => callbacksRef.current.onSubmit?.(feedback);\n const handleError = (error: Error) => callbacksRef.current.onError?.(error);\n\n instance.on('ready', handleReady);\n instance.on('open', handleOpen);\n instance.on('close', handleClose);\n instance.on('submit', handleSubmit);\n instance.on('error', handleError);\n\n return () => {\n instance.off('ready', handleReady);\n instance.off('open', handleOpen);\n instance.off('close', handleClose);\n instance.off('submit', handleSubmit);\n instance.off('error', handleError);\n instance.destroy();\n instanceRef.current = null;\n };\n }, [widgetId, apiBaseUrl, headless]); // Re-init if widgetId, apiBaseUrl, or headless changes\n\n // Use useSyncExternalStore for concurrent mode compatibility\n const state = useSyncExternalStore(\n // Subscribe function\n (callback) => {\n const instance = instanceRef.current;\n if (!instance) return () => {};\n return instance.subscribe(callback);\n },\n // getSnapshot - client\n () => instanceRef.current?.getSnapshot() ?? getServerSnapshot(),\n // getServerSnapshot - SSR\n getServerSnapshot\n );\n\n // Stable callback references to prevent recreation on every state change\n const open = useCallback(() => instanceRef.current?.open(), []);\n const close = useCallback(() => instanceRef.current?.close(), []);\n const toggle = useCallback(() => instanceRef.current?.toggle(), []);\n const show = useCallback(() => instanceRef.current?.show(), []);\n const hide = useCallback(() => instanceRef.current?.hide(), []);\n const submit = useCallback(\n (feedback: Partial<FeedbackData>) =>\n instanceRef.current?.submit(feedback) ?? Promise.reject(new Error('Not initialized')),\n []\n );\n const identify = useCallback(\n (userId: string, traits?: UserTraits) => instanceRef.current?.identify(userId, traits),\n []\n );\n const setData = useCallback(\n (data: Record<string, string>) => instanceRef.current?.setData(data),\n []\n );\n const reset = useCallback(() => instanceRef.current?.reset(), []);\n\n // Memoize context value to prevent unnecessary re-renders\n const value = useMemo<FeedValueContextValue>(\n () => ({\n instance: instanceRef.current,\n isReady: state.isReady,\n isOpen: state.isOpen,\n isVisible: state.isVisible,\n error: state.error,\n isSubmitting: state.isSubmitting,\n isHeadless: headless ?? false,\n open,\n close,\n toggle,\n show,\n hide,\n submit,\n identify,\n setData,\n reset,\n }),\n [state, headless, open, close, toggle, show, hide, submit, identify, setData, reset]\n );\n\n return (\n <FeedValueContext.Provider value={value}>\n {children}\n </FeedValueContext.Provider>\n );\n}\n\n/**\n * Hook to access FeedValue context\n *\n * Must be used within a FeedValueProvider.\n *\n * @example\n * ```tsx\n * 'use client';\n * import { useFeedValue } from '@feedvalue/react';\n *\n * export function FeedbackButton() {\n * const { open, isReady } = useFeedValue();\n *\n * return (\n * <button onClick={open} disabled={!isReady}>\n * Give Feedback\n * </button>\n * );\n * }\n * ```\n */\nexport function useFeedValue(): FeedValueContextValue {\n const context = useContext(FeedValueContext);\n\n if (!context) {\n throw new Error(\n 'useFeedValue must be used within a FeedValueProvider. ' +\n 'Make sure to wrap your app with <FeedValueProvider widgetId=\"...\">.'\n );\n }\n\n return context;\n}\n\n/**\n * Hook to check if inside FeedValueProvider\n * Returns null if outside provider, context value if inside\n */\nexport function useFeedValueOptional(): FeedValueContextValue | null {\n return useContext(FeedValueContext);\n}\n","'use client';\n\n/**\n * @feedvalue/react - Components\n *\n * Standalone React components for FeedValue.\n */\n\nimport React from 'react';\nimport { FeedValueProvider, type FeedValueProviderProps } from './provider';\n\n/**\n * Props for FeedValueWidget component\n */\nexport interface FeedValueWidgetProps extends Omit<FeedValueProviderProps, 'children'> {}\n\n/**\n * Standalone FeedValue widget component\n *\n * Use this when you don't need to access the FeedValue context elsewhere.\n * The widget renders itself via DOM injection - this component is a container.\n *\n * @example\n * ```tsx\n * // Simple usage - just drop in anywhere\n * import { FeedValueWidget } from '@feedvalue/react';\n *\n * export function App() {\n * return (\n * <div>\n * <h1>My App</h1>\n * <FeedValueWidget\n * widgetId=\"your-widget-id\"\n * onSubmit={(feedback) => console.log('Feedback:', feedback)}\n * />\n * </div>\n * );\n * }\n * ```\n */\nexport function FeedValueWidget(props: FeedValueWidgetProps): React.ReactElement {\n return (\n <FeedValueProvider {...props}>\n {/* Widget renders via DOM injection, no children needed */}\n {null}\n </FeedValueProvider>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/provider.tsx","../src/use-reaction.ts","../src/components.tsx"],"names":["createContext","useRef","useEffect","FeedValue","useSyncExternalStore","useCallback","useMemo","jsx","useContext","useState","NEGATIVE_OPTIONS_MAP","error","Fragment","React","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["'use client';\n\n/**\n * @feedvalue/react - Provider\n *\n * FeedValueProvider component for React applications.\n * Uses useSyncExternalStore for React 18+ concurrent mode compatibility.\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useSyncExternalStore,\n useMemo,\n type ReactNode,\n} from 'react';\nimport {\n FeedValue,\n type FeedValueConfig,\n type FeedValueState,\n type FeedbackData,\n type UserTraits,\n} from '@feedvalue/core';\n\n/**\n * Context value provided by FeedValueProvider\n */\nexport interface FeedValueContextValue {\n /** FeedValue instance (for advanced usage) */\n instance: FeedValue | null;\n /** Widget is ready (config loaded) */\n isReady: boolean;\n /** Modal is open */\n isOpen: boolean;\n /** Widget is visible */\n isVisible: boolean;\n /** Current error (if any) */\n error: Error | null;\n /** Submission in progress */\n isSubmitting: boolean;\n /** Running in headless mode (no default UI rendered) */\n isHeadless: boolean;\n /** Open the feedback modal */\n open: () => void;\n /** Close the feedback modal */\n close: () => void;\n /** Toggle the feedback modal */\n toggle: () => void;\n /** Show the trigger button */\n show: () => void;\n /** Hide the trigger button */\n hide: () => void;\n /** Submit feedback programmatically */\n submit: (feedback: Partial<FeedbackData>) => Promise<void>;\n /** Identify user */\n identify: (userId: string, traits?: UserTraits) => void;\n /** Set user data */\n setData: (data: Record<string, string>) => void;\n /** Reset user data */\n reset: () => void;\n}\n\n/**\n * FeedValue context (internal)\n */\nconst FeedValueContext = createContext<FeedValueContextValue | null>(null);\n\n/**\n * Props for FeedValueProvider\n */\nexport interface FeedValueProviderProps {\n /** Widget ID from FeedValue dashboard */\n widgetId: string;\n /** API base URL (for self-hosted) */\n apiBaseUrl?: string;\n /** Configuration overrides */\n config?: Partial<FeedValueConfig>;\n /**\n * Headless mode - disables all DOM rendering.\n * Use this when you want full control over the UI.\n * The SDK will still fetch config and provide all API methods\n * but won't render any trigger button or modal.\n *\n * @default false\n */\n headless?: boolean;\n /** Children */\n children: ReactNode;\n /** Callback when widget is ready */\n onReady?: () => void;\n /** Callback when modal opens */\n onOpen?: () => void;\n /** Callback when modal closes */\n onClose?: () => void;\n /** Callback when feedback is submitted */\n onSubmit?: (feedback: FeedbackData) => void;\n /** Callback when an error occurs */\n onError?: (error: Error) => void;\n}\n\n/**\n * Server snapshot for SSR - always returns initial state\n * This prevents hydration mismatches\n */\nconst getServerSnapshot = (): FeedValueState => ({\n isReady: false,\n isOpen: false,\n isVisible: false,\n error: null,\n isSubmitting: false,\n});\n\n/**\n * FeedValueProvider component\n *\n * Provides FeedValue context to child components.\n *\n * @example\n * ```tsx\n * // app/layout.tsx (Next.js App Router)\n * import { FeedValueProvider } from '@feedvalue/react';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * <FeedValueProvider\n * widgetId=\"your-widget-id\"\n * onSubmit={(feedback) => console.log('Submitted:', feedback)}\n * >\n * {children}\n * </FeedValueProvider>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport function FeedValueProvider({\n widgetId,\n apiBaseUrl,\n config,\n headless,\n children,\n onReady,\n onOpen,\n onClose,\n onSubmit,\n onError,\n}: FeedValueProviderProps): React.ReactElement {\n const instanceRef = useRef<FeedValue | null>(null);\n const callbacksRef = useRef({ onReady, onOpen, onClose, onSubmit, onError });\n\n // Keep callbacks ref updated\n callbacksRef.current = { onReady, onOpen, onClose, onSubmit, onError };\n\n // Initialize on mount\n useEffect(() => {\n // Only initialize on client side\n if (typeof window === 'undefined') return;\n\n const instance = FeedValue.init({\n widgetId,\n apiBaseUrl,\n config,\n headless,\n });\n instanceRef.current = instance;\n\n // Subscribe to events\n const handleReady = () => callbacksRef.current.onReady?.();\n const handleOpen = () => callbacksRef.current.onOpen?.();\n const handleClose = () => callbacksRef.current.onClose?.();\n const handleSubmit = (feedback: FeedbackData) => callbacksRef.current.onSubmit?.(feedback);\n const handleError = (error: Error) => callbacksRef.current.onError?.(error);\n\n instance.on('ready', handleReady);\n instance.on('open', handleOpen);\n instance.on('close', handleClose);\n instance.on('submit', handleSubmit);\n instance.on('error', handleError);\n\n return () => {\n instance.off('ready', handleReady);\n instance.off('open', handleOpen);\n instance.off('close', handleClose);\n instance.off('submit', handleSubmit);\n instance.off('error', handleError);\n instance.destroy();\n instanceRef.current = null;\n };\n }, [widgetId, apiBaseUrl, headless]); // Re-init if widgetId, apiBaseUrl, or headless changes\n\n // Use useSyncExternalStore for concurrent mode compatibility\n const state = useSyncExternalStore(\n // Subscribe function\n (callback) => {\n const instance = instanceRef.current;\n if (!instance) return () => {};\n return instance.subscribe(callback);\n },\n // getSnapshot - client\n () => instanceRef.current?.getSnapshot() ?? getServerSnapshot(),\n // getServerSnapshot - SSR\n getServerSnapshot\n );\n\n // Stable callback references to prevent recreation on every state change\n const open = useCallback(() => instanceRef.current?.open(), []);\n const close = useCallback(() => instanceRef.current?.close(), []);\n const toggle = useCallback(() => instanceRef.current?.toggle(), []);\n const show = useCallback(() => instanceRef.current?.show(), []);\n const hide = useCallback(() => instanceRef.current?.hide(), []);\n const submit = useCallback(\n (feedback: Partial<FeedbackData>) =>\n instanceRef.current?.submit(feedback) ?? Promise.reject(new Error('Not initialized')),\n []\n );\n const identify = useCallback(\n (userId: string, traits?: UserTraits) => instanceRef.current?.identify(userId, traits),\n []\n );\n const setData = useCallback(\n (data: Record<string, string>) => instanceRef.current?.setData(data),\n []\n );\n const reset = useCallback(() => instanceRef.current?.reset(), []);\n\n // Memoize context value to prevent unnecessary re-renders\n const value = useMemo<FeedValueContextValue>(\n () => ({\n instance: instanceRef.current,\n isReady: state.isReady,\n isOpen: state.isOpen,\n isVisible: state.isVisible,\n error: state.error,\n isSubmitting: state.isSubmitting,\n isHeadless: headless ?? false,\n open,\n close,\n toggle,\n show,\n hide,\n submit,\n identify,\n setData,\n reset,\n }),\n [state, headless, open, close, toggle, show, hide, submit, identify, setData, reset]\n );\n\n return (\n <FeedValueContext.Provider value={value}>\n {children}\n </FeedValueContext.Provider>\n );\n}\n\n/**\n * Hook to access FeedValue context\n *\n * Must be used within a FeedValueProvider.\n *\n * @example\n * ```tsx\n * 'use client';\n * import { useFeedValue } from '@feedvalue/react';\n *\n * export function FeedbackButton() {\n * const { open, isReady } = useFeedValue();\n *\n * return (\n * <button onClick={open} disabled={!isReady}>\n * Give Feedback\n * </button>\n * );\n * }\n * ```\n */\nexport function useFeedValue(): FeedValueContextValue {\n const context = useContext(FeedValueContext);\n\n if (!context) {\n throw new Error(\n 'useFeedValue must be used within a FeedValueProvider. ' +\n 'Make sure to wrap your app with <FeedValueProvider widgetId=\"...\">.'\n );\n }\n\n return context;\n}\n\n/**\n * Hook to check if inside FeedValueProvider\n * Returns null if outside provider, context value if inside\n */\nexport function useFeedValueOptional(): FeedValueContextValue | null {\n return useContext(FeedValueContext);\n}\n","'use client';\n\n/**\n * @feedvalue/react - useReaction Hook\n *\n * Hook for reaction widgets in React applications.\n * Provides a simple API for submitting reactions.\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport type { ReactionOption, ReactionState, ReactionConfig, ButtonSize, FollowUpTrigger } from '@feedvalue/core';\nimport { NEGATIVE_OPTIONS_MAP } from '@feedvalue/core';\nimport { useFeedValue } from './provider';\n\n/**\n * Return type for useReaction hook\n */\nexport interface UseReactionReturn extends ReactionState {\n /** Submit a reaction */\n react: (value: string, followUp?: string) => Promise<void>;\n /** Set which option is showing follow-up input */\n setShowFollowUp: (value: string | null) => void;\n /** Clear the submitted state to allow re-submission */\n clearSubmitted: () => void;\n /** Check if widget is a reaction type */\n isReactionWidget: boolean;\n /** Widget configuration is ready */\n isReady: boolean;\n /** Whether to show text labels next to icons */\n showLabels: boolean;\n /** Button size */\n buttonSize: ButtonSize;\n /** When to show follow-up input */\n followUpTrigger: FollowUpTrigger;\n /** Check if an option should show follow-up based on followUpTrigger */\n shouldShowFollowUp: (optionValue: string) => boolean;\n}\n\n/**\n * Hook for reaction widgets\n *\n * Provides reaction options, submission handling, and follow-up state management.\n *\n * @example\n * ```tsx\n * 'use client';\n * import { useReaction } from '@feedvalue/react';\n *\n * function ReactionButtons() {\n * const {\n * options,\n * react,\n * isSubmitting,\n * submitted,\n * error,\n * showFollowUp,\n * setShowFollowUp,\n * isReady,\n * } = useReaction();\n *\n * if (!isReady || !options) return null;\n *\n * if (submitted) {\n * return <div>Thanks for your feedback!</div>;\n * }\n *\n * return (\n * <div>\n * {options.map((option) => (\n * <button\n * key={option.value}\n * onClick={() => {\n * if (option.showFollowUp) {\n * setShowFollowUp(option.value);\n * } else {\n * react(option.value);\n * }\n * }}\n * disabled={isSubmitting}\n * >\n * {option.icon} {option.label}\n * </button>\n * ))}\n *\n * {showFollowUp && (\n * <form onSubmit={(e) => {\n * e.preventDefault();\n * const form = e.target as HTMLFormElement;\n * const input = form.elements.namedItem('followUp') as HTMLInputElement;\n * react(showFollowUp, input.value);\n * }}>\n * <input name=\"followUp\" placeholder=\"Tell us more...\" />\n * <button type=\"submit\">Send</button>\n * </form>\n * )}\n *\n * {error && <div>Error: {error.message}</div>}\n * </div>\n * );\n * }\n * ```\n */\nexport function useReaction(): UseReactionReturn {\n const { instance, isReady } = useFeedValue();\n\n // Local state for follow-up and submitted\n const [showFollowUp, setShowFollowUp] = useState<string | null>(null);\n const [submitted, setSubmitted] = useState<string | null>(null);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Get reaction options from instance\n const options = useMemo<ReactionOption[] | null>(() => {\n if (!instance || !isReady) return null;\n return instance.getReactionOptions();\n }, [instance, isReady]);\n\n // Get reaction config from instance\n const reactionConfig = useMemo<Partial<ReactionConfig> | null>(() => {\n if (!instance || !isReady) return null;\n // Access the widget config which includes reaction config\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (instance as any)._widgetConfig?.config ?? null;\n }, [instance, isReady]);\n\n // Extract config values with defaults\n const showLabels = reactionConfig?.showLabels ?? true;\n const buttonSize: ButtonSize = reactionConfig?.buttonSize ?? 'md';\n const followUpTrigger: FollowUpTrigger = reactionConfig?.followUpTrigger ?? 'negative';\n const template = reactionConfig?.template;\n\n // Check if this is a reaction widget\n const isReactionWidget = useMemo(() => {\n return instance?.isReaction() ?? false;\n }, [instance]);\n\n // Function to determine if follow-up should show for an option\n const shouldShowFollowUp = useCallback(\n (optionValue: string): boolean => {\n if (followUpTrigger === 'none') return false;\n if (followUpTrigger === 'all') return true;\n // followUpTrigger === 'negative'\n if (template && NEGATIVE_OPTIONS_MAP[template]) {\n return NEGATIVE_OPTIONS_MAP[template].includes(optionValue);\n }\n // For custom options, use the option's own showFollowUp setting\n const option = options?.find((o) => o.value === optionValue);\n return option?.showFollowUp ?? false;\n },\n [followUpTrigger, template, options]\n );\n\n // Submit reaction\n const react = useCallback(\n async (value: string, followUp?: string) => {\n if (!instance) {\n throw new Error('FeedValue not initialized');\n }\n\n setIsSubmitting(true);\n setError(null);\n\n try {\n await instance.react(value, followUp ? { followUp } : undefined);\n setSubmitted(value);\n setShowFollowUp(null);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n throw error;\n } finally {\n setIsSubmitting(false);\n }\n },\n [instance]\n );\n\n // Clear submitted state\n const clearSubmitted = useCallback(() => {\n setSubmitted(null);\n setError(null);\n }, []);\n\n return {\n options,\n isSubmitting,\n submitted,\n error,\n showFollowUp,\n setShowFollowUp,\n react,\n clearSubmitted,\n isReactionWidget,\n isReady,\n showLabels,\n buttonSize,\n followUpTrigger,\n shouldShowFollowUp,\n };\n}\n","'use client';\n\n/**\n * @feedvalue/react - Components\n *\n * Standalone React components for FeedValue.\n */\n\nimport React, { useCallback, useState, type FormEvent } from 'react';\nimport type { ReactionOption } from '@feedvalue/core';\nimport { FeedValueProvider, type FeedValueProviderProps } from './provider';\nimport { useReaction } from './use-reaction';\n\n/**\n * Props for FeedValueWidget component\n */\nexport interface FeedValueWidgetProps extends Omit<FeedValueProviderProps, 'children'> {}\n\n/**\n * Standalone FeedValue widget component\n *\n * Use this when you don't need to access the FeedValue context elsewhere.\n * The widget renders itself via DOM injection - this component is a container.\n *\n * @example\n * ```tsx\n * // Simple usage - just drop in anywhere\n * import { FeedValueWidget } from '@feedvalue/react';\n *\n * export function App() {\n * return (\n * <div>\n * <h1>My App</h1>\n * <FeedValueWidget\n * widgetId=\"your-widget-id\"\n * onSubmit={(feedback) => console.log('Feedback:', feedback)}\n * />\n * </div>\n * );\n * }\n * ```\n */\nexport function FeedValueWidget(props: FeedValueWidgetProps): React.ReactElement {\n return (\n <FeedValueProvider {...props}>\n {/* Widget renders via DOM injection, no children needed */}\n {null}\n </FeedValueProvider>\n );\n}\n\n/**\n * Props for ReactionButtons component\n */\nexport interface ReactionButtonsProps {\n /** Custom CSS class for the container */\n className?: string;\n /** Custom CSS class for buttons */\n buttonClassName?: string;\n /** Custom CSS class for the follow-up form */\n formClassName?: string;\n /** Custom CSS class for the thank you message */\n thankYouClassName?: string;\n /** Callback when a reaction is submitted */\n onReact?: (value: string, followUp?: string) => void;\n /** Callback when an error occurs */\n onError?: (error: Error) => void;\n /** Custom render function for buttons (for full control) */\n renderButton?: (option: ReactionOption, onClick: () => void, isDisabled: boolean) => React.ReactNode;\n /** Custom render function for thank you message */\n renderThankYou?: (value: string) => React.ReactNode;\n /** Whether to show follow-up inline (default) or in a modal */\n followUpMode?: 'inline' | 'none';\n /** Hide after submission (default: false) */\n hideAfterSubmit?: boolean;\n}\n\n/**\n * Button size style variants\n */\nconst sizeStyles = {\n sm: {\n button: { padding: '6px 12px', fontSize: '12px', gap: '4px' },\n icon: { fontSize: '16px' },\n label: { fontSize: '12px' },\n },\n md: {\n button: { padding: '8px 16px', fontSize: '14px', gap: '6px' },\n icon: { fontSize: '18px' },\n label: { fontSize: '14px' },\n },\n lg: {\n button: { padding: '12px 20px', fontSize: '16px', gap: '8px' },\n icon: { fontSize: '24px' },\n label: { fontSize: '16px' },\n },\n};\n\n/**\n * Default button styles (inline to avoid CSS dependencies)\n */\nconst defaultStyles = {\n container: {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n flexWrap: 'wrap' as const,\n },\n button: {\n display: 'inline-flex',\n alignItems: 'center',\n border: '1px solid #e0e0e0',\n borderRadius: '20px',\n background: '#fff',\n cursor: 'pointer',\n transition: 'all 0.15s ease',\n },\n buttonHover: {\n borderColor: '#6366f1',\n background: '#f5f3ff',\n },\n buttonDisabled: {\n opacity: 0.6,\n cursor: 'not-allowed',\n },\n form: {\n display: 'flex',\n flexDirection: 'column' as const,\n gap: '8px',\n marginTop: '8px',\n width: '100%',\n maxWidth: '400px',\n },\n input: {\n padding: '8px 12px',\n border: '1px solid #e0e0e0',\n borderRadius: '8px',\n fontSize: '14px',\n resize: 'none' as const,\n },\n submitButton: {\n padding: '8px 16px',\n border: 'none',\n borderRadius: '8px',\n background: '#6366f1',\n color: '#fff',\n cursor: 'pointer',\n fontSize: '14px',\n fontWeight: 500,\n alignSelf: 'flex-start' as const,\n },\n thankYou: {\n color: '#059669',\n fontSize: '14px',\n fontWeight: 500,\n },\n error: {\n color: '#dc2626',\n fontSize: '14px',\n },\n};\n\n/**\n * Internal ReactionButtons implementation (must be inside FeedValueProvider)\n */\nfunction ReactionButtonsInner({\n className,\n buttonClassName,\n formClassName,\n thankYouClassName,\n onReact,\n onError,\n renderButton,\n renderThankYou,\n followUpMode = 'inline',\n hideAfterSubmit = false,\n}: ReactionButtonsProps): React.ReactElement | null {\n const {\n options,\n react,\n isSubmitting,\n submitted,\n error,\n showFollowUp,\n setShowFollowUp,\n isReactionWidget,\n isReady,\n showLabels,\n buttonSize,\n shouldShowFollowUp,\n } = useReaction();\n\n const [followUpText, setFollowUpText] = useState('');\n const [hoveredButton, setHoveredButton] = useState<string | null>(null);\n\n // Handle button click\n const handleClick = useCallback(\n (option: ReactionOption) => {\n if (shouldShowFollowUp(option.value) && followUpMode === 'inline') {\n setShowFollowUp(option.value);\n } else {\n react(option.value)\n .then(() => onReact?.(option.value))\n .catch((err) => onError?.(err));\n }\n },\n [react, setShowFollowUp, followUpMode, onReact, onError, shouldShowFollowUp]\n );\n\n // Handle follow-up form submission\n const handleFollowUpSubmit = useCallback(\n (e: FormEvent) => {\n e.preventDefault();\n if (!showFollowUp) return;\n\n react(showFollowUp, followUpText.trim() || undefined)\n .then(() => {\n onReact?.(showFollowUp, followUpText.trim() || undefined);\n setFollowUpText('');\n })\n .catch((err) => onError?.(err));\n },\n [react, showFollowUp, followUpText, onReact, onError]\n );\n\n // Cancel follow-up\n const handleCancelFollowUp = useCallback(() => {\n setShowFollowUp(null);\n setFollowUpText('');\n }, [setShowFollowUp]);\n\n // Not ready or not a reaction widget\n if (!isReady || !isReactionWidget || !options) {\n return null;\n }\n\n // Already submitted and hide option enabled\n if (submitted && hideAfterSubmit) {\n return null;\n }\n\n // Show thank you message\n if (submitted) {\n if (renderThankYou) {\n return <>{renderThankYou(submitted)}</>;\n }\n\n return (\n <div className={thankYouClassName} style={thankYouClassName ? undefined : defaultStyles.thankYou}>\n Thanks for your feedback!\n </div>\n );\n }\n\n // Get the option that needs follow-up\n const followUpOption = showFollowUp ? options.find((o) => o.value === showFollowUp) : null;\n\n return (\n <div className={className} style={className ? undefined : defaultStyles.container}>\n {/* Reaction buttons */}\n {!showFollowUp &&\n options.map((option) => {\n if (renderButton) {\n return (\n <React.Fragment key={option.value}>\n {renderButton(option, () => handleClick(option), isSubmitting)}\n </React.Fragment>\n );\n }\n\n const isHovered = hoveredButton === option.value;\n const sizeStyle = sizeStyles[buttonSize] || sizeStyles.md;\n const buttonStyle = {\n ...defaultStyles.button,\n ...sizeStyle.button,\n ...(isHovered ? defaultStyles.buttonHover : {}),\n ...(isSubmitting ? defaultStyles.buttonDisabled : {}),\n };\n\n return (\n <button\n key={option.value}\n type=\"button\"\n className={buttonClassName}\n style={buttonClassName ? undefined : buttonStyle}\n onClick={() => handleClick(option)}\n onMouseEnter={() => setHoveredButton(option.value)}\n onMouseLeave={() => setHoveredButton(null)}\n disabled={isSubmitting}\n aria-label={option.label}\n >\n <span role=\"img\" aria-hidden=\"true\" style={sizeStyle.icon}>\n {option.icon}\n </span>\n {showLabels && <span style={sizeStyle.label}>{option.label}</span>}\n </button>\n );\n })}\n\n {/* Follow-up form */}\n {showFollowUp && followUpOption && (\n <form\n className={formClassName}\n style={formClassName ? undefined : defaultStyles.form}\n onSubmit={handleFollowUpSubmit}\n >\n <textarea\n value={followUpText}\n onChange={(e) => setFollowUpText(e.target.value)}\n placeholder={followUpOption.followUpPlaceholder ?? 'Tell us more (optional)'}\n rows={3}\n style={defaultStyles.input}\n disabled={isSubmitting}\n />\n <div style={{ display: 'flex', gap: '8px' }}>\n <button\n type=\"submit\"\n style={defaultStyles.submitButton}\n disabled={isSubmitting}\n >\n {isSubmitting ? 'Sending...' : 'Send'}\n </button>\n <button\n type=\"button\"\n onClick={handleCancelFollowUp}\n style={{ ...defaultStyles.button, padding: '8px 16px' }}\n disabled={isSubmitting}\n >\n Cancel\n </button>\n </div>\n </form>\n )}\n\n {/* Error message */}\n {error && (\n <div style={defaultStyles.error}>\n {error.message}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * ReactionButtons component\n *\n * Renders reaction buttons (thumbs up/down, helpful, emoji, etc.) for inline feedback.\n * Must be used within a FeedValueProvider with a reaction-type widget.\n *\n * @example\n * ```tsx\n * 'use client';\n * import { FeedValueProvider, ReactionButtons } from '@feedvalue/react';\n *\n * function ArticleFooter() {\n * return (\n * <FeedValueProvider widgetId=\"your-reaction-widget-id\" headless>\n * <div>\n * <h3>Was this helpful?</h3>\n * <ReactionButtons\n * onReact={(value) => console.log('Reacted:', value)}\n * />\n * </div>\n * </FeedValueProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With custom button rendering\n * <ReactionButtons\n * renderButton={(option, onClick, isDisabled) => (\n * <button\n * key={option.value}\n * onClick={onClick}\n * disabled={isDisabled}\n * className=\"my-custom-button\"\n * >\n * {option.icon} {option.label}\n * </button>\n * )}\n * />\n * ```\n */\nexport function ReactionButtons(props: ReactionButtonsProps): React.ReactElement {\n return <ReactionButtonsInner {...props} />;\n}\n"]}