@angelerator/uuics-react
Version:
Universal UI Context System - React hooks and components
627 lines (476 loc) • 13 kB
Markdown
React integration for Universal UI Context System (UUICS).
## Features
- **React Hooks**: `useUICS`, `useUIElement`, `useUIElements`
- **Context Provider**: `UUICSProvider` for app-wide access
- **Debug Panel**: Visual component for debugging context
- **TypeScript**: Full type safety with TypeScript
## Installation
```bash
# npm
npm install @angelerator/uuics-core @angelerator/uuics-react
# pnpm
pnpm add @angelerator/uuics-core @angelerator/uuics-react
# yarn
yarn add @angelerator/uuics-core @angelerator/uuics-react
```
**NPM Packages**:
- [@angelerator/uuics-core](https://www.npmjs.com/package/@angelerator/uuics-core)
- [@angelerator/uuics-react](https://www.npmjs.com/package/@angelerator/uuics-react)
## Basic Usage
### Provider Setup
Wrap your app with `UUICSProvider`:
```tsx
import { UUICSProvider } from '@angelerator/uuics-react';
function App() {
return (
<UUICSProvider
config={{
scan: { interval: 2000 },
track: { mutations: true },
}}
>
<YourApp />
</UUICSProvider>
);
}
```
Main hook for accessing UUICS functionality:
```tsx
import { useUICS } from '@angelerator/uuics-react';
function MyComponent() {
const { context, execute, serialize, scan } = useUICS();
const handleAction = async () => {
await execute({
action: 'click',
target: '#submit-btn',
});
};
return (
<div>
<p>Elements: {context?.elements.length}</p>
<button onClick={handleAction}>Execute Action</button>
</div>
);
}
```
Hook to track a specific element:
```tsx
import { useUIElement } from '@angelerator/uuics-react';
function ElementTracker() {
const submitButton = useUIElement('#submit-btn');
return (
<div>
{submitButton ? (
<>
<p>Button found: {submitButton.label}</p>
<p>Enabled: {submitButton.enabled ? 'Yes' : 'No'}</p>
</>
) : (
<p>Button not found</p>
)}
</div>
);
}
```
Hook to find elements by type:
```tsx
import { useUIElements } from '@angelerator/uuics-react';
function ButtonList() {
const buttons = useUIElements('button');
return (
<ul>
{buttons.map((btn) => (
<li key={btn.id}>{btn.label}</li>
))}
</ul>
);
}
```
Visual component for debugging UUICS context:
```tsx
import { DebugPanel } from '@angelerator/uuics-react';
function App() {
return (
<UUICSProvider>
<YourApp />
<DebugPanel format="natural" />
</UUICSProvider>
);
}
```
```tsx
interface UUICSProviderProps {
children: ReactNode;
config?: UUICSConfig;
}
```
Provides UUICS context to child components.
```typescript
function useUICS(): {
context: PageContext | null;
isInitialized: boolean;
execute: (command: ActionCommand) => Promise<ActionResult>;
executeBatch: (commands: ActionCommand[]) => Promise<ActionResult[]>;
serialize: (format?: 'json' | 'natural' | 'openapi') => string;
scan: () => Promise<PageContext | null>;
engine: UUICSEngine | null;
}
```
Main hook for UUICS functionality.
```typescript
function useUUICSContext(): ReturnType<typeof useUICS>
```
Access UUICS context from anywhere in the tree. Must be used within `UUICSProvider`.
### useUIElement
```typescript
function useUIElement(selector: string): UIElement | null
```
Find and track a specific element by selector.
### useUIElements
```typescript
function useUIElements(type: string): UIElement[]
```
Find all elements of a specific type.
### DebugPanel
```tsx
interface DebugPanelProps {
format?: 'json' | 'natural' | 'openapi';
className?: string;
style?: React.CSSProperties;
}
```
Visual debug panel component.
```tsx
import { useUICS } from '@angelerator/uuics-react';
import { ClaudeAdapter } from '@angelerator/uuics-models-claude';
function AIAssistant() {
const { context, execute, serialize } = useUICS();
const [prompt, setPrompt] = useState('');
const [response, setResponse] = useState('');
const adapter = new ClaudeAdapter({ apiKey: 'your-api-key' });
const handleSubmit = async () => {
if (!context) return;
// Serialize context for AI
const contextString = serialize('natural');
// Send to Claude
const aiResponse = await adapter.chat(prompt, context, contextString);
setResponse(aiResponse);
// Parse and execute action
const command = adapter.parseResponse(aiResponse);
if (command) {
await execute(command);
}
};
return (
<div>
<textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} />
<button onClick={handleSubmit}>Send to AI</button>
{response && <pre>{response}</pre>}
</div>
);
}
```
```tsx
import { useUICS } from '@angelerator/uuics-react';
function AutoFillButton() {
const { execute, context } = useUICS();
const autoFill = async () => {
const formData = {
name: 'John Doe',
email: 'john@example.com',
role: 'developer',
};
const commands = Object.entries(formData).map(([field, value]) => ({
action: 'setValue' as const,
target: `
parameters: { value },
}));
await executeBatch(commands);
};
return <button onClick={autoFill}>Auto-fill Form</button>;
}
```
The Debug Panel comes with default styles. You can customize it:
```css
.uuics-debug-panel {
background: white;
border: 1px solid
border-radius: 8px;
padding: 20px;
}
.uuics-debug-content {
max-height: 600px;
overflow: auto;
background:
padding: 15px;
border-radius: 4px;
}
```
Or pass custom styles:
```tsx
<DebugPanel
style={{
position: 'fixed',
bottom: 20,
right: 20,
width: 400,
maxHeight: 500,
}}
/>
```
1. **Provider Placement**: Place `UUICSProvider` as high as needed but not higher
2. **Memoization**: UUICS hooks use `useMemo` for optimal re-renders
3. **Subscription**: Hooks automatically cleanup on unmount
4. **Element Finding**: `useUIElement` and `useUIElements` are memoized
```tsx
function ConditionalWorkflow() {
const { execute, context } = useUICS();
const runWorkflow = async () => {
// Step 1: Click menu
const result1 = await execute({ action: 'click', target: '#menu' });
if (result1.success) {
// Step 2: Only if step 1 succeeded
const result2 = await execute({ action: 'click', target: '#submenu' });
if (result2.success) {
// Step 3: Only if step 2 succeeded
await execute({
action: 'setValue',
target: '#search',
parameters: { value: 'query' }
});
}
}
};
return <button onClick={runWorkflow}>Run Workflow</button>;
}
```
```tsx
function StateTrackingExample() {
const { engine } = useUICS();
const [userState, setUserState] = useState({ name: '', role: '' });
useEffect(() => {
if (engine) {
// Track state with proxy
const tracked = engine.trackState('user', userState);
// Register computed state
engine.registerState('appInfo', () => ({
timestamp: Date.now(),
pageTitle: document.title
}));
return () => {
engine.untrackState('user');
engine.unregisterState('appInfo');
};
}
}, [engine]);
return <div>State tracking enabled</div>;
}
```
```tsx
function CustomScanComponent() {
const { engine } = useUICS();
const scanFormOnly = async () => {
if (engine) {
const context = await engine.scan(undefined, {
rootSelectors: ['#my-form'],
excludeSelectors: ['.help-text']
});
console.log('Form elements:', context.elements);
}
};
return <button onClick={scanFormOnly}>Scan Form</button>;
}
```
```tsx
import { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
class UUICSErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('UUICS Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong with UUICS</h2>
<pre>{this.state.error?.message}</pre>
</div>
);
}
return this.props.children;
}
}
// Usage
function App() {
return (
<UUICSErrorBoundary>
<UUICSProvider>
<YourApp />
</UUICSProvider>
</UUICSErrorBoundary>
);
}
```
Full TypeScript support with type inference:
```tsx
import type { PageContext, UIElement, ActionCommand } from '@angelerator/uuics-core';
import { useUICS } from '@angelerator/uuics-react';
function TypedComponent() {
const { context, execute } = useUICS();
// Type-safe action execution
const command: ActionCommand = {
action: 'click',
target: '#btn'
};
// Type-safe element access
const button: UIElement | undefined = context?.elements.find(
el => el.type === 'button'
);
return <div>{button?.label}</div>;
}
```
UUICS is browser-only. For Next.js or SSR frameworks:
```tsx
'use client'; // Next.js 13+ App Router
import dynamic from 'next/dynamic';
// Dynamic import with no SSR
const UUICSProvider = dynamic(
() => import('@angelerator/uuics-react').then(mod => mod.UUICSProvider),
{ ssr: false }
);
export default function App() {
return (
<UUICSProvider>
<YourApp />
</UUICSProvider>
);
}
```
```tsx
import { render, screen, waitFor } from '@testing-library/react';
import { UUICSProvider, useUICS } from '@angelerator/uuics-react';
import { describe, it, expect } from 'vitest';
function TestComponent() {
const { context, execute } = useUICS();
return (
<div>
<span data-testid="count">{context?.elements.length || 0}</span>
<button
data-testid="action-btn"
onClick={() => execute({ action: 'click', target: '#test' })}
>
Execute
</button>
</div>
);
}
describe('UUICS React Integration', () => {
it('provides context to components', async () => {
render(
<UUICSProvider config={{ scan: { interval: 0 } }}>
<TestComponent />
</UUICSProvider>
);
await waitFor(() => {
const count = screen.getByTestId('count');
expect(count.textContent).not.toBe('0');
});
});
});
```
```tsx
// ❌ BAD - Hook called before provider initialized
function MyComponent() {
const { context } = useUICS();
console.log(context.elements); // Error: context is null
}
// ✅ GOOD - Check for null
function MyComponent() {
const { context } = useUICS();
if (!context) return <div>Loading...</div>;
return <div>{context.elements.length} elements</div>;
}
```
```tsx
// ✅ Hooks auto-cleanup, but manual subscriptions need cleanup
useEffect(() => {
const unsubscribe = engine?.subscribe((context) => {
console.log('Updated:', context);
});
return unsubscribe; // Cleanup on unmount
}, [engine]);
```
```tsx
// Reduce re-renders by selecting only what you need
function OptimizedComponent() {
const { context } = useUICS();
// Memoize expensive computations
const buttonCount = useMemo(
() => context?.elements.filter(el => el.type === 'button').length || 0,
[]
);
return <div>{buttonCount} buttons</div>;
}
```
- React 18+ (for useId, automatic batching)
- Modern browsers (same as @angelerator/uuics-core)
- No IE11 support
## Migration from v0.x
```tsx
// v0.x
import { UUICSContext } from '@angelerator/uuics-react';
const context = useContext(UUICSContext);
// v1.x
import { useUICS } from '@angelerator/uuics-react';
const { context } = useUICS(); // More features, better DX
```
- [@angelerator/uuics-core](../core/README.md) - Core engine
- [@angelerator/uuics-models-claude](../models-claude/README.md) - Claude adapter
- [@angelerator/uuics-models-openai](../models-openai/README.md) - OpenAI adapter
See main [README](../../README.md
MIT - see [LICENSE](../../LICENSE) file for details.
- Issues: [GitHub Issues](https://github.com/Angelerator/uuics/issues)
- Examples: [examples/react-app/](../../examples/react-app/)
- Main Docs: [README](../../README.md)