mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
feat: add terminal panel components and Zustand store for state management
- Created a barrel export file for terminal panel components. - Implemented Zustand store for managing terminal panel UI state, including visibility, active terminal, view mode, and terminal ordering. - Added actions for opening/closing the terminal panel, setting the active terminal, changing view modes, and managing terminal order. - Introduced selectors for accessing terminal panel state properties.
This commit is contained in:
@@ -261,7 +261,7 @@ describe('Component Renderer Interface', () => {
|
||||
});
|
||||
|
||||
it('should support async action handlers', async () => {
|
||||
const asyncAction: ActionHandler = async (actionId, params) => {
|
||||
const asyncAction: ActionHandler = async (_actionId, _params) => {
|
||||
await Promise.resolve();
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { z } from 'zod';
|
||||
import { A2UIParser, a2uiParser, A2UIParseError } from '../core/A2UIParser';
|
||||
import type { SurfaceUpdate, A2UIComponent } from '../core/A2UITypes';
|
||||
import { a2uiParser, A2UIParseError } from '../core/A2UIParser';
|
||||
|
||||
// Import component renderers to trigger auto-registration
|
||||
import '../renderer/components';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Tests for all A2UI component renderers
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, cleanup, within } from '@testing-library/react';
|
||||
import { render, screen, cleanup } from '@testing-library/react';
|
||||
import type { A2UIComponent } from '../core/A2UITypes';
|
||||
import type { A2UIState, ActionHandler, BindingResolver } from '../core/A2UIComponentRegistry';
|
||||
import type { TextComponent, ButtonComponent, DropdownComponent, CLIOutputComponent, DateTimeInputComponent } from '../core/A2UITypes';
|
||||
@@ -653,7 +653,7 @@ describe('A2UI Component Integration', () => {
|
||||
});
|
||||
|
||||
it('should handle async action handlers', async () => {
|
||||
const asyncOnAction: ActionHandler = async (actionId, params) => {
|
||||
const asyncOnAction: ActionHandler = async (_actionId, _params) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
};
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
// ========================================
|
||||
// React component that renders A2UI surfaces
|
||||
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import type { SurfaceUpdate, SurfaceComponent, A2UIComponent, LiteralString, Binding } from '../core/A2UITypes';
|
||||
import { useState, useCallback } from 'react';
|
||||
import type { SurfaceUpdate, A2UIComponent, LiteralString, Binding } from '../core/A2UITypes';
|
||||
import { a2uiRegistry, type A2UIState, type ActionHandler, type BindingResolver } from '../core/A2UIComponentRegistry';
|
||||
|
||||
// ========== Renderer Props ==========
|
||||
@@ -26,7 +26,7 @@ interface A2UIRendererProps {
|
||||
*/
|
||||
export function A2UIRenderer({ surface, onAction, className = '' }: A2UIRendererProps) {
|
||||
// Local state initialized with surface's initial state
|
||||
const [localState, setLocalState] = useState<A2UIState>(surface.initialState || {});
|
||||
const [localState] = useState<A2UIState>(surface.initialState || {});
|
||||
|
||||
// Handle action from components
|
||||
const handleAction = useCallback<ActionHandler>(
|
||||
@@ -57,21 +57,6 @@ export function A2UIRenderer({ surface, onAction, className = '' }: A2UIRenderer
|
||||
[localState]
|
||||
);
|
||||
|
||||
// Update state from external source
|
||||
const updateState = useCallback((updates: Partial<A2UIState>) => {
|
||||
setLocalState((prev) => ({ ...prev, ...updates }));
|
||||
}, []);
|
||||
|
||||
// Memoize context for components
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
state: localState,
|
||||
resolveBinding,
|
||||
updateState,
|
||||
}),
|
||||
[localState, resolveBinding, updateState]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`a2ui-surface ${className}`} data-surface-id={surface.surfaceId}>
|
||||
{surface.components.map((comp) => (
|
||||
|
||||
@@ -3,25 +3,16 @@
|
||||
// ========================================
|
||||
// Maps A2UI Button component to shadcn/ui Button
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import type { A2UIState, ActionHandler, BindingResolver } from '../../core/A2UIComponentRegistry';
|
||||
import type { ButtonComponent, A2UIComponent } from '../../core/A2UITypes';
|
||||
import type { ButtonComponent } from '../../core/A2UITypes';
|
||||
import { resolveLiteralOrBinding } from '../A2UIRenderer';
|
||||
|
||||
interface A2UIButtonRendererProps {
|
||||
component: A2UIComponent;
|
||||
state: A2UIState;
|
||||
onAction: ActionHandler;
|
||||
resolveBinding: BindingResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Button Component Renderer
|
||||
* Maps A2UI variants (primary/secondary/destructive) to shadcn/ui variants (default/secondary/destructive/ghost)
|
||||
*/
|
||||
export const A2UIButton: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UIButton: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
|
||||
const buttonComp = component as ButtonComponent;
|
||||
const { Button: buttonConfig } = buttonComp;
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ function StreamingIndicator() {
|
||||
* A2UI CLIOutput Component Renderer
|
||||
* Displays CLI output with optional syntax highlighting and streaming indicator
|
||||
*/
|
||||
export const A2UICLIOutput: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UICLIOutput: ComponentRenderer = ({ component, resolveBinding }) => {
|
||||
const cliOutputComp = component as CLIOutputComponent;
|
||||
const { CLIOutput: config } = cliOutputComp;
|
||||
|
||||
|
||||
@@ -3,24 +3,16 @@
|
||||
// ========================================
|
||||
// Maps A2UI Card component to shadcn/ui Card
|
||||
|
||||
import React from 'react';
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/Card';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveTextContent } from '../A2UIRenderer';
|
||||
import type { CardComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UICardProps {
|
||||
component: CardComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Card Component Renderer
|
||||
* Container component with optional title and description
|
||||
*/
|
||||
export const A2UICard: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UICard: ComponentRenderer = ({ component, resolveBinding }) => {
|
||||
const cardComp = component as CardComponent;
|
||||
const { Card: cardConfig } = cardComp;
|
||||
|
||||
|
||||
@@ -3,25 +3,18 @@
|
||||
// ========================================
|
||||
// Maps A2UI Checkbox component to shadcn/ui Checkbox
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Checkbox } from '@/components/ui/Checkbox';
|
||||
import { Label } from '@/components/ui/Label';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer';
|
||||
import type { CheckboxComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UICheckboxProps {
|
||||
component: CheckboxComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Checkbox Component Renderer
|
||||
* Boolean state binding with onChange handler
|
||||
*/
|
||||
export const A2UICheckbox: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UICheckbox: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
|
||||
const checkboxComp = component as CheckboxComponent;
|
||||
const { Checkbox: checkboxConfig } = checkboxComp;
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
// ========================================
|
||||
// Date/time picker with ISO string format support
|
||||
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer';
|
||||
import { resolveTextContent } from '../A2UIRenderer';
|
||||
import type { DateTimeInputComponent } from '../../core/A2UITypes';
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,7 @@ function isoToDateTimeLocal(isoString: string): string {
|
||||
/**
|
||||
* Convert datetime-local input format to ISO string
|
||||
*/
|
||||
function dateTimeLocalToIso(dateTimeLocal: string, includeTime: boolean): string {
|
||||
function dateTimeLocalToIso(dateTimeLocal: string, _includeTime: boolean): string {
|
||||
if (!dateTimeLocal) return '';
|
||||
|
||||
const date = new Date(dateTimeLocal);
|
||||
@@ -43,7 +43,7 @@ function dateTimeLocalToIso(dateTimeLocal: string, includeTime: boolean): string
|
||||
* A2UI DateTimeInput Component Renderer
|
||||
* Uses native input[type="datetime-local"] or input[type="date"] based on includeTime
|
||||
*/
|
||||
export const A2UIDateTimeInput: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UIDateTimeInput: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
|
||||
const dateTimeComp = component as DateTimeInputComponent;
|
||||
const { DateTimeInput: config } = dateTimeComp;
|
||||
const includeTime = config.includeTime ?? true;
|
||||
|
||||
@@ -3,24 +3,16 @@
|
||||
// ========================================
|
||||
// Maps A2UI Progress component to shadcn/ui Progress
|
||||
|
||||
import React from 'react';
|
||||
import { Progress } from '@/components/ui/Progress';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding } from '../A2UIRenderer';
|
||||
import type { ProgressComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UIProgressProps {
|
||||
component: ProgressComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Progress Component Renderer
|
||||
* For CLI output progress display
|
||||
*/
|
||||
export const A2UIProgress: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UIProgress: ComponentRenderer = ({ component, resolveBinding }) => {
|
||||
const progressComp = component as ProgressComponent;
|
||||
const { Progress: progressConfig } = progressComp;
|
||||
|
||||
|
||||
@@ -4,25 +4,18 @@
|
||||
// Maps A2UI RadioGroup component to shadcn/ui RadioGroup
|
||||
// Used for single-select questions with visible options
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/RadioGroup';
|
||||
import { Label } from '@/components/ui/Label';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer';
|
||||
import type { RadioGroupComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UIRadioGroupProps {
|
||||
component: RadioGroupComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI RadioGroup Component Renderer
|
||||
* Single selection from visible options with onChange handler
|
||||
*/
|
||||
export const A2UIRadioGroup: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UIRadioGroup: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
|
||||
const radioGroupComp = component as RadioGroupComponent;
|
||||
const { RadioGroup: radioConfig } = radioGroupComp;
|
||||
|
||||
|
||||
@@ -6,20 +6,12 @@
|
||||
import React from 'react';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveTextContent } from '../A2UIRenderer';
|
||||
import type { TextComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UITextProps {
|
||||
component: TextComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Text Component Renderer
|
||||
* Maps A2UI Text usageHint to HTML elements (h1, h2, h3, p, span, code)
|
||||
*/
|
||||
export const A2UIText: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UIText: ComponentRenderer = ({ component, resolveBinding }) => {
|
||||
const { Text } = component as { Text: { text: unknown; usageHint?: string } };
|
||||
|
||||
// Resolve text content
|
||||
|
||||
@@ -3,24 +3,17 @@
|
||||
// ========================================
|
||||
// Maps A2UI TextArea component to shadcn/ui Textarea
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Textarea } from '@/components/ui/Textarea';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding } from '../A2UIRenderer';
|
||||
import type { TextAreaComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UITextAreaProps {
|
||||
component: TextAreaComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI TextArea Component Renderer
|
||||
* Two-way binding via onChange updates to local state
|
||||
*/
|
||||
export const A2UITextArea: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UITextArea: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
|
||||
const areaComp = component as TextAreaComponent;
|
||||
const { TextArea: areaConfig } = areaComp;
|
||||
|
||||
|
||||
@@ -3,24 +3,17 @@
|
||||
// ========================================
|
||||
// Maps A2UI TextField component to shadcn/ui Input
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding } from '../A2UIRenderer';
|
||||
import type { TextFieldComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UITextFieldProps {
|
||||
component: TextFieldComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI TextField Component Renderer
|
||||
* Two-way binding via onChange updates to local state
|
||||
*/
|
||||
export const A2UITextField: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
export const A2UITextField: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
|
||||
const fieldComp = component as TextFieldComponent;
|
||||
const { TextField: fieldConfig } = fieldComp;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user