refactor: better shortcut hook

This commit is contained in:
Max Koon
2025-12-05 17:05:23 -05:00
parent 2df7f2d924
commit 76f2a43bd0
21 changed files with 481 additions and 143 deletions

View File

@@ -0,0 +1,72 @@
import { use } from "react";
import { View, Text } from "react-native";
import { RouterContext } from ".";
import { queries, type Mutators, type Schema } from "@money/shared";
import { useQuery, useZero } from "@rocicorp/zero/react";
import * as Table from "../components/Table";
import { Button } from "../components/Button";
const COLUMNS: Table.Column[] = [{ name: "label", label: "Name" }];
export function Budget() {
const { auth } = use(RouterContext);
const [budgets] = useQuery(queries.getBudgets(auth));
// const [items] = useQuery(queries.getBudgetCategories(auth));
const items: any[] = [];
const z = useZero<Schema, Mutators>();
const newBudget = () => {
const id = new Date().getTime().toString();
z.mutate.budget.create({
id,
});
};
if (budgets.length == 0)
return (
<View
style={{
justifyContent: "center",
alignItems: "center",
flex: 1,
gap: 10,
}}
>
<Text style={{ fontFamily: "mono" }}>
No budgets, please create a new budget
</Text>
<Button onPress={newBudget} shortcut="n">
New budget
</Button>
</View>
);
const budget = budgets[0]!;
return (
<>
<View>
<Text style={{ fontFamily: "mono" }}>
Selected Budget: {budget.label}
</Text>
</View>
<Table.Provider
data={items}
columns={COLUMNS}
onKey={(event) => {
if (event.name == "n" && event.shift) {
newBudget();
}
}}
>
<View style={{ flex: 1 }}>
<View style={{ flexShrink: 0 }}>
<Table.Body />
</View>
</View>
</Table.Provider>
</>
);
}

View File

@@ -1,18 +1,24 @@
import { createContext, use } from "react";
import { createContext, use, useEffect, useRef } from "react";
import { Transactions } from "./transactions";
import { View, Text } from "react-native";
import { View } from "react-native";
import { Settings } from "./settings";
import { useKeyboard } from "./useKeyboard";
import type { AuthData } from "@money/shared/auth";
import { Budget } from "./budget";
import { ShortcutProvider, ShortcutDebug, keysStore } from "../lib/shortcuts";
import { useShortcut } from "../lib/shortcuts/hooks";
const PAGES = {
"/": {
screen: <Transactions />,
key: "1",
},
"/budget": {
screen: <Budget />,
key: "2",
},
"/settings": {
screen: <Settings />,
key: "2",
key: "3",
children: {
"/accounts": {},
"/family": {},
@@ -59,7 +65,10 @@ type AppProps = {
export function App({ auth, route, setRoute }: AppProps) {
return (
<RouterContext.Provider value={{ auth, route, setRoute }}>
<Main />
<ShortcutProvider>
<ShortcutDebug />
<Main />
</ShortcutProvider>
</RouterContext.Provider>
);
}
@@ -67,17 +76,9 @@ export function App({ auth, route, setRoute }: AppProps) {
function Main() {
const { route, setRoute } = use(RouterContext);
useKeyboard((key) => {
const screen = Object.entries(PAGES).find(
([, screen]) => screen.key == key.name,
);
if (!screen) return;
const [route] = screen as [Route, never];
setRoute(route);
});
for (const [route, page] of Object.entries(PAGES)) {
useShortcut(page.key, () => setRoute(route as Route));
}
const match =
route in PAGES

View File

@@ -4,8 +4,6 @@ import { RouterContext, type Route } from ".";
import { General } from "./settings/general";
import { Accounts } from "./settings/accounts";
import { Family } from "./settings/family";
import { useKeyboard } from "./useKeyboard";
import { Modal } from "react-native-opentui";
type SettingsRoute = Extract<Route, `/settings${string}`>;
@@ -32,28 +30,27 @@ type Tab = keyof typeof TABS;
export function Settings() {
const { route, setRoute } = use(RouterContext);
useKeyboard(
(key) => {
if (key.name == "h") {
const currentIdx = Object.entries(TABS).findIndex(
([tabRoute, _]) => tabRoute == route,
);
const routes = Object.keys(TABS) as SettingsRoute[];
const last = routes[currentIdx - 1];
if (!last) return;
setRoute(last);
} else if (key.name == "l") {
const currentIdx = Object.entries(TABS).findIndex(
([tabRoute, _]) => tabRoute == route,
);
const routes = Object.keys(TABS) as SettingsRoute[];
const next = routes[currentIdx + 1];
if (!next) return;
setRoute(next);
}
},
[route],
);
// useKeyboard(
// (key) => {
// if (key.name == "h") {
// const currentIdx = Object.entries(TABS).findIndex(
// ([tabRoute, _]) => tabRoute == route,
// );
// const routes = Object.keys(TABS) as SettingsRoute[];
// const last = routes[currentIdx - 1];
// if (!last) return;
// setRoute(last);
// } else if (key.name == "l") {
// const currentIdx = Object.entries(TABS).findIndex(
// ([tabRoute, _]) => tabRoute == route,
// );
// const routes = Object.keys(TABS) as SettingsRoute[];
// const next = routes[currentIdx + 1];
// if (!next) return;
// setRoute(next);
// }
// },
// );
return (
<View style={{ flexDirection: "row" }}>

View File

@@ -3,7 +3,6 @@ import { queries, type Mutators, type Schema } from "@money/shared";
import { use, useEffect, useState } from "react";
import { RouterContext } from "..";
import { View, Text, Linking } from "react-native";
import { useKeyboard } from "../useKeyboard";
import { Button } from "../../components/Button";
import * as Table from "../../components/Table";
import * as Dialog from "../../components/Dialog";

View File

@@ -1,8 +0,0 @@
import { useKeyboard as useOpentuiKeyboard } from "@opentui/react";
export function useKeyboard(
handler: Parameters<typeof useOpentuiKeyboard>[0],
_deps: any[] = [],
) {
return useOpentuiKeyboard(handler);
}

View File

@@ -1,47 +0,0 @@
import { useEffect } from "react";
import type { KeyboardEvent } from "react";
import type { KeyEvent } from "@opentui/core";
function convertName(keyName: string): string {
const result = keyName.toLowerCase();
if (result == "arrowdown") return "down";
if (result == "arrowup") return "up";
return result;
}
export function useKeyboard(
handler: (key: KeyEvent) => void,
deps: any[] = [],
) {
useEffect(() => {
const handlerWeb = (event: KeyboardEvent) => {
// @ts-ignore
handler({
name: convertName(event.key),
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,
preventDefault: () => event.preventDefault(),
});
};
// @ts-ignore
window.addEventListener("keydown", handlerWeb);
return () => {
// @ts-ignore
window.removeEventListener("keydown", handlerWeb);
};
}, deps);
}