- "content": "import { Box, Text } from \"ink\";\nimport React, { useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { useTheme } from \"@/components/ui/theme-provider\";\nimport { useInput } from \"@/hooks/use-input\";\n\nexport interface AppShellProps {\n children: ReactNode;\n fullscreen?: boolean;\n}\n\nexport interface AppShellHeaderProps {\n children: ReactNode;\n}\n\nexport interface AppShellTipProps {\n children: ReactNode;\n}\n\nexport interface AppShellInputProps {\n value?: string;\n onChange?: (value: string) => void;\n onSubmit?: (value: string) => void;\n placeholder?: string;\n borderStyle?: \"single\" | \"double\" | \"round\" | \"bold\";\n borderColor?: string;\n prefix?: string;\n}\n\nexport interface AppShellContentProps {\n children: ReactNode;\n autoscroll?: boolean;\n height?: number;\n}\n\nexport interface AppShellHintsProps {\n items?: string[];\n children?: ReactNode;\n}\n\nconst AppShellRoot = function AppShellRoot({ children }: AppShellProps) {\n return (\n <Box flexDirection=\"column\" flexGrow={1}>\n {children}\n </Box>\n );\n};\n\nconst AppShellHeader = function AppShellHeader({\n children,\n}: AppShellHeaderProps) {\n return <Box flexDirection=\"column\">{children}</Box>;\n};\n\nconst AppShellTip = function AppShellTip({ children }: AppShellTipProps) {\n return (\n <Box paddingLeft={2} paddingY={0}>\n <Text dimColor>{\" Tip: \"}</Text>\n <Text dimColor>{children}</Text>\n </Box>\n );\n};\n\nconst AppShellInput = function AppShellInput({\n value: controlledValue,\n onChange,\n onSubmit,\n placeholder = \"Type something...\",\n borderStyle = \"single\",\n borderColor,\n prefix = \">\",\n}: AppShellInputProps) {\n const [internalValue, setInternalValue] = useState(\"\");\n const theme = useTheme();\n const value = controlledValue ?? internalValue;\n\n useInput((input, key) => {\n if (key.return) {\n onSubmit?.(value);\n if (!controlledValue) {\n setInternalValue(\"\");\n }\n return;\n }\n if (key.backspace || key.delete) {\n const next = value.slice(0, -1);\n if (onChange) {\n onChange(next);\n } else {\n setInternalValue(next);\n }\n return;\n }\n if (key.escape || key.upArrow || key.downArrow || key.tab) {\n return;\n }\n const next = value + input;\n if (onChange) {\n onChange(next);\n } else {\n setInternalValue(next);\n }\n });\n\n return (\n <Box\n borderStyle={borderStyle}\n borderColor={borderColor ?? theme.colors.border}\n flexDirection=\"row\"\n paddingX={1}\n >\n {prefix && (\n <Text color={theme.colors.primary} bold>\n {`${prefix} `}\n </Text>\n )}\n <Text>{value || <Text dimColor>{placeholder}</Text>}</Text>\n <Text color={theme.colors.focusRing}>█</Text>\n </Box>\n );\n};\n\nconst AppShellContent = function AppShellContent({\n children,\n height = 20,\n}: AppShellContentProps) {\n const [scrollTop, setScrollTop] = useState(0);\n\n useInput((_input, key) => {\n if (key.upArrow) {\n setScrollTop((s) => Math.max(0, s - 1));\n } else if (key.downArrow) {\n setScrollTop((s) => s + 1);\n }\n });\n\n return (\n <Box flexDirection=\"row\" height={height} overflow=\"hidden\">\n <Box flexGrow={1} flexDirection=\"column\" marginTop={-scrollTop as number}>\n {children}\n </Box>\n </Box>\n );\n};\n\nconst AppShellHints = function AppShellHints({\n items,\n children,\n}: AppShellHintsProps) {\n const theme = useTheme();\n const content = items ? items.join(\" | \") : children;\n return (\n <Box paddingX={1}>\n <Text dimColor color={theme.colors.mutedForeground}>\n {content as string}\n </Text>\n </Box>\n );\n};\n\nexport const AppShell = Object.assign(AppShellRoot, {\n Content: AppShellContent,\n Header: AppShellHeader,\n Hints: AppShellHints,\n Input: AppShellInput,\n Tip: AppShellTip,\n});\n",
0 commit comments