@ewjdev/anyclick-react

React 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-react

Requires React 19+ and includes @ewjdev/anyclick-core as a dependency.

Basic Usage

app/providers.tsx
'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

adapterrequiredAnyclickAdapter

The adapter to use for submitting feedback. See adapters documentation for available options.

childrenrequiredReactNode

Child components to wrap with the feedback provider.

scopedbooleandefault: false

When true, only captures events within this provider's children. Useful for section-specific feedback.

themeAnyclickTheme | null

Theme 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) => boolean

Filter function to determine if feedback should be captured for a target element. Return false to ignore.

highlightConfigHighlightConfig

Configuration for element highlighting behavior and colors.

screenshotConfigScreenshotConfig

Configuration for screenshot capture settings (quality, format, etc.).

maxInnerTextLengthnumberdefault: 500

Maximum length for captured inner text content.

maxOuterHTMLLengthnumberdefault: 2000

Maximum length for captured outer HTML.

maxAncestorsnumberdefault: 5

Maximum number of ancestor elements to capture.

cooldownMsnumberdefault: 1000

Cooldown in milliseconds between submissions (rate limiting).

stripAttributesstring[]

HTML attributes to strip from captured outerHTML for privacy.

onSubmitSuccess(payload: AnyclickPayload) => void

Callback fired after successful submission.

onSubmitError(error: Error, payload: AnyclickPayload) => void

Callback fired after failed submission.

menuStyleCSSProperties

Custom inline styles for the context menu.

menuClassNamestring

Custom CSS class name for the context menu.

disabledbooleandefault: false

Disable feedback capture entirely.

Scoped Providers

See it live: scoped & nested providers

Use the scoped prop to limit feedback capture to a specific section of your app:

app/layout.tsx
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 menu

Customize the feedback menu with your own items, icons, and submenus:

app/providers.tsx
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 presets

Show 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 masking

Control 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

// Components
export { AnyclickProvider } from './AnyclickProvider';
export { ContextMenu } from './ContextMenu';
export { ScreenshotPreview } from './ScreenshotPreview';
// Context & Hooks
export { AnyclickContext, useAnyclick } from './context';
// Store (for advanced use cases)
export { useProviderStore, generateProviderId } from './store';
// Utilities
export { filterMenuItemsByRole } from './types';
// Types
export type {
AnyclickProviderProps,
AnyclickContextValue,
AnyclickTheme,
ContextMenuItem,
AnyclickUserContext,
ContextMenuProps,
ScreenshotPreviewProps,
HighlightConfig,
HighlightColors,
} from './types';

Related