UNPKG

@unblessed/react

Version:

React renderer for unblessed with flexbox layout support

610 lines (595 loc) 16.2 kB
import * as react from 'react'; import { ReactNode } from 'react'; import { FlexboxProps } from '@unblessed/layout'; import { MouseEvent, KeyEvent, Runtime, Screen } from '@unblessed/core'; import * as react_jsx_runtime from 'react/jsx-runtime'; /** * types.ts - Type definitions for @unblessed/react */ /** * Options for the render() function */ interface RenderOptions { /** * Runtime instance * Create this from @unblessed/node or @unblessed/browser */ runtime: Runtime; /** * Screen instance (optional) * If not provided, render() will create a default Screen. * If provided, you are responsible for calling screen.destroy(). */ screen?: Screen; /** * Debug mode - logs render cycles */ debug?: boolean; } /** * Instance returned by render() */ interface RenderInstance { /** * The screen instance. */ screen: Screen; /** * Unmount the React tree and clean up */ unmount: () => void; /** * Re-render with new element */ rerender: (element: ReactNode) => void; /** * Wait for exit (Promise that resolves when unmounted) */ waitUntilExit: () => Promise<void>; } /** * Event handler map for wiring React props to unblessed widget events */ interface EventHandlers { click?: (data: MouseEvent) => void; mousedown?: (data: MouseEvent) => void; mouseup?: (data: MouseEvent) => void; mousemove?: (data: MouseEvent) => void; mouseover?: (data: MouseEvent) => void; mouseout?: (data: MouseEvent) => void; mousewheel?: (data: MouseEvent) => void; wheeldown?: (data: MouseEvent) => void; wheelup?: (data: MouseEvent) => void; keypress?: (ch: string, key: KeyEvent) => void; focus?: () => void; blur?: () => void; press?: () => void; submit?: (value?: string) => void; cancel?: () => void; action?: () => void; } /** * React event props that map to unblessed widget events */ interface ReactEventProps { onClick?: (data: MouseEvent) => void; onMouseDown?: (data: MouseEvent) => void; onMouseUp?: (data: MouseEvent) => void; onMouseMove?: (data: MouseEvent) => void; onMouseOver?: (data: MouseEvent) => void; onMouseOut?: (data: MouseEvent) => void; onMouseWheel?: (data: MouseEvent) => void; onWheelDown?: (data: MouseEvent) => void; onWheelUp?: (data: MouseEvent) => void; onKeyPress?: (ch: string, key: KeyEvent) => void; onFocus?: () => void; onBlur?: () => void; onPress?: () => void; onSubmit?: (value?: string) => void; onCancel?: () => void; onAction?: () => void; } /** * common-props.ts - Shared prop interfaces for widget inheritance * * This module defines common prop interfaces that are composed/extended * by specific widget descriptors, avoiding duplication. */ /** * Props for widgets that can receive focus */ interface FocusableProps { /** * Tab index for focus navigation (0 = can be focused, -1 = skip) * @default 0 for interactive widgets */ tabIndex?: number; } /** * Props for widgets with borders */ interface BorderProps { borderStyle?: "single" | "double" | "round" | "bold" | "classic"; borderColor?: string; borderDimColor?: boolean; borderTopColor?: string; borderBottomColor?: string; borderLeftColor?: string; borderRightColor?: string; borderTopDim?: boolean; borderBottomDim?: boolean; borderLeftDim?: boolean; borderRightDim?: boolean; } /** * Props for interactive widgets (buttons, inputs, etc.) * Combines layout, events, focus behavior, and styling * Default state style properties are direct props (inherited from StyleObject) * State variations use nested objects (hover, focus) */ interface InteractiveWidgetProps extends FlexboxProps, ReactEventProps, FocusableProps, BorderProps, Omit<StyleObject, "border"> { hover?: StyleObject; focus?: StyleObject; } /** * Border state styling object * Used for styling borders in different states (hover, focus, etc.) */ interface BorderStyleObject { color?: string; backgroundColor?: string; fg?: string; bg?: string; background?: string; topColor?: string; bottomColor?: string; leftColor?: string; rightColor?: string; dim?: boolean; topDim?: boolean; bottomDim?: boolean; leftDim?: boolean; rightDim?: boolean; } /** * Complete style object for widget state styling * Supports both primary names and shorthands (like CSS) */ interface StyleObject { color?: string; backgroundColor?: string; fg?: string; bg?: string; background?: string; bold?: boolean; italic?: boolean; underline?: boolean; strikethrough?: boolean; reverse?: boolean; dim?: boolean; blink?: boolean; hide?: boolean; border?: BorderStyleObject; } /** * Props interface for Box component * Combines flexbox layout props with box-specific visual properties * * Inherits all interactive widget properties including: * - Layout (flexbox, width, height, padding, margin, etc.) * - Events (onClick, onKeyPress, onFocus, etc.) * - Styling (color, bg, bold, etc.) * - State styling (hover, focus) * - Borders (borderStyle, borderColor, etc.) */ interface BoxProps extends InteractiveWidgetProps { tags?: boolean; content?: string; children?: ReactNode; } /** * Box component - Container with flexbox layout support * * Supports flexbox properties, borders, colors, event handling, and state styling. * Default state styling uses direct props (color, bg, bold, etc.) * State variations use nested objects (hover, focus) * * @example Basic layout * ```tsx * <Box * flexDirection="row" * gap={2} * padding={1} * border={1} * borderStyle="single" * borderColor="cyan" * > * <Box width={20}>Left</Box> * <Box flexGrow={1}>Middle</Box> * <Box width={20}>Right</Box> * </Box> * ``` * * @example With styling and state effects * ```tsx * <Box * padding={1} * border={1} * borderStyle="single" * borderColor="white" * color="gray" * hover={{ border: { color: "cyan" }, color: "white" }} * focus={{ border: { color: "yellow" } }} * > * Interactive Box * </Box> * ``` * * @example With event handling * ```tsx * <Box * padding={1} * border={1} * borderStyle="single" * tabIndex={0} * onClick={(data) => console.log('Clicked!', data)} * onKeyPress={(ch, key) => { * if (key.name === 'enter') handleSubmit(); * }} * > * Click or press Enter * </Box> * ``` */ declare const Box: react.ForwardRefExoticComponent<BoxProps & { children?: ReactNode | undefined; } & react.RefAttributes<any>>; /** * Props interface for Button component * Inherits all interactive widget properties (layout, events, focus, borders, styling) */ interface ButtonProps extends InteractiveWidgetProps { content?: string; children?: ReactNode; } /** * Button component - Interactive button with hover and focus effects * * Supports mouse clicks, keyboard press (Enter), and visual state changes. * Automatically receives focus when tabbed to. * * Default state styling uses direct props (color, bg, bold, etc.) * State variations use nested objects (hover, focus) * * @example Basic button * ```tsx * <Button * borderStyle="single" * borderColor="green" * padding={1} * onClick={() => console.log('Clicked!')} * > * Click Me * </Button> * ``` * * @example With hover and focus effects * ```tsx * <Button * borderStyle="single" * borderColor="blue" * color="white" * bg="blue" * bold={true} * hover={{ bg: "darkblue" }} * focus={{ border: { color: "cyan" } }} * padding={1} * onPress={() => handleSubmit()} * > * Submit * </Button> * ``` * * @example Interactive counter * ```tsx * const [count, setCount] = useState(0); * * <Button onClick={() => setCount(c => c + 1)}> * Count: {count} * </Button> * ``` */ declare const Button: react.ForwardRefExoticComponent<ButtonProps & { children?: ReactNode | undefined; } & react.RefAttributes<any>>; /** * Props interface for Input component * Inherits all interactive widget properties (layout, events, focus, borders) */ interface InputProps extends InteractiveWidgetProps { /** Controlled value - when provided, input becomes controlled */ value?: string; /** Default value for uncontrolled mode */ defaultValue?: string; } /** * Input component - Text input field for user interaction * * Provides a single-line text input with submit/cancel events. * Users can type text and submit with Enter or cancel with Escape. * Automatically receives focus when tabbed to (tabIndex=0 by default). * * Supports both controlled and uncontrolled modes: * - Controlled: Provide `value` prop and update it via events * - Uncontrolled: Use `defaultValue` for initial value, or omit for empty input * * Default state styling uses direct props (color, bg, bold, etc.) * State variations use nested objects (hover, focus) * * @example Uncontrolled input (with default value) * ```tsx * <Input * width={30} * defaultValue="Initial text" * color="white" * focus={{ border: { color: "cyan" } }} * onSubmit={(value) => console.log('Submitted:', value)} * onCancel={() => console.log('Cancelled')} * /> * ``` * * @example Controlled input * ```tsx * const [text, setText] = useState(''); * <Input * width={40} * value={text} * onChange={(newValue) => setText(newValue)} * onSubmit={(value) => handleSubmit(value)} * /> * ``` * * @example With auto-focus and styling * ```tsx * <Input * width={40} * defaultValue="Focus me!" * color="white" * bg="blue" * focus={{ border: { color: "cyan" }, bg: "darkblue" }} * onSubmit={(value) => handleSubmit(value)} * /> * ``` */ declare const Input: react.ForwardRefExoticComponent<InputProps & react.RefAttributes<any>>; /** * Props interface for List component * Provides a simplified, React-friendly API with sensible defaults * * Styling hierarchy: * - style, hover, focus: Affect the List container (borders, background) * - itemStyle, itemSelected, itemHover: Affect individual list items */ interface ListProps extends InteractiveWidgetProps { items: string[]; children?: ReactNode; label: string; onSelect?: (item: string, index: number) => void; onCancel?: () => void; defaultSelected?: number; disabled?: boolean; vi?: boolean; itemStyle?: StyleObject; itemSelected?: StyleObject; itemHover?: StyleObject; scrollbar?: boolean; scrollbarBg?: string; scrollbarFg?: string; scrollbarChar?: string; scrollbarTrack?: boolean; scrollbarTrackBg?: string; scrollbarTrackFg?: string; scrollbarTrackChar?: string; } /** * List component - Scrollable, selectable list with keyboard and mouse support * * Provides a simplified API with better defaults than raw unblessed List. * Mouse, keyboard, and tags are always enabled. * * Styling hierarchy: * - style, hover, focus: Affect the List container (borders, background) * - itemStyle, itemSelected, itemHover: Affect individual list items * * @example Basic list * ```tsx * <List * items={['Apple', 'Banana', 'Cherry']} * label="Choose a fruit" * width={40} * height={10} * border={1} * borderStyle="single" * itemSelected={{ bg: "blue", color: "white" }} * onSelect={(item, index) => console.log('Selected:', item)} * /> * ``` * * @example With list container and item styling * ```tsx * <List * items={items} * label="Select an option" * border={1} * borderStyle="single" * borderColor="white" * hover={{ border: { color: "cyan" } }} // List border on hover * focus={{ border: { color: "yellow" } }} // List border on focus * itemStyle={{ color: "gray" }} // Normal items * itemSelected={{ bg: "white", color: "black", bold: true }} // Selected item * itemHover={{ bg: "darkgray" }} // Hovered item * /> * ``` * * @example Disabled list (read-only) * ```tsx * <List * items={items} * label="Read only" * disabled={true} * itemStyle={{ color: "gray" }} * /> * ``` */ declare const List: react.ForwardRefExoticComponent<ListProps & react.RefAttributes<any>>; /** * Props interface for Spacer component */ interface SpacerProps extends FlexboxProps { } /** * Spacer component - Flexible space that expands to fill available space * * This is a convenience component that renders a Box with flexGrow={1}. * It's useful for creating space-between layouts. * * @example * ```tsx * <Box flexDirection="row"> * <Box width={20}>Left</Box> * <Spacer /> * <Box width={20}>Right</Box> * </Box> * ``` * * @example With custom flexGrow * ```tsx * <Box flexDirection="row"> * <Spacer flexGrow={2} /> * <Box width={30}>Content</Box> * <Spacer flexGrow={1} /> * </Box> * ``` */ declare const Spacer: { (props: SpacerProps): react_jsx_runtime.JSX.Element; displayName: string; }; /** * Props interface for Text component */ interface TextProps extends StyleObject { content?: string; children?: ReactNode; } /** * Text component - Renders text with styling * * @example * ```tsx * <Text color="green" bold> * Hello World! * </Text> * ``` * * @example With nested text * ```tsx * <Text> * Hello <Text color="red">World</Text>! * </Text> * ``` */ declare const Text: react.ForwardRefExoticComponent<TextProps & { children?: ReactNode | undefined; } & react.RefAttributes<any>>; /** * Props interface for BigText component */ interface BigTextProps extends FlexboxProps, Pick<StyleObject, "color" | "backgroundColor"> { font?: string; fontBold?: boolean; char?: string; content?: string; children?: string; } /** * BigText component - Renders large ASCII art text * * Uses terminal fonts to render large text. Each character is 14 rows × 8 columns. * Supports all BoxProps including flexbox layout, borders, and event handling. * * @example * ```tsx * <BigText color="cyan"> * HELLO * </BigText> * ``` * * @example With border and events * ```tsx * <BigText * color="green" * borderStyle="single" * padding={1} * onClick={() => console.log('Big text clicked!')} * > * WELCOME * </BigText> * ``` */ declare const BigText: react.ForwardRefExoticComponent<BigTextProps & { children?: react.ReactNode | undefined; } & react.RefAttributes<any>>; /** * render.ts - Main render function for @unblessed/react * * This module provides the main `render()` function that users call to * mount React components to an unblessed Screen. */ /** * Render a React element to an unblessed Screen * * @example * Basic usage (render() creates screen automatically): * ```tsx * import { NodeRuntime } from '@unblessed/node'; * import { render, Box, Text } from '@unblessed/react'; * * const App = () => ( * <Box flexDirection="row" gap={2}> * <Box width={20}>Left</Box> * <Box flexGrow={1}>Middle</Box> * <Box width={20}>Right</Box> * </Box> * ); * * const instance = render(<App />, { runtime: new NodeRuntime() }); * * // Later: * instance.unmount(); // Automatically destroys screen * ``` * * @example * Advanced usage (provide custom screen): * ```tsx * import { Screen, NodeRuntime } from '@unblessed/node'; * import { render, Box, Text } from '@unblessed/react'; * * const screen = new Screen({ * smartCSR: false, * debug: true, * log: './debug.log' * }); * * const instance = render(<App />, { * runtime: new NodeRuntime(), * screen * }); * * // Later: * instance.unmount(); * screen.destroy(); // You must destroy screen manually * ``` */ declare function render(element: ReactNode, options: RenderOptions): RenderInstance; export { BigText, type BigTextProps, Box, type BoxProps, Button, type ButtonProps, type EventHandlers, Input, type InputProps, List, type ListProps, type ReactEventProps, type RenderInstance, type RenderOptions, Spacer, Text, type TextProps, render };