generated from coulomb/repo-seed
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>
136 lines
5.0 KiB
HTML
136 lines
5.0 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>IHF React Adapter — Test Fixture</title>
|
||
<style>
|
||
body { font-family: sans-serif; padding: 24px; background: #f9fafb; }
|
||
h1 { font-size: 1.4rem; margin-bottom: 4px; }
|
||
p { color: #6b7280; font-size: 0.9rem; margin-bottom: 24px; }
|
||
.card {
|
||
background: #fff; border: 1px solid #e5e7eb; border-radius: 8px;
|
||
padding: 16px; margin-bottom: 16px; max-width: 480px;
|
||
}
|
||
.card h2 { font-size: 1rem; margin: 0 0 8px; }
|
||
.label { font-size: 0.7rem; color: #9ca3af; margin-bottom: 4px; }
|
||
button {
|
||
background: #4f46e5; color: #fff; border: none; border-radius: 4px;
|
||
padding: 6px 14px; font-size: 0.85rem; cursor: pointer;
|
||
}
|
||
button:hover { background: #4338ca; }
|
||
pre { background: #f3f4f6; border-radius: 4px; padding: 8px; font-size: 0.75rem; overflow: auto; }
|
||
.badge {
|
||
display: inline-block; font-size: 0.65rem; font-weight: 600;
|
||
padding: 2px 6px; border-radius: 4px; margin-left: 6px;
|
||
}
|
||
.badge-react { background: #dbeafe; color: #1d4ed8; }
|
||
.badge-native { background: #dcfce7; color: #15803d; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<h1>IHF React Adapter — Test Fixture</h1>
|
||
<p>
|
||
Demonstrates a React-backed widget alongside a native IHP widget on the same page.
|
||
Both carry <code>data-widget-id</code> attributes — the annotation launcher
|
||
and reporting contract work identically for both.
|
||
</p>
|
||
|
||
<!-- ============================================================
|
||
NATIVE IHP WIDGET (simulated — normally rendered by IHP HSX)
|
||
============================================================ -->
|
||
<div class="card"
|
||
data-widget-id="00000000-0000-0000-0000-000000000001"
|
||
data-hub-id="00000000-0000-0000-0000-000000000099"
|
||
data-view-context="test-fixture"
|
||
data-policy-scope="internal"
|
||
data-widget-version="1">
|
||
<div class="label">Native IHP widget <span class="badge badge-native">IHP</span></div>
|
||
<h2>System Health Panel</h2>
|
||
<p style="font-size:0.85rem;color:#374151;margin:0">
|
||
This div was emitted by <code>widgetEnvelope</code> in Haskell HSX.
|
||
All <code>data-*</code> attributes are present per EnvelopeEmissionContract v1.0.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ============================================================
|
||
REACT WIDGET — mounted by the script below
|
||
============================================================ -->
|
||
<div id="react-widget-root"></div>
|
||
|
||
<!-- ============================================================
|
||
EVENT LOG
|
||
============================================================ -->
|
||
<div class="card" style="max-width:480px">
|
||
<h2>Interaction Event Log</h2>
|
||
<pre id="event-log">No events yet.</pre>
|
||
</div>
|
||
|
||
<!--
|
||
In a real integration React would come from your bundle.
|
||
Here we load it from a CDN for the standalone test.
|
||
-->
|
||
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||
|
||
<script type="module">
|
||
import { useWidgetEnvelope, useInteractionReporter } from '/js/ihf-react-adapter.js';
|
||
|
||
const { createElement: h, useState } = React;
|
||
|
||
const WIDGET_ID = '00000000-0000-0000-0000-000000000002';
|
||
const HUB_ID = '00000000-0000-0000-0000-000000000099';
|
||
const API_KEY = 'test-key'; // replace with real hub api_key in integration
|
||
|
||
function log(msg) {
|
||
var el = document.getElementById('event-log');
|
||
var ts = new Date().toISOString().substr(11, 12);
|
||
el.textContent = '[' + ts + '] ' + msg + '\n' + el.textContent;
|
||
}
|
||
|
||
function ReactMetricsWidget() {
|
||
const [count, setCount] = useState(0);
|
||
const { envelopeProps } = useWidgetEnvelope(WIDGET_ID, HUB_ID, 'test-fixture');
|
||
const { reportEvent } = useInteractionReporter(WIDGET_ID, HUB_ID, API_KEY);
|
||
|
||
async function handleClick() {
|
||
setCount(c => c + 1);
|
||
try {
|
||
await reportEvent('clicked');
|
||
log('reportEvent("clicked") → success');
|
||
} catch (e) {
|
||
log('reportEvent("clicked") → ' + e.message);
|
||
}
|
||
}
|
||
|
||
return h('div', Object.assign({ className: 'card' }, envelopeProps),
|
||
h('div', { className: 'label' },
|
||
'React widget',
|
||
h('span', { className: 'badge badge-react' }, 'React'),
|
||
),
|
||
h('h2', null, 'Live Metrics Panel'),
|
||
h('p', { style: { fontSize: '0.85rem', color: '#374151', margin: '0 0 12px' } },
|
||
'This component uses useWidgetEnvelope + useInteractionReporter.',
|
||
h('br', null),
|
||
'Button clicks are 14reported to /api/v1/interaction-events.'
|
||
),
|
||
h('button', { onClick: handleClick }, 'Click me (clicked × ' + count + ')'),
|
||
h('pre', { style: { marginTop: '10px' } },
|
||
JSON.stringify(envelopeProps, null, 2)
|
||
),
|
||
);
|
||
}
|
||
|
||
ReactDOM.createRoot(document.getElementById('react-widget-root'))
|
||
.render(h(ReactMetricsWidget));
|
||
|
||
log('React widget mounted — widget_id=' + WIDGET_ID);
|
||
</script>
|
||
|
||
<!-- Annotation launcher — normally injected by IHP when IHP_ANNOTATION_LAUNCHER=true -->
|
||
<script src="/js/ihf-annotation-launcher.js"></script>
|
||
|
||
</body>
|
||
</html>
|