@feedvalue/react 0.1.8 → 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 +322 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +160 -3
- package/dist/index.d.ts +160 -3
- package/dist/index.js +302 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { FeedValue, FeedbackData, UserTraits, FeedValueConfig } from '@feedvalue/core';
|
|
3
|
-
export { FeedValueConfig, FeedValueEvents, FeedValueState, FeedbackData, FeedbackMetadata, UserData, UserTraits } from '@feedvalue/core';
|
|
2
|
+
import { FeedValue, FeedbackData, UserTraits, FeedValueConfig, ReactionState, ButtonSize, FollowUpTrigger, ReactionOption } from '@feedvalue/core';
|
|
3
|
+
export { FeedValueConfig, FeedValueEvents, FeedValueState, FeedbackData, FeedbackMetadata, ReactionConfig, ReactionData, ReactionOption, ReactionState, UserData, UserTraits, WidgetType } from '@feedvalue/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @feedvalue/react - Provider
|
|
@@ -133,6 +133,95 @@ declare function useFeedValue(): FeedValueContextValue;
|
|
|
133
133
|
*/
|
|
134
134
|
declare function useFeedValueOptional(): FeedValueContextValue | null;
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Return type for useReaction hook
|
|
138
|
+
*/
|
|
139
|
+
interface UseReactionReturn extends ReactionState {
|
|
140
|
+
/** Submit a reaction */
|
|
141
|
+
react: (value: string, followUp?: string) => Promise<void>;
|
|
142
|
+
/** Set which option is showing follow-up input */
|
|
143
|
+
setShowFollowUp: (value: string | null) => void;
|
|
144
|
+
/** Clear the submitted state to allow re-submission */
|
|
145
|
+
clearSubmitted: () => void;
|
|
146
|
+
/** Check if widget is a reaction type */
|
|
147
|
+
isReactionWidget: boolean;
|
|
148
|
+
/** Widget configuration is ready */
|
|
149
|
+
isReady: boolean;
|
|
150
|
+
/** Whether to show text labels next to icons */
|
|
151
|
+
showLabels: boolean;
|
|
152
|
+
/** Button size */
|
|
153
|
+
buttonSize: ButtonSize;
|
|
154
|
+
/** When to show follow-up input */
|
|
155
|
+
followUpTrigger: FollowUpTrigger;
|
|
156
|
+
/** Check if an option should show follow-up based on followUpTrigger */
|
|
157
|
+
shouldShowFollowUp: (optionValue: string) => boolean;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Hook for reaction widgets
|
|
161
|
+
*
|
|
162
|
+
* Provides reaction options, submission handling, and follow-up state management.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```tsx
|
|
166
|
+
* 'use client';
|
|
167
|
+
* import { useReaction } from '@feedvalue/react';
|
|
168
|
+
*
|
|
169
|
+
* function ReactionButtons() {
|
|
170
|
+
* const {
|
|
171
|
+
* options,
|
|
172
|
+
* react,
|
|
173
|
+
* isSubmitting,
|
|
174
|
+
* submitted,
|
|
175
|
+
* error,
|
|
176
|
+
* showFollowUp,
|
|
177
|
+
* setShowFollowUp,
|
|
178
|
+
* isReady,
|
|
179
|
+
* } = useReaction();
|
|
180
|
+
*
|
|
181
|
+
* if (!isReady || !options) return null;
|
|
182
|
+
*
|
|
183
|
+
* if (submitted) {
|
|
184
|
+
* return <div>Thanks for your feedback!</div>;
|
|
185
|
+
* }
|
|
186
|
+
*
|
|
187
|
+
* return (
|
|
188
|
+
* <div>
|
|
189
|
+
* {options.map((option) => (
|
|
190
|
+
* <button
|
|
191
|
+
* key={option.value}
|
|
192
|
+
* onClick={() => {
|
|
193
|
+
* if (option.showFollowUp) {
|
|
194
|
+
* setShowFollowUp(option.value);
|
|
195
|
+
* } else {
|
|
196
|
+
* react(option.value);
|
|
197
|
+
* }
|
|
198
|
+
* }}
|
|
199
|
+
* disabled={isSubmitting}
|
|
200
|
+
* >
|
|
201
|
+
* {option.icon} {option.label}
|
|
202
|
+
* </button>
|
|
203
|
+
* ))}
|
|
204
|
+
*
|
|
205
|
+
* {showFollowUp && (
|
|
206
|
+
* <form onSubmit={(e) => {
|
|
207
|
+
* e.preventDefault();
|
|
208
|
+
* const form = e.target as HTMLFormElement;
|
|
209
|
+
* const input = form.elements.namedItem('followUp') as HTMLInputElement;
|
|
210
|
+
* react(showFollowUp, input.value);
|
|
211
|
+
* }}>
|
|
212
|
+
* <input name="followUp" placeholder="Tell us more..." />
|
|
213
|
+
* <button type="submit">Send</button>
|
|
214
|
+
* </form>
|
|
215
|
+
* )}
|
|
216
|
+
*
|
|
217
|
+
* {error && <div>Error: {error.message}</div>}
|
|
218
|
+
* </div>
|
|
219
|
+
* );
|
|
220
|
+
* }
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function useReaction(): UseReactionReturn;
|
|
224
|
+
|
|
136
225
|
/**
|
|
137
226
|
* @feedvalue/react - Components
|
|
138
227
|
*
|
|
@@ -169,5 +258,73 @@ interface FeedValueWidgetProps extends Omit<FeedValueProviderProps, 'children'>
|
|
|
169
258
|
* ```
|
|
170
259
|
*/
|
|
171
260
|
declare function FeedValueWidget(props: FeedValueWidgetProps): React.ReactElement;
|
|
261
|
+
/**
|
|
262
|
+
* Props for ReactionButtons component
|
|
263
|
+
*/
|
|
264
|
+
interface ReactionButtonsProps {
|
|
265
|
+
/** Custom CSS class for the container */
|
|
266
|
+
className?: string;
|
|
267
|
+
/** Custom CSS class for buttons */
|
|
268
|
+
buttonClassName?: string;
|
|
269
|
+
/** Custom CSS class for the follow-up form */
|
|
270
|
+
formClassName?: string;
|
|
271
|
+
/** Custom CSS class for the thank you message */
|
|
272
|
+
thankYouClassName?: string;
|
|
273
|
+
/** Callback when a reaction is submitted */
|
|
274
|
+
onReact?: (value: string, followUp?: string) => void;
|
|
275
|
+
/** Callback when an error occurs */
|
|
276
|
+
onError?: (error: Error) => void;
|
|
277
|
+
/** Custom render function for buttons (for full control) */
|
|
278
|
+
renderButton?: (option: ReactionOption, onClick: () => void, isDisabled: boolean) => React.ReactNode;
|
|
279
|
+
/** Custom render function for thank you message */
|
|
280
|
+
renderThankYou?: (value: string) => React.ReactNode;
|
|
281
|
+
/** Whether to show follow-up inline (default) or in a modal */
|
|
282
|
+
followUpMode?: 'inline' | 'none';
|
|
283
|
+
/** Hide after submission (default: false) */
|
|
284
|
+
hideAfterSubmit?: boolean;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* ReactionButtons component
|
|
288
|
+
*
|
|
289
|
+
* Renders reaction buttons (thumbs up/down, helpful, emoji, etc.) for inline feedback.
|
|
290
|
+
* Must be used within a FeedValueProvider with a reaction-type widget.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```tsx
|
|
294
|
+
* 'use client';
|
|
295
|
+
* import { FeedValueProvider, ReactionButtons } from '@feedvalue/react';
|
|
296
|
+
*
|
|
297
|
+
* function ArticleFooter() {
|
|
298
|
+
* return (
|
|
299
|
+
* <FeedValueProvider widgetId="your-reaction-widget-id" headless>
|
|
300
|
+
* <div>
|
|
301
|
+
* <h3>Was this helpful?</h3>
|
|
302
|
+
* <ReactionButtons
|
|
303
|
+
* onReact={(value) => console.log('Reacted:', value)}
|
|
304
|
+
* />
|
|
305
|
+
* </div>
|
|
306
|
+
* </FeedValueProvider>
|
|
307
|
+
* );
|
|
308
|
+
* }
|
|
309
|
+
* ```
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```tsx
|
|
313
|
+
* // With custom button rendering
|
|
314
|
+
* <ReactionButtons
|
|
315
|
+
* renderButton={(option, onClick, isDisabled) => (
|
|
316
|
+
* <button
|
|
317
|
+
* key={option.value}
|
|
318
|
+
* onClick={onClick}
|
|
319
|
+
* disabled={isDisabled}
|
|
320
|
+
* className="my-custom-button"
|
|
321
|
+
* >
|
|
322
|
+
* {option.icon} {option.label}
|
|
323
|
+
* </button>
|
|
324
|
+
* )}
|
|
325
|
+
* />
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
declare function ReactionButtons(props: ReactionButtonsProps): React.ReactElement;
|
|
172
329
|
|
|
173
|
-
export { type FeedValueContextValue, FeedValueProvider, type FeedValueProviderProps, FeedValueWidget, type FeedValueWidgetProps, useFeedValue, useFeedValueOptional };
|
|
330
|
+
export { type FeedValueContextValue, FeedValueProvider, type FeedValueProviderProps, FeedValueWidget, type FeedValueWidgetProps, ReactionButtons, type ReactionButtonsProps, type UseReactionReturn, useFeedValue, useFeedValueOptional, useReaction };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { FeedValue, FeedbackData, UserTraits, FeedValueConfig } from '@feedvalue/core';
|
|
3
|
-
export { FeedValueConfig, FeedValueEvents, FeedValueState, FeedbackData, FeedbackMetadata, UserData, UserTraits } from '@feedvalue/core';
|
|
2
|
+
import { FeedValue, FeedbackData, UserTraits, FeedValueConfig, ReactionState, ButtonSize, FollowUpTrigger, ReactionOption } from '@feedvalue/core';
|
|
3
|
+
export { FeedValueConfig, FeedValueEvents, FeedValueState, FeedbackData, FeedbackMetadata, ReactionConfig, ReactionData, ReactionOption, ReactionState, UserData, UserTraits, WidgetType } from '@feedvalue/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @feedvalue/react - Provider
|
|
@@ -133,6 +133,95 @@ declare function useFeedValue(): FeedValueContextValue;
|
|
|
133
133
|
*/
|
|
134
134
|
declare function useFeedValueOptional(): FeedValueContextValue | null;
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Return type for useReaction hook
|
|
138
|
+
*/
|
|
139
|
+
interface UseReactionReturn extends ReactionState {
|
|
140
|
+
/** Submit a reaction */
|
|
141
|
+
react: (value: string, followUp?: string) => Promise<void>;
|
|
142
|
+
/** Set which option is showing follow-up input */
|
|
143
|
+
setShowFollowUp: (value: string | null) => void;
|
|
144
|
+
/** Clear the submitted state to allow re-submission */
|
|
145
|
+
clearSubmitted: () => void;
|
|
146
|
+
/** Check if widget is a reaction type */
|
|
147
|
+
isReactionWidget: boolean;
|
|
148
|
+
/** Widget configuration is ready */
|
|
149
|
+
isReady: boolean;
|
|
150
|
+
/** Whether to show text labels next to icons */
|
|
151
|
+
showLabels: boolean;
|
|
152
|
+
/** Button size */
|
|
153
|
+
buttonSize: ButtonSize;
|
|
154
|
+
/** When to show follow-up input */
|
|
155
|
+
followUpTrigger: FollowUpTrigger;
|
|
156
|
+
/** Check if an option should show follow-up based on followUpTrigger */
|
|
157
|
+
shouldShowFollowUp: (optionValue: string) => boolean;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Hook for reaction widgets
|
|
161
|
+
*
|
|
162
|
+
* Provides reaction options, submission handling, and follow-up state management.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```tsx
|
|
166
|
+
* 'use client';
|
|
167
|
+
* import { useReaction } from '@feedvalue/react';
|
|
168
|
+
*
|
|
169
|
+
* function ReactionButtons() {
|
|
170
|
+
* const {
|
|
171
|
+
* options,
|
|
172
|
+
* react,
|
|
173
|
+
* isSubmitting,
|
|
174
|
+
* submitted,
|
|
175
|
+
* error,
|
|
176
|
+
* showFollowUp,
|
|
177
|
+
* setShowFollowUp,
|
|
178
|
+
* isReady,
|
|
179
|
+
* } = useReaction();
|
|
180
|
+
*
|
|
181
|
+
* if (!isReady || !options) return null;
|
|
182
|
+
*
|
|
183
|
+
* if (submitted) {
|
|
184
|
+
* return <div>Thanks for your feedback!</div>;
|
|
185
|
+
* }
|
|
186
|
+
*
|
|
187
|
+
* return (
|
|
188
|
+
* <div>
|
|
189
|
+
* {options.map((option) => (
|
|
190
|
+
* <button
|
|
191
|
+
* key={option.value}
|
|
192
|
+
* onClick={() => {
|
|
193
|
+
* if (option.showFollowUp) {
|
|
194
|
+
* setShowFollowUp(option.value);
|
|
195
|
+
* } else {
|
|
196
|
+
* react(option.value);
|
|
197
|
+
* }
|
|
198
|
+
* }}
|
|
199
|
+
* disabled={isSubmitting}
|
|
200
|
+
* >
|
|
201
|
+
* {option.icon} {option.label}
|
|
202
|
+
* </button>
|
|
203
|
+
* ))}
|
|
204
|
+
*
|
|
205
|
+
* {showFollowUp && (
|
|
206
|
+
* <form onSubmit={(e) => {
|
|
207
|
+
* e.preventDefault();
|
|
208
|
+
* const form = e.target as HTMLFormElement;
|
|
209
|
+
* const input = form.elements.namedItem('followUp') as HTMLInputElement;
|
|
210
|
+
* react(showFollowUp, input.value);
|
|
211
|
+
* }}>
|
|
212
|
+
* <input name="followUp" placeholder="Tell us more..." />
|
|
213
|
+
* <button type="submit">Send</button>
|
|
214
|
+
* </form>
|
|
215
|
+
* )}
|
|
216
|
+
*
|
|
217
|
+
* {error && <div>Error: {error.message}</div>}
|
|
218
|
+
* </div>
|
|
219
|
+
* );
|
|
220
|
+
* }
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function useReaction(): UseReactionReturn;
|
|
224
|
+
|
|
136
225
|
/**
|
|
137
226
|
* @feedvalue/react - Components
|
|
138
227
|
*
|
|
@@ -169,5 +258,73 @@ interface FeedValueWidgetProps extends Omit<FeedValueProviderProps, 'children'>
|
|
|
169
258
|
* ```
|
|
170
259
|
*/
|
|
171
260
|
declare function FeedValueWidget(props: FeedValueWidgetProps): React.ReactElement;
|
|
261
|
+
/**
|
|
262
|
+
* Props for ReactionButtons component
|
|
263
|
+
*/
|
|
264
|
+
interface ReactionButtonsProps {
|
|
265
|
+
/** Custom CSS class for the container */
|
|
266
|
+
className?: string;
|
|
267
|
+
/** Custom CSS class for buttons */
|
|
268
|
+
buttonClassName?: string;
|
|
269
|
+
/** Custom CSS class for the follow-up form */
|
|
270
|
+
formClassName?: string;
|
|
271
|
+
/** Custom CSS class for the thank you message */
|
|
272
|
+
thankYouClassName?: string;
|
|
273
|
+
/** Callback when a reaction is submitted */
|
|
274
|
+
onReact?: (value: string, followUp?: string) => void;
|
|
275
|
+
/** Callback when an error occurs */
|
|
276
|
+
onError?: (error: Error) => void;
|
|
277
|
+
/** Custom render function for buttons (for full control) */
|
|
278
|
+
renderButton?: (option: ReactionOption, onClick: () => void, isDisabled: boolean) => React.ReactNode;
|
|
279
|
+
/** Custom render function for thank you message */
|
|
280
|
+
renderThankYou?: (value: string) => React.ReactNode;
|
|
281
|
+
/** Whether to show follow-up inline (default) or in a modal */
|
|
282
|
+
followUpMode?: 'inline' | 'none';
|
|
283
|
+
/** Hide after submission (default: false) */
|
|
284
|
+
hideAfterSubmit?: boolean;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* ReactionButtons component
|
|
288
|
+
*
|
|
289
|
+
* Renders reaction buttons (thumbs up/down, helpful, emoji, etc.) for inline feedback.
|
|
290
|
+
* Must be used within a FeedValueProvider with a reaction-type widget.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```tsx
|
|
294
|
+
* 'use client';
|
|
295
|
+
* import { FeedValueProvider, ReactionButtons } from '@feedvalue/react';
|
|
296
|
+
*
|
|
297
|
+
* function ArticleFooter() {
|
|
298
|
+
* return (
|
|
299
|
+
* <FeedValueProvider widgetId="your-reaction-widget-id" headless>
|
|
300
|
+
* <div>
|
|
301
|
+
* <h3>Was this helpful?</h3>
|
|
302
|
+
* <ReactionButtons
|
|
303
|
+
* onReact={(value) => console.log('Reacted:', value)}
|
|
304
|
+
* />
|
|
305
|
+
* </div>
|
|
306
|
+
* </FeedValueProvider>
|
|
307
|
+
* );
|
|
308
|
+
* }
|
|
309
|
+
* ```
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```tsx
|
|
313
|
+
* // With custom button rendering
|
|
314
|
+
* <ReactionButtons
|
|
315
|
+
* renderButton={(option, onClick, isDisabled) => (
|
|
316
|
+
* <button
|
|
317
|
+
* key={option.value}
|
|
318
|
+
* onClick={onClick}
|
|
319
|
+
* disabled={isDisabled}
|
|
320
|
+
* className="my-custom-button"
|
|
321
|
+
* >
|
|
322
|
+
* {option.icon} {option.label}
|
|
323
|
+
* </button>
|
|
324
|
+
* )}
|
|
325
|
+
* />
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
declare function ReactionButtons(props: ReactionButtonsProps): React.ReactElement;
|
|
172
329
|
|
|
173
|
-
export { type FeedValueContextValue, FeedValueProvider, type FeedValueProviderProps, FeedValueWidget, type FeedValueWidgetProps, useFeedValue, useFeedValueOptional };
|
|
330
|
+
export { type FeedValueContextValue, FeedValueProvider, type FeedValueProviderProps, FeedValueWidget, type FeedValueWidgetProps, ReactionButtons, type ReactionButtonsProps, type UseReactionReturn, useFeedValue, useFeedValueOptional, useReaction };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createContext, useRef, useEffect, useSyncExternalStore, useCallback, useMemo, useContext } from 'react';
|
|
2
|
-
import { FeedValue } from '@feedvalue/core';
|
|
3
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import React2, { createContext, useRef, useEffect, useSyncExternalStore, useCallback, useMemo, useContext, useState } from 'react';
|
|
2
|
+
import { FeedValue, NEGATIVE_OPTIONS_MAP } from '@feedvalue/core';
|
|
3
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
var FeedValueContext = createContext(null);
|
|
6
6
|
var getServerSnapshot = () => ({
|
|
@@ -120,10 +120,308 @@ function useFeedValue() {
|
|
|
120
120
|
function useFeedValueOptional() {
|
|
121
121
|
return useContext(FeedValueContext);
|
|
122
122
|
}
|
|
123
|
+
function useReaction() {
|
|
124
|
+
const { instance, isReady } = useFeedValue();
|
|
125
|
+
const [showFollowUp, setShowFollowUp] = useState(null);
|
|
126
|
+
const [submitted, setSubmitted] = useState(null);
|
|
127
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
128
|
+
const [error, setError] = useState(null);
|
|
129
|
+
const options = useMemo(() => {
|
|
130
|
+
if (!instance || !isReady) return null;
|
|
131
|
+
return instance.getReactionOptions();
|
|
132
|
+
}, [instance, isReady]);
|
|
133
|
+
const reactionConfig = useMemo(() => {
|
|
134
|
+
if (!instance || !isReady) return null;
|
|
135
|
+
return instance._widgetConfig?.config ?? null;
|
|
136
|
+
}, [instance, isReady]);
|
|
137
|
+
const showLabels = reactionConfig?.showLabels ?? true;
|
|
138
|
+
const buttonSize = reactionConfig?.buttonSize ?? "md";
|
|
139
|
+
const followUpTrigger = reactionConfig?.followUpTrigger ?? "negative";
|
|
140
|
+
const template = reactionConfig?.template;
|
|
141
|
+
const isReactionWidget = useMemo(() => {
|
|
142
|
+
return instance?.isReaction() ?? false;
|
|
143
|
+
}, [instance]);
|
|
144
|
+
const shouldShowFollowUp = useCallback(
|
|
145
|
+
(optionValue) => {
|
|
146
|
+
if (followUpTrigger === "none") return false;
|
|
147
|
+
if (followUpTrigger === "all") return true;
|
|
148
|
+
if (template && NEGATIVE_OPTIONS_MAP[template]) {
|
|
149
|
+
return NEGATIVE_OPTIONS_MAP[template].includes(optionValue);
|
|
150
|
+
}
|
|
151
|
+
const option = options?.find((o) => o.value === optionValue);
|
|
152
|
+
return option?.showFollowUp ?? false;
|
|
153
|
+
},
|
|
154
|
+
[followUpTrigger, template, options]
|
|
155
|
+
);
|
|
156
|
+
const react = useCallback(
|
|
157
|
+
async (value, followUp) => {
|
|
158
|
+
if (!instance) {
|
|
159
|
+
throw new Error("FeedValue not initialized");
|
|
160
|
+
}
|
|
161
|
+
setIsSubmitting(true);
|
|
162
|
+
setError(null);
|
|
163
|
+
try {
|
|
164
|
+
await instance.react(value, followUp ? { followUp } : void 0);
|
|
165
|
+
setSubmitted(value);
|
|
166
|
+
setShowFollowUp(null);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
169
|
+
setError(error2);
|
|
170
|
+
throw error2;
|
|
171
|
+
} finally {
|
|
172
|
+
setIsSubmitting(false);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
[instance]
|
|
176
|
+
);
|
|
177
|
+
const clearSubmitted = useCallback(() => {
|
|
178
|
+
setSubmitted(null);
|
|
179
|
+
setError(null);
|
|
180
|
+
}, []);
|
|
181
|
+
return {
|
|
182
|
+
options,
|
|
183
|
+
isSubmitting,
|
|
184
|
+
submitted,
|
|
185
|
+
error,
|
|
186
|
+
showFollowUp,
|
|
187
|
+
setShowFollowUp,
|
|
188
|
+
react,
|
|
189
|
+
clearSubmitted,
|
|
190
|
+
isReactionWidget,
|
|
191
|
+
isReady,
|
|
192
|
+
showLabels,
|
|
193
|
+
buttonSize,
|
|
194
|
+
followUpTrigger,
|
|
195
|
+
shouldShowFollowUp
|
|
196
|
+
};
|
|
197
|
+
}
|
|
123
198
|
function FeedValueWidget(props) {
|
|
124
199
|
return /* @__PURE__ */ jsx(FeedValueProvider, { ...props, children: null });
|
|
125
200
|
}
|
|
201
|
+
var sizeStyles = {
|
|
202
|
+
sm: {
|
|
203
|
+
button: { padding: "6px 12px", fontSize: "12px", gap: "4px" },
|
|
204
|
+
icon: { fontSize: "16px" },
|
|
205
|
+
label: { fontSize: "12px" }
|
|
206
|
+
},
|
|
207
|
+
md: {
|
|
208
|
+
button: { padding: "8px 16px", fontSize: "14px", gap: "6px" },
|
|
209
|
+
icon: { fontSize: "18px" },
|
|
210
|
+
label: { fontSize: "14px" }
|
|
211
|
+
},
|
|
212
|
+
lg: {
|
|
213
|
+
button: { padding: "12px 20px", fontSize: "16px", gap: "8px" },
|
|
214
|
+
icon: { fontSize: "24px" },
|
|
215
|
+
label: { fontSize: "16px" }
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var defaultStyles = {
|
|
219
|
+
container: {
|
|
220
|
+
display: "flex",
|
|
221
|
+
alignItems: "center",
|
|
222
|
+
gap: "8px",
|
|
223
|
+
flexWrap: "wrap"
|
|
224
|
+
},
|
|
225
|
+
button: {
|
|
226
|
+
display: "inline-flex",
|
|
227
|
+
alignItems: "center",
|
|
228
|
+
border: "1px solid #e0e0e0",
|
|
229
|
+
borderRadius: "20px",
|
|
230
|
+
background: "#fff",
|
|
231
|
+
cursor: "pointer",
|
|
232
|
+
transition: "all 0.15s ease"
|
|
233
|
+
},
|
|
234
|
+
buttonHover: {
|
|
235
|
+
borderColor: "#6366f1",
|
|
236
|
+
background: "#f5f3ff"
|
|
237
|
+
},
|
|
238
|
+
buttonDisabled: {
|
|
239
|
+
opacity: 0.6,
|
|
240
|
+
cursor: "not-allowed"
|
|
241
|
+
},
|
|
242
|
+
form: {
|
|
243
|
+
display: "flex",
|
|
244
|
+
flexDirection: "column",
|
|
245
|
+
gap: "8px",
|
|
246
|
+
marginTop: "8px",
|
|
247
|
+
width: "100%",
|
|
248
|
+
maxWidth: "400px"
|
|
249
|
+
},
|
|
250
|
+
input: {
|
|
251
|
+
padding: "8px 12px",
|
|
252
|
+
border: "1px solid #e0e0e0",
|
|
253
|
+
borderRadius: "8px",
|
|
254
|
+
fontSize: "14px",
|
|
255
|
+
resize: "none"
|
|
256
|
+
},
|
|
257
|
+
submitButton: {
|
|
258
|
+
padding: "8px 16px",
|
|
259
|
+
border: "none",
|
|
260
|
+
borderRadius: "8px",
|
|
261
|
+
background: "#6366f1",
|
|
262
|
+
color: "#fff",
|
|
263
|
+
cursor: "pointer",
|
|
264
|
+
fontSize: "14px",
|
|
265
|
+
fontWeight: 500,
|
|
266
|
+
alignSelf: "flex-start"
|
|
267
|
+
},
|
|
268
|
+
thankYou: {
|
|
269
|
+
color: "#059669",
|
|
270
|
+
fontSize: "14px",
|
|
271
|
+
fontWeight: 500
|
|
272
|
+
},
|
|
273
|
+
error: {
|
|
274
|
+
color: "#dc2626",
|
|
275
|
+
fontSize: "14px"
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
function ReactionButtonsInner({
|
|
279
|
+
className,
|
|
280
|
+
buttonClassName,
|
|
281
|
+
formClassName,
|
|
282
|
+
thankYouClassName,
|
|
283
|
+
onReact,
|
|
284
|
+
onError,
|
|
285
|
+
renderButton,
|
|
286
|
+
renderThankYou,
|
|
287
|
+
followUpMode = "inline",
|
|
288
|
+
hideAfterSubmit = false
|
|
289
|
+
}) {
|
|
290
|
+
const {
|
|
291
|
+
options,
|
|
292
|
+
react,
|
|
293
|
+
isSubmitting,
|
|
294
|
+
submitted,
|
|
295
|
+
error,
|
|
296
|
+
showFollowUp,
|
|
297
|
+
setShowFollowUp,
|
|
298
|
+
isReactionWidget,
|
|
299
|
+
isReady,
|
|
300
|
+
showLabels,
|
|
301
|
+
buttonSize,
|
|
302
|
+
shouldShowFollowUp
|
|
303
|
+
} = useReaction();
|
|
304
|
+
const [followUpText, setFollowUpText] = useState("");
|
|
305
|
+
const [hoveredButton, setHoveredButton] = useState(null);
|
|
306
|
+
const handleClick = useCallback(
|
|
307
|
+
(option) => {
|
|
308
|
+
if (shouldShowFollowUp(option.value) && followUpMode === "inline") {
|
|
309
|
+
setShowFollowUp(option.value);
|
|
310
|
+
} else {
|
|
311
|
+
react(option.value).then(() => onReact?.(option.value)).catch((err) => onError?.(err));
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
[react, setShowFollowUp, followUpMode, onReact, onError, shouldShowFollowUp]
|
|
315
|
+
);
|
|
316
|
+
const handleFollowUpSubmit = useCallback(
|
|
317
|
+
(e) => {
|
|
318
|
+
e.preventDefault();
|
|
319
|
+
if (!showFollowUp) return;
|
|
320
|
+
react(showFollowUp, followUpText.trim() || void 0).then(() => {
|
|
321
|
+
onReact?.(showFollowUp, followUpText.trim() || void 0);
|
|
322
|
+
setFollowUpText("");
|
|
323
|
+
}).catch((err) => onError?.(err));
|
|
324
|
+
},
|
|
325
|
+
[react, showFollowUp, followUpText, onReact, onError]
|
|
326
|
+
);
|
|
327
|
+
const handleCancelFollowUp = useCallback(() => {
|
|
328
|
+
setShowFollowUp(null);
|
|
329
|
+
setFollowUpText("");
|
|
330
|
+
}, [setShowFollowUp]);
|
|
331
|
+
if (!isReady || !isReactionWidget || !options) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
if (submitted && hideAfterSubmit) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
if (submitted) {
|
|
338
|
+
if (renderThankYou) {
|
|
339
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderThankYou(submitted) });
|
|
340
|
+
}
|
|
341
|
+
return /* @__PURE__ */ jsx("div", { className: thankYouClassName, style: thankYouClassName ? void 0 : defaultStyles.thankYou, children: "Thanks for your feedback!" });
|
|
342
|
+
}
|
|
343
|
+
const followUpOption = showFollowUp ? options.find((o) => o.value === showFollowUp) : null;
|
|
344
|
+
return /* @__PURE__ */ jsxs("div", { className, style: className ? void 0 : defaultStyles.container, children: [
|
|
345
|
+
!showFollowUp && options.map((option) => {
|
|
346
|
+
if (renderButton) {
|
|
347
|
+
return /* @__PURE__ */ jsx(React2.Fragment, { children: renderButton(option, () => handleClick(option), isSubmitting) }, option.value);
|
|
348
|
+
}
|
|
349
|
+
const isHovered = hoveredButton === option.value;
|
|
350
|
+
const sizeStyle = sizeStyles[buttonSize] || sizeStyles.md;
|
|
351
|
+
const buttonStyle = {
|
|
352
|
+
...defaultStyles.button,
|
|
353
|
+
...sizeStyle.button,
|
|
354
|
+
...isHovered ? defaultStyles.buttonHover : {},
|
|
355
|
+
...isSubmitting ? defaultStyles.buttonDisabled : {}
|
|
356
|
+
};
|
|
357
|
+
return /* @__PURE__ */ jsxs(
|
|
358
|
+
"button",
|
|
359
|
+
{
|
|
360
|
+
type: "button",
|
|
361
|
+
className: buttonClassName,
|
|
362
|
+
style: buttonClassName ? void 0 : buttonStyle,
|
|
363
|
+
onClick: () => handleClick(option),
|
|
364
|
+
onMouseEnter: () => setHoveredButton(option.value),
|
|
365
|
+
onMouseLeave: () => setHoveredButton(null),
|
|
366
|
+
disabled: isSubmitting,
|
|
367
|
+
"aria-label": option.label,
|
|
368
|
+
children: [
|
|
369
|
+
/* @__PURE__ */ jsx("span", { role: "img", "aria-hidden": "true", style: sizeStyle.icon, children: option.icon }),
|
|
370
|
+
showLabels && /* @__PURE__ */ jsx("span", { style: sizeStyle.label, children: option.label })
|
|
371
|
+
]
|
|
372
|
+
},
|
|
373
|
+
option.value
|
|
374
|
+
);
|
|
375
|
+
}),
|
|
376
|
+
showFollowUp && followUpOption && /* @__PURE__ */ jsxs(
|
|
377
|
+
"form",
|
|
378
|
+
{
|
|
379
|
+
className: formClassName,
|
|
380
|
+
style: formClassName ? void 0 : defaultStyles.form,
|
|
381
|
+
onSubmit: handleFollowUpSubmit,
|
|
382
|
+
children: [
|
|
383
|
+
/* @__PURE__ */ jsx(
|
|
384
|
+
"textarea",
|
|
385
|
+
{
|
|
386
|
+
value: followUpText,
|
|
387
|
+
onChange: (e) => setFollowUpText(e.target.value),
|
|
388
|
+
placeholder: followUpOption.followUpPlaceholder ?? "Tell us more (optional)",
|
|
389
|
+
rows: 3,
|
|
390
|
+
style: defaultStyles.input,
|
|
391
|
+
disabled: isSubmitting
|
|
392
|
+
}
|
|
393
|
+
),
|
|
394
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
395
|
+
/* @__PURE__ */ jsx(
|
|
396
|
+
"button",
|
|
397
|
+
{
|
|
398
|
+
type: "submit",
|
|
399
|
+
style: defaultStyles.submitButton,
|
|
400
|
+
disabled: isSubmitting,
|
|
401
|
+
children: isSubmitting ? "Sending..." : "Send"
|
|
402
|
+
}
|
|
403
|
+
),
|
|
404
|
+
/* @__PURE__ */ jsx(
|
|
405
|
+
"button",
|
|
406
|
+
{
|
|
407
|
+
type: "button",
|
|
408
|
+
onClick: handleCancelFollowUp,
|
|
409
|
+
style: { ...defaultStyles.button, padding: "8px 16px" },
|
|
410
|
+
disabled: isSubmitting,
|
|
411
|
+
children: "Cancel"
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
] })
|
|
415
|
+
]
|
|
416
|
+
}
|
|
417
|
+
),
|
|
418
|
+
error && /* @__PURE__ */ jsx("div", { style: defaultStyles.error, children: error.message })
|
|
419
|
+
] });
|
|
420
|
+
}
|
|
421
|
+
function ReactionButtons(props) {
|
|
422
|
+
return /* @__PURE__ */ jsx(ReactionButtonsInner, { ...props });
|
|
423
|
+
}
|
|
126
424
|
|
|
127
|
-
export { FeedValueProvider, FeedValueWidget, useFeedValue, useFeedValueOptional };
|
|
425
|
+
export { FeedValueProvider, FeedValueWidget, ReactionButtons, useFeedValue, useFeedValueOptional, useReaction };
|
|
128
426
|
//# sourceMappingURL=index.js.map
|
|
129
427
|
//# sourceMappingURL=index.js.map
|