@ewjdev/anyclick-reactReact Provider
Drop-in React provider and context menu UI for feedback capture. Handles all event management, UI rendering, and screenshot capture.
Installation
npm install @ewjdev/anyclick-reactRequires React 19+ and includes @ewjdev/anyclick-core as a dependency.
Basic Usage
'use client';
import { AnyclickProvider } from '@ewjdev/anyclick-react';import { createHttpAdapter } from '@ewjdev/anyclick-github';
const adapter = createHttpAdapter({ endpoint: '/api/feedback',});
export function Providers({ children }: { children: React.ReactNode }) { return ( <AnyclickProvider adapter={adapter}> {children} </AnyclickProvider> );}AnyclickProvider Props
adapterrequiredAnyclickAdapterThe adapter to use for submitting feedback. See adapters documentation for available options.
childrenrequiredReactNodeChild components to wrap with the feedback provider.
scopedbooleandefault: falseWhen true, only captures events within this provider's children. Useful for section-specific feedback.
themeAnyclickTheme | nullTheme configuration. Themes inherit from parent providers. Set to null or { disabled: true } to disable feedback.
menuItemsContextMenuItem[]default: [issue, feature, like]Custom menu items to show in the context menu. Each item can have type, label, icon, and children for submenus.
metadataRecord<string, unknown>Additional metadata to include with every feedback submission. Useful for user ID, session ID, etc.
targetFilter(event: MouseEvent, target: Element) => booleanFilter function to determine if feedback should be captured for a target element. Return false to ignore.
highlightConfigHighlightConfigConfiguration for element highlighting behavior and colors.
screenshotConfigScreenshotConfigConfiguration for screenshot capture settings (quality, format, etc.).
maxInnerTextLengthnumberdefault: 500Maximum length for captured inner text content.
maxOuterHTMLLengthnumberdefault: 2000Maximum length for captured outer HTML.
maxAncestorsnumberdefault: 5Maximum number of ancestor elements to capture.
cooldownMsnumberdefault: 1000Cooldown in milliseconds between submissions (rate limiting).
stripAttributesstring[]HTML attributes to strip from captured outerHTML for privacy.
onSubmitSuccess(payload: AnyclickPayload) => voidCallback fired after successful submission.
onSubmitError(error: Error, payload: AnyclickPayload) => voidCallback fired after failed submission.
menuStyleCSSPropertiesCustom inline styles for the context menu.
menuClassNamestringCustom CSS class name for the context menu.
disabledbooleandefault: falseDisable feedback capture entirely.
Scoped Providers
See it live: scoped & nested providersUse the scoped prop to limit feedback capture to a specific section of your app:
import { AnyclickProvider } from '@ewjdev/anyclick-react';
export function Providers({ children }) { return ( <AnyclickProvider adapter={globalAdapter}> <Header /> {/* Scoped provider - only captures events in Dashboard */} <AnyclickProvider adapter={dashboardAdapter} scoped menuItems={dashboardMenuItems} > <Dashboard /> </AnyclickProvider> <Footer /> </AnyclickProvider> );}Nested Theming
Child providers inherit and can override parent themes. Use this for section-specific styling:
<AnyclickProvider adapter={adapter} theme={{ highlightConfig: { colors: { targetColor: '#3b82f6' } // Blue } }}> <MainContent /> {/* Uses blue highlights */} <AnyclickProvider scoped theme={{ highlightConfig: { colors: { targetColor: '#ef4444' } // Red } }} > <WarningSection /> {/* Uses red highlights */} </AnyclickProvider> {/* Disable feedback in sensitive areas */} <AnyclickProvider scoped theme={{ disabled: true }}> <PaymentForm /> </AnyclickProvider></AnyclickProvider>Custom Menu Items
See it live: custom menuCustomize the feedback menu with your own items, icons, and submenus:
import { AnyclickProvider } from '@ewjdev/anyclick-react';import { Bug, Lightbulb, ThumbsUp, Code, Monitor, Cloud } from 'lucide-react';
const menuItems = [ { type: 'bug', label: 'Report Bug', icon: <Bug className="w-4 h-4" />, showComment: true, }, { type: 'feature', label: 'Suggest Feature', icon: <Lightbulb className="w-4 h-4" />, showComment: true, }, { type: 'praise', label: 'This is great!', icon: <ThumbsUp className="w-4 h-4" />, showComment: false, }, // Submenu example { type: 'dev_menu', label: 'Developer Tools', icon: <Code className="w-4 h-4" />, requiredRoles: ['developer', 'admin'], // Role-based visibility children: [ { type: 'cursor_local', label: 'Fix with Cursor (Local)', icon: <Monitor className="w-4 h-4" /> }, { type: 'cursor_cloud', label: 'Fix with Cursor (Cloud)', icon: <Cloud className="w-4 h-4" /> }, ], },];
<AnyclickProvider adapter={adapter} menuItems={menuItems}> {children}</AnyclickProvider>Role-Based Menu Filtering
See it live: role-based presetsShow different menu items based on user roles:
import { AnyclickProvider, filterMenuItemsByRole } from '@ewjdev/anyclick-react';import type { ContextMenuItem, AnyclickUserContext } from '@ewjdev/anyclick-react';
const allMenuItems: ContextMenuItem[] = [ { type: 'issue', label: 'Report Issue', showComment: true }, { type: 'feature', label: 'Request Feature', showComment: true }, // Only visible to developers { type: 'debug', label: 'Debug Info', requiredRoles: ['developer'], },];
function Providers({ children, user }) { const userContext: AnyclickUserContext = { roles: user.roles, id: user.id, email: user.email, }; const menuItems = filterMenuItemsByRole(allMenuItems, userContext); return ( <AnyclickProvider adapter={adapter} menuItems={menuItems} metadata={userContext} > {children} </AnyclickProvider> );}Highlight Configuration
Customize how elements are highlighted when the context menu is open:
<AnyclickProvider adapter={adapter} theme={{ highlightConfig: { enabled: true, colors: { targetColor: '#3b82f6', // Blue for target element containerColor: '#8b5cf6', // Purple for container targetShadowOpacity: 0.25, containerShadowOpacity: 0.1, }, containerSelectors: [ '[data-component]', '[data-section]', '.card', 'section', ], minChildrenForContainer: 2, }, }}> {children}</AnyclickProvider>Screenshot Configuration
See it live: sensitive maskingControl screenshot capture behavior:
<AnyclickProvider adapter={adapter} theme={{ screenshotConfig: { enabled: true, quality: 0.9, // Selectors to mask for privacy sensitiveSelectors: [ '[data-sensitive]', '.credit-card-input', 'input[type="password"]', ], }, }}> {children}</AnyclickProvider>useAnyclick Hook
Access anyclick context from child components:
import { useAnyclick } from '@ewjdev/anyclick-react';
function MyComponent() { const { isEnabled, // Whether feedback is enabled isSubmitting, // Whether submission is in progress submitFeedback, // Submit feedback programmatically openMenu, // Open menu programmatically closeMenu, // Close menu theme, // Current merged theme scoped, // Whether provider is scoped providerId, // Unique provider ID } = useAnyclick(); // Open menu programmatically const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { openMenu( event.currentTarget, { x: event.clientX, y: event.clientY } ); }; // Submit feedback programmatically const handleSubmit = async () => { await submitFeedback( document.querySelector('#my-element')!, 'issue', 'This element has a problem' ); }; return ( <button onClick={handleClick} disabled={isSubmitting}> {isSubmitting ? 'Submitting...' : 'Open Feedback'} </button> );}All Exports
// Componentsexport { AnyclickProvider } from './AnyclickProvider';export { ContextMenu } from './ContextMenu';export { ScreenshotPreview } from './ScreenshotPreview';
// Context & Hooksexport { AnyclickContext, useAnyclick } from './context';
// Store (for advanced use cases)export { useProviderStore, generateProviderId } from './store';
// Utilitiesexport { filterMenuItemsByRole } from './types';
// Typesexport type { AnyclickProviderProps, AnyclickContextValue, AnyclickTheme, ContextMenuItem, AnyclickUserContext, ContextMenuProps, ScreenshotPreviewProps, HighlightConfig, HighlightColors,} from './types';