ihf-react-adapter.js: useWidgetEnvelope (EnvelopeEmissionContract v1.0 props), withWidgetEnvelope HOC, useInteractionReporter (POSTs to reporting contract endpoint). Plain ESM module — no IHP build toolchain required. docs/react-adapter.md with usage examples. static/ihf-react-test.html: React widget + native IHP widget on same page demonstrating annotation launcher pickup and reportEvent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.7 KiB
IHF React Adapter
static/js/ihf-react-adapter.js provides three exports that let React
components participate in the IHF widget protocol:
| Export | What it does |
|---|---|
useWidgetEnvelope |
Hook — returns data-* props for the envelope |
withWidgetEnvelope |
HOC — wraps a component with the envelope div |
useInteractionReporter |
Hook — returns reportEvent(type) for API submission |
The adapter conforms to:
- EnvelopeEmissionContract v1.0 — emits
data-widget-id,data-view-context,data-hub-id - InteractionReportingContract v1.0 — POSTs to
/api/v1/interaction-eventswith Bearer auth
No build toolchain is added to the IHP project. Consumers bundle this file
themselves (webpack, vite, esbuild, etc.) or load it via <script type="module">.
useWidgetEnvelope
import { useWidgetEnvelope } from '/js/ihf-react-adapter.js';
function MetricsPanel({ widgetId, hubId }) {
const { ref, envelopeProps } = useWidgetEnvelope(
widgetId,
hubId,
'ops-dashboard', // viewContext
{ policyScope: 'internal' } // optional
);
return (
<div ref={ref} {...envelopeProps} className="p-4 border rounded">
<h2>Metrics</h2>
{/* content */}
</div>
);
}
The envelopeProps object contains all required data-* attributes.
The ref allows the annotation launcher to find the element.
withWidgetEnvelope
import { withWidgetEnvelope } from '/js/ihf-react-adapter.js';
// Wrap at module definition time, supplying stable IDs:
const GovernedMetricsPanel = withWidgetEnvelope(
MetricsPanel,
'a3f1c2b4-...', // widgetId (UUID from widget registry)
'b9e2d3a1-...', // hubId
'ops-dashboard' // viewContext
);
// Use anywhere:
function Dashboard() {
return <GovernedMetricsPanel title="CPU Usage" />;
}
The HOC wraps the component in an envelope <div> carrying the data-*
attributes. The inner component receives its props unchanged.
useInteractionReporter
import { useInteractionReporter } from '/js/ihf-react-adapter.js';
function ActionButton({ widgetId, hubId, apiKey }) {
const { reportEvent } = useInteractionReporter(widgetId, hubId, apiKey);
async function handleClick() {
await reportEvent('clicked');
// proceed with action
}
return <button onClick={handleClick}>Run Report</button>;
}
Accepted event types (InteractionReportingContract v1.0):
clicked, viewed, submitted, dismissed, errored
The apiKey is the hub's bearer token (hubs.api_key). Retrieve it from
your app's server-side config — do not embed it in public bundle output.
Combined example
import {
useWidgetEnvelope,
useInteractionReporter,
} from '/js/ihf-react-adapter.js';
function GovernedButton({ widgetId, hubId, apiKey, label, onAction }) {
const { envelopeProps } = useWidgetEnvelope(widgetId, hubId, 'action-bar');
const { reportEvent } = useInteractionReporter(widgetId, hubId, apiKey);
async function handleClick() {
await reportEvent('clicked');
onAction();
}
return (
<div {...envelopeProps}>
<button onClick={handleClick}>{label}</button>
</div>
);
}
The annotation launcher (ihf-annotation-launcher.js) will automatically
detect the data-widget-id attribute and inject an Annotate trigger alongside
this component if IHP_ANNOTATION_LAUNCHER=true.
Test fixture
static/ihf-react-test.html demonstrates a React widget alongside an
IHP-rendered widget on the same page. Open it directly in the browser
(no server needed for the React side).