feat: add tui app
This commit is contained in:
14
packages/ui/package.json
Normal file
14
packages/ui/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@money/ui",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"exports": {
|
||||
".": "./src/index.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-native-opentui": "workspace:*",
|
||||
"@money/shared": "workspace:*"
|
||||
},
|
||||
"packageManager": "pnpm@10.18.2"
|
||||
}
|
||||
15
packages/ui/src/index.tsx
Normal file
15
packages/ui/src/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Text } from "react-native";
|
||||
import { List } from "./list";
|
||||
import { useQuery } from "@rocicorp/zero/react";
|
||||
import { queries } from '@money/shared';
|
||||
|
||||
export function Settings() {
|
||||
const [items] = useQuery(queries.getItems(null));
|
||||
|
||||
return <List
|
||||
items={items}
|
||||
renderItem={({ item, isSelected }) => <Text style={{ color: isSelected ? 'white' : 'black' }}>{item.name}</Text>}
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
30
packages/ui/src/list.tsx
Normal file
30
packages/ui/src/list.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { useKeyboard } from "./useKeyboard";
|
||||
|
||||
export type ListProps<T> = {
|
||||
items: T[],
|
||||
renderItem: (props: { item: T, isSelected: boolean }) => ReactNode;
|
||||
};
|
||||
export function List<T>({ items, renderItem }: ListProps<T>) {
|
||||
const [idx, setIdx] = useState(0);
|
||||
|
||||
useKeyboard((key) => {
|
||||
if (key.name == 'j') {
|
||||
setIdx((prevIdx) => prevIdx + 1 < items.length ? prevIdx + 1 : items.length - 1);
|
||||
} else if (key.name == 'k') {
|
||||
setIdx((prevIdx) => prevIdx == 0 ? 0 : prevIdx - 1);
|
||||
} else if (key.name == 'g' && key.shift) {
|
||||
setIdx(items.length - 1);
|
||||
}
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<View>
|
||||
{items.map((item, index) => <View style={{ backgroundColor: index == idx ? 'black' : undefined }}>
|
||||
{renderItem({ item, isSelected: index == idx })}
|
||||
</View>)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
5
packages/ui/src/useKeyboard.ts
Normal file
5
packages/ui/src/useKeyboard.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useKeyboard as useOpentuiKeyboard } from "@opentui/react";
|
||||
|
||||
export function useKeyboard(handler: Parameters<typeof useOpentuiKeyboard>[0], _deps: any[] = []) {
|
||||
return useOpentuiKeyboard(handler);
|
||||
}
|
||||
35
packages/ui/src/useKeyboard.web.ts
Normal file
35
packages/ui/src/useKeyboard.web.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useEffect } from "react";
|
||||
import type { KeyboardEvent } from "react";
|
||||
import type { KeyEvent } from "@opentui/core";
|
||||
|
||||
|
||||
export function useKeyboard(handler: (key: KeyEvent) => void, deps: any[] = []) {
|
||||
useEffect(() => {
|
||||
const handlerWeb = (event: KeyboardEvent) => {
|
||||
// @ts-ignore
|
||||
handler({
|
||||
name: event.key.toLowerCase(),
|
||||
ctrl: event.ctrlKey,
|
||||
meta: event.metaKey,
|
||||
shift: event.shiftKey,
|
||||
option: event.metaKey,
|
||||
sequence: '',
|
||||
number: false,
|
||||
raw: '',
|
||||
eventType: 'press',
|
||||
source: "raw",
|
||||
code: event.code,
|
||||
super: false,
|
||||
hyper: false,
|
||||
capsLock: false,
|
||||
numLock: false,
|
||||
baseCode: event.keyCode,
|
||||
});
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
window.addEventListener("keydown", handlerWeb);
|
||||
// @ts-ignore
|
||||
return () => window.removeEventListener("keydown", handlerWeb);
|
||||
}, deps);
|
||||
}
|
||||
28
packages/ui/tsconfig.json
Normal file
28
packages/ui/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user