import * as React from "react"; import type { ViewProps, TextProps, PressableProps, ScrollViewProps, ModalProps, StyleProp, ViewStyle, LinkingImpl, TextInputProps, } from "react-native"; import { useTerminalDimensions } from "@opentui/react"; import { BorderSides, RGBA } from "@opentui/core"; import { platform } from "node:os"; import { exec } from "node:child_process"; const RATIO_WIDTH = 8.433; const RATIO_HEIGHT = 17; function attr( style: StyleProp, name: K, type: "string", ): Extract | undefined; function attr( style: StyleProp, name: K, type: "number", ): Extract | undefined; function attr( style: StyleProp, name: K, type: "boolean", ): Extract | undefined; function attr( style: StyleProp, name: K, type: "string" | "number" | "boolean", ) { if (!style) return undefined; const obj: ViewStyle = Array.isArray(style) ? Object.assign({}, ...style.filter(Boolean)) : (style as ViewStyle); const v = obj[name]; return typeof v === type ? v : undefined; } export function View({ children, style }: ViewProps) { const bg = style && "backgroundColor" in style ? typeof style.backgroundColor == "string" ? style.backgroundColor.startsWith("rgba(") ? (() => { const parts = style.backgroundColor.split("(")[1].split(")")[0]; const [r, g, b, a] = parts.split(",").map(parseFloat); return RGBA.fromInts(r, g, b, a * 255); })() : style.backgroundColor : undefined : undefined; const padding = attr(style, "padding", "number"); const paddingTop = attr(style, "paddingTop", "number"); const paddingLeft = attr(style, "paddingLeft", "number"); const paddingBottom = attr(style, "paddingBottom", "number"); const paddingRight = attr(style, "paddingRight", "number"); const gap = attr(style, "gap", "number"); const borderBottomWidth = attr(style, "borderBottomWidth", "number"); const borderTopWidth = attr(style, "borderTopWidth", "number"); const borderLeftWidth = attr(style, "borderLeftWidth", "number"); const borderRightWidth = attr(style, "borderRightWidth", "number"); const borderBottomColor = attr(style, "borderBottomColor", "string"); const borderTopColor = attr(style, "borderTopColor", "string"); const borderLeftColor = attr(style, "borderLeftColor", "string"); const borderRightColor = attr(style, "borderRightColor", "string"); const borderColor = attr(style, "borderColor", "string"); const top = attr(style, "top", "number"); const width = attr(style, "width", "number"); const props = { overflow: attr(style, "overflow", "string"), position: attr(style, "position", "string"), alignSelf: attr(style, "alignSelf", "string"), alignItems: attr(style, "alignItems", "string"), justifyContent: attr(style, "justifyContent", "string"), flexShrink: attr(style, "flexShrink", "number"), flexDirection: attr(style, "flexDirection", "string"), zIndex: attr(style, "zIndex", "number"), left: attr(style, "left", "number"), right: attr(style, "right", "number"), bottom: attr(style, "bottom", "number"), flexGrow: attr(style, "flex", "number") || attr(style, "flexGrow", "number"), }; const border = (() => { const sides: BorderSides[] = []; if (borderBottomWidth) sides.push("bottom"); if (borderTopWidth) sides.push("top"); if (borderLeftWidth) sides.push("left"); if (borderRightWidth) sides.push("right"); if (!sides.length) return undefined; return sides; })(); return ( {children} ); } export function Pressable({ children: childrenRaw, style, onPress, }: PressableProps) { const bg = style && "backgroundColor" in style ? typeof style.backgroundColor == "string" ? style.backgroundColor.startsWith("rgba(") ? (() => { const parts = style.backgroundColor.split("(")[1].split(")")[0]; const [r, g, b, a] = parts.split(",").map(parseFloat); return RGBA.fromInts(r, g, b, a * 255); })() : style.backgroundColor : undefined : undefined; const flexDirection = style && "flexDirection" in style ? typeof style.flexDirection == "string" ? style.flexDirection : undefined : undefined; const flex = style && "flex" in style ? typeof style.flex == "number" ? style.flex : undefined : undefined; const flexShrink = style && "flexShrink" in style ? typeof style.flexShrink == "number" ? style.flexShrink : undefined : undefined; const overflow = style && "overflow" in style ? typeof style.overflow == "string" ? style.overflow : undefined : undefined; const position = style && "position" in style ? typeof style.position == "string" ? style.position : undefined : undefined; const justifyContent = style && "justifyContent" in style ? typeof style.justifyContent == "string" ? style.justifyContent : undefined : undefined; const alignItems = style && "alignItems" in style ? typeof style.alignItems == "string" ? style.alignItems : undefined : undefined; const padding = style && "padding" in style ? typeof style.padding == "number" ? style.padding : undefined : undefined; const children = childrenRaw instanceof Function ? childrenRaw({ pressed: true }) : childrenRaw; return ( { // @ts-ignore onPress(); } : undefined } backgroundColor={bg} flexDirection={flexDirection} flexGrow={flex} overflow={overflow} flexShrink={flexShrink} position={position} justifyContent={justifyContent} alignItems={alignItems} paddingTop={padding && Math.round(padding / RATIO_HEIGHT)} paddingBottom={padding && Math.round(padding / RATIO_HEIGHT)} paddingLeft={padding && Math.round(padding / RATIO_WIDTH)} paddingRight={padding && Math.round(padding / RATIO_WIDTH)} > {children} ); } export function Text({ style, children }: TextProps) { const fg = style && "color" in style ? typeof style.color == "string" ? style.color : undefined : undefined; return {children}; } export function ScrollView({ children }: ScrollViewProps) { return {children}; } export function Modal({ children, visible }: ModalProps) { const { width, height } = useTerminalDimensions(); return ( {children} ); } export function TextInput({ defaultValue, onChangeText, onKeyPress, }: TextInputProps) { return ( // @ts-ignore onKeyPress({ nativeEvent: { key: key.name == "return" ? "Enter" : key.name, }, }) } placeholder={defaultValue} /> ); } export const Platform = { OS: "tui", }; export const Linking = { openURL: async (url: string) => { const cmd = platform() == "darwin" ? `open ${url}` : platform() == "win32" ? `start "" "${url}"` : `xdg-open "${url}"`; exec(cmd); }, } satisfies Partial; export default { View, Text, };