@ewjdev/anyclick-react
v5.2.0
Published
React provider and context menu UI for UI feedback capture
Maintainers
Readme
@ewjdev/anyclick-react
Scoped conversations and reviewed actions
Conversation is the opt-in controller and UI used by the homepage's four sample applications. It supports full AI SDK 5 UI messages, editable result cards, / action completion, @ object references, and cancellable suggestion providers. Import @ewjdev/anyclick-react/styles.css for the default styles, or style the exported ac-* classes in your application.
The host supplies a stable conversationId, selected context, onContextChange, a chat endpoint, and optionally a historyEndpoint, commands, objects, suggestionProvider, and actionDispatcher. History returns { messages: ConversationMessage[] }. Chat streams text and typed data-result parts. An action dispatcher prepares a preview; it must not silently execute a mutation. Preserve full message parts, IDs, and metadata when persisting history.
Existing QuickChat consumers remain supported. Pass the optional conversation prop to opt into the new controller without changing the visibility/close API. Legacy endpoints and histories are not silently migrated.
See the website's /docs/showcase route and apps/web/src/lib/showcase for executable domain and server examples. The core library does not own application records, credentials, or a workflow engine.
React provider and context menu UI for UI feedback capture
Overview
@ewjdev/anyclick-react provides a drop-in React component that adds feedback capture to your application. Right-click any element to open a customizable context menu for submitting feedback.
Installation
npm install @ewjdev/anyclick-react
# or
yarn add @ewjdev/anyclick-react
# or
pnpm add @ewjdev/anyclick-reactRequirements
- React 19+
- @ewjdev/anyclick-core (included as dependency)
Quick Start
"use client";
import { createHttpAdapter } from "@ewjdev/anyclick-github";
import { AnyclickProvider } from "@ewjdev/anyclick-react";
const adapter = createHttpAdapter({
endpoint: "/api/feedback",
});
export function Providers({ children }: { children: React.ReactNode }) {
return <AnyclickProvider adapter={adapter}>{children}</AnyclickProvider>;
}That's it! Users can now right-click any element to submit feedback.
Advanced inspector (DevTools UI)
For the full Chrome DevTools-style inspector (modification tracking, box model overlay, accessibility deep dive), install the dedicated package:
npm install @ewjdev/anyclick-devtoolsimport {
InspectDialogManager,
openInspectDialog,
} from "@ewjdev/anyclick-devtools";
// Place once near the root
<InspectDialogManager />;
// Trigger from a menu item or button
openInspectDialog(targetElement);@ewjdev/anyclick-react keeps the tiny inspector for quick selector/copy/screenshot flows, while @ewjdev/anyclick-devtools hosts the full-featured experience.
Features
- 🖱️ Right-Click Context Menu - Native feel with customizable items
- 📱 Touch Support - Press-and-hold on mobile devices
- 🎨 Element Highlighting - Visual feedback for target and container elements
- 📸 Screenshot Capture - Automatic screenshots of target, container, and viewport
- 🔒 Role-Based Menu Items - Show different options based on user roles
- 📁 Submenus - Organize menu items with nested submenus
- 🎯 Scoped Providers - Limit feedback capture to specific components
- 🎨 Nested Theming - Override themes for specific sections of your app
- 🔍 Tiny Inspector - Built-in lightweight inspector (selector, contents, screenshot); full DevTools UI now lives in
@ewjdev/anyclick-devtools - ⚡ Performance Optimized - Memoized components and efficient re-renders
API Reference
AnyclickProvider Props
| Prop | Type | Default | Description |
| --------------------- | -------------------------- | ----------- | ---------------------------------------------- |
| adapter | AnyclickAdapter | Required | The adapter for submitting feedback |
| menuItems | ContextMenuItem[] | Default set | Custom menu items |
| metadata | Record<string, unknown> | undefined | Additional data included with every submission |
| theme | AnyclickTheme \| null | undefined | Theme configuration (inherits from parent) |
| scoped | boolean | false | Limit capture to this provider's children only |
| disabled | boolean | false | Disable anyclick capture |
| highlightConfig | HighlightConfig | Default | Element highlighting configuration |
| screenshotConfig | ScreenshotConfig | Default | Screenshot capture configuration |
| onSubmitSuccess | (payload) => void | undefined | Success callback |
| onSubmitError | (error, payload) => void | undefined | Error callback |
| touchHoldDurationMs | number | 500 | Touch hold duration before triggering menu |
| touchMoveThreshold | number | 10 | Max movement in px before touch hold cancels |
AnyclickTheme
interface AnyclickTheme {
/** Custom styles for the context menu */
menuStyle?: CSSProperties;
/** Custom class name for the context menu */
menuClassName?: string;
/** Configuration for element highlighting */
highlightConfig?: HighlightConfig;
/** Configuration for screenshot capture */
screenshotConfig?: ScreenshotConfig;
/** Disable anyclick in this subtree */
disabled?: boolean;
/** Enable fun mode (go-kart cursor) */
funMode?: boolean | FunModeThemeConfig;
}ContextMenuItem
interface ContextMenuItem {
/** Unique type identifier */
type: string;
/** Display label */
label: string;
/** Optional icon */
icon?: ReactNode;
/** Show comment input after selection */
showComment?: boolean;
/** Status badge (e.g., "comingSoon") */
status?: "available" | "comingSoon";
/** Visual badge configuration */
badge?: { label: string; tone?: "neutral" | "info" | "warning" | "success" };
/** Required roles to see this item */
requiredRoles?: string[];
/** Nested submenu items */
children?: ContextMenuItem[];
/** Custom click handler */
onClick?: (context: {
targetElement: Element | null;
containerElement: Element | null;
closeMenu: () => void;
}) => void | boolean | Promise<void | boolean>;
}HighlightConfig
interface HighlightConfig {
/** Enable/disable highlighting */
enabled?: boolean;
/** Custom highlight colors */
colors?: {
targetColor?: string; // Default: "#3b82f6"
containerColor?: string; // Default: "#8b5cf6"
targetShadowOpacity?: number; // Default: 0.25
containerShadowOpacity?: number; // Default: 0.1
};
/** CSS selectors to identify container elements */
containerSelectors?: string[];
/** Minimum children count for auto-container detection */
minChildrenForContainer?: number;
}Examples
Scoped Providers
Limit feedback capture to specific sections of your app:
import { AnyclickProvider } from "@ewjdev/anyclick-react";
function App() {
return (
<AnyclickProvider adapter={globalAdapter}>
{/* Global feedback works everywhere */}
<Header />
{/* Scoped provider - separate configuration */}
<AnyclickProvider
adapter={dashboardAdapter}
scoped
menuItems={dashboardMenuItems}
>
<Dashboard />
</AnyclickProvider>
<Footer />
</AnyclickProvider>
);
}Nested Theming
Override themes for specific sections:
import { AnyclickProvider } from "@ewjdev/anyclick-react";
function App() {
return (
<AnyclickProvider
adapter={adapter}
theme={{
highlightConfig: {
colors: { targetColor: "#3b82f6" },
},
}}
>
{/* Uses blue highlights */}
<MainContent />
{/* Uses red highlights (overrides parent) */}
<AnyclickProvider
scoped
theme={{
highlightConfig: {
colors: { targetColor: "#ef4444" },
},
}}
>
<WarningSection />
</AnyclickProvider>
{/* Disable anyclick for this section */}
<AnyclickProvider scoped theme={{ disabled: true }}>
<SensitiveArea />
</AnyclickProvider>
</AnyclickProvider>
);
}Custom Menu Items
import { Bug, Heart, Lightbulb } 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: "love",
label: "Love It!",
icon: <Heart className="w-4 h-4" />,
showComment: false,
},
];
<AnyclickProvider adapter={adapter} menuItems={menuItems}>
{children}
</AnyclickProvider>;Role-Based Filtering
import { filterMenuItemsByRole } from "@ewjdev/anyclick-react";
const allMenuItems = [
{ type: "bug", label: "Report Bug" },
{ type: "debug", label: "Debug Info", requiredRoles: ["developer"] },
];
const userContext = { roles: ["user", "developer"] };
const menuItems = filterMenuItemsByRole(allMenuItems, userContext);Role-Based Presets
Skip hand-authoring menus by pulling in a preset for common roles:
import { AnyclickProvider, createPresetMenu } from "@ewjdev/anyclick-react";
const qaPreset = createPresetMenu("qa");
// Available presets: "qa", "pm", "designer", "developer", "chrome"
<AnyclickProvider
adapter={adapter}
menuItems={qaPreset.menuItems}
screenshotConfig={qaPreset.screenshotConfig}
metadata={qaPreset.metadata}
theme={qaPreset.theme}
>
{children}
</AnyclickProvider>;Available Presets:
| Preset | Description |
| ----------- | ----------------------------------------------------------------- |
| qa | Bug/defect, UX papercut, repro steps, + performance trace (soon) |
| pm | Feature idea, UX papercut, customer quote, + impact sizing (soon) |
| designer | Visual bug, accessibility, copy/tone, + motion glitch (soon) |
| developer | Bug, refactor request, + diagnostics submenu |
| chrome | Chrome-like menu with reload, print, search, share, inspect |
useAnyclick Hook
Access anyclick context from child components:
import { useAnyclick } from "@ewjdev/anyclick-react";
function MyComponent() {
const {
isEnabled, // Whether anyclick is active
isSubmitting, // Whether a submission is in progress
openMenu, // Open menu programmatically
closeMenu, // Close menu
theme, // Current merged theme
scoped, // Whether provider is scoped
providerId, // Unique provider ID
} = useAnyclick();
const handleClick = (event) => {
openMenu(event.currentTarget, { x: event.clientX, y: event.clientY });
};
return <button onClick={handleClick}>Open Feedback</button>;
}Custom Styling with CSS Variables
Override menu styling using CSS custom properties:
<AnyclickProvider
adapter={adapter}
theme={{
menuStyle: {
"--anyclick-menu-bg": "rgba(0, 0, 0, 0.9)",
"--anyclick-menu-text": "#ffffff",
"--anyclick-menu-border": "rgba(255, 255, 255, 0.1)",
"--anyclick-menu-hover": "rgba(255, 255, 255, 0.1)",
"--anyclick-menu-accent": "#f59e0b",
},
}}
>
{children}
</AnyclickProvider>Available CSS Variables:
| Variable | Default | Description |
| ------------------------------ | --------- | ------------------------ |
| --anyclick-menu-bg | #ffffff | Menu background color |
| --anyclick-menu-hover | #f5f5f5 | Item hover background |
| --anyclick-menu-text | #333333 | Primary text color |
| --anyclick-menu-text-muted | #666666 | Muted text color |
| --anyclick-menu-border | #e5e5e5 | Border color |
| --anyclick-menu-accent | #0066cc | Accent/action color |
| --anyclick-menu-accent-text | #ffffff | Accent text color |
| --anyclick-menu-input-bg | #ffffff | Input background |
| --anyclick-menu-input-border | #dddddd | Input border color |
| --anyclick-menu-cancel-bg | #f0f0f0 | Cancel button background |
| --anyclick-menu-cancel-text | #666666 | Cancel button text |
Performance Considerations
The package is optimized for performance:
- Components use
React.memoto prevent unnecessary re-renders - Expensive computations are wrapped in
useMemo - Event handlers use
useCallbackfor stable references - Screenshot capture is lazy-loaded and async
- Touch event handling uses passive listeners where possible
Exported Utilities
// Components
export { AnyclickProvider, ContextMenu, ScreenshotPreview, InspectDialog };
// Hooks
export { useAnyclick, useProviderStore, useInspectStore };
// Presets
export { createPresetMenu, listPresets, presetDefaults };
// Utilities
export { filterMenuItemsByRole, getBadgeStyle };
export {
findContainerParent,
highlightTarget,
highlightContainer,
clearHighlights,
applyHighlights,
};
// Styles
export { menuStyles, darkMenuStyles, menuCSSVariables };
// IDE Integration
export { openInIDE, buildIDEUrl, detectPreferredIDE };Documentation
For full documentation, visit anyclick.dev/docs/react
Related Packages
@ewjdev/anyclick-core- Core library@ewjdev/anyclick-github- GitHub Issues integration@ewjdev/anyclick-cursor- Cursor AI integration@ewjdev/anyclick-pointer- Custom pointer effects
License
MIT © anyclick
