Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:command kbar #1450

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .yarn/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/install-state.gz
# /cache
2 changes: 2 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-1.22.19.cjs
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"jotai": "^1.8.4",
"json-stable-stringify-without-jsonify": "^1.0.1",
"jszip": "^3.10.0",
"kbar": "^0.1.0-beta.43",
"lodash-es": "^4.17.21",
"match-sorter": "^6.3.1",
"material-ui-popup-state": "^4.0.1",
Expand Down
33 changes: 32 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { lazy, Suspense } from "react";
import { Routes, Route, Navigate } from "react-router-dom";
import { Routes, Route, Navigate, useNavigate } from "react-router-dom";
import { useAtom } from "jotai";

import { Backdrop } from "@mui/material";
Expand All @@ -17,13 +17,16 @@ import {
currentUserAtom,
userRolesAtom,
altPressAtom,
tablesAtom,
} from "@src/atoms/projectScope";
import { ROUTES } from "@src/constants/routes";
import useKeyPressWithAtom from "@src/hooks/useKeyPressWithAtom";

import TableGroupRedirectPage from "./pages/TableGroupRedirectPage";
import SignOutPage from "@src/pages/Auth/SignOutPage";
import ProvidedArraySubTablePage from "./pages/Table/ProvidedArraySubTablePage";
import SearchTableActionRegister from "./components/Kbar/SearchTableRegisterAction";
import { TableSettings } from "./types/table";

// prettier-ignore
const AuthPage = lazy(() => import("@src/pages/Auth/AuthPage" /* webpackChunkName: "AuthPage" */));
Expand Down Expand Up @@ -66,11 +69,32 @@ const MembersPage = lazy(() => import("@src/pages/Settings/MembersPage" /* webpa
// prettier-ignore
const DebugPage = lazy(() => import("@src/pages/Settings/DebugPage" /* webpackChunkName: "DebugPage" */));

const getLink = (table: TableSettings) =>
`${ROUTES.table}/${table.id.replace(/\//g, "~2F")}`;

export default function App() {
const [currentUser] = useAtom(currentUserAtom, projectScope);
const [userRoles] = useAtom(userRolesAtom, projectScope);
const [tables] = useAtom(tablesAtom, projectScope);
const navigate = useNavigate();
useKeyPressWithAtom("Alt", altPressAtom, projectScope);

// Actions for table navigation through kbar
const generateTableActionObjects = () => {
return tables.map((table) => {
const { id, name } = table;

return {
id: `switchTable-${id}`,
name: `Go to table: ${name}`,
keywords: "Go to table",
perform: () => {
navigate(getLink(table));
},
};
});
};

return (
<Suspense fallback={<Loading fullScreen />}>
<ProjectSourceFirebase />
Expand Down Expand Up @@ -193,6 +217,13 @@ export default function App() {
</Route>
</Routes>
)}
{tables.length ? (
<SearchTableActionRegister
tableObjects={generateTableActionObjects()}
/>
) : (
""
)}
</Suspense>
);
}
22 changes: 12 additions & 10 deletions src/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { CacheProvider } from "@emotion/react";
import RowyThemeProvider from "@src/theme/RowyThemeProvider";
import SnackbarProvider from "@src/contexts/SnackbarContext";
import { SnackLogProvider } from "@src/contexts/SnackLogContext";

import CommandKProvider from "./contexts/CommandKProvider";
import { Suspense } from "react";
import Loading from "@src/components/Loading";

Expand Down Expand Up @@ -52,15 +52,17 @@ export default function Providers({
<LocalizationProvider dateAdapter={AdapterDateFns}>
<CacheProvider value={muiCache}>
<RowyThemeProvider>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<SnackbarProvider>
<SnackLogProvider>
<Suspense fallback={<Loading fullScreen />}>
{children}
</Suspense>
</SnackLogProvider>
</SnackbarProvider>
</ErrorBoundary>
<CommandKProvider>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<SnackbarProvider>
<SnackLogProvider>
<Suspense fallback={<Loading fullScreen />}>
{children}
</Suspense>
</SnackLogProvider>
</SnackbarProvider>
</ErrorBoundary>
</CommandKProvider>
</RowyThemeProvider>
</CacheProvider>
</LocalizationProvider>
Expand Down
37 changes: 37 additions & 0 deletions src/components/Kbar/KbarRenderResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { KBarResults, useMatches } from "kbar";
import KbarResultItem from "./KbarResultItem";
import { useTheme } from "@mui/material";

const KbarRenderResults: React.FC = () => {
const { results, rootActionId } = useMatches();
const theme = useTheme();

const groupNameStyle = {
padding: "12px 16px",
fontSize: "10px",
textTransform: "uppercase" as const,
opacity: 0.5,
background: theme.palette.mode === "dark" ? "#1C1C1F" : "#FFFFFF",
zIndex: 2000,
};

return (
<KBarResults
items={results}
maxHeight={300}
onRender={({ item, active }) =>
typeof item === "string" ? (
<div style={groupNameStyle}>{item}</div>
) : (
<KbarResultItem
action={item}
active={active}
currentRootActionId={rootActionId}
/>
)
}
/>
);
};

export default KbarRenderResults;
111 changes: 111 additions & 0 deletions src/components/Kbar/KbarResultItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useTheme } from "@mui/material";
import { ActionImpl } from "kbar";
import React from "react";

//ResultItem from kbar documentation
const KbarResultItem = React.forwardRef(
(
{
action,
active,
currentRootActionId,
}: {
action: ActionImpl;
active: boolean;
currentRootActionId: string | null | undefined;
},
ref: React.Ref<HTMLDivElement>
) => {
const theme = useTheme();

const ancestors = React.useMemo(() => {
if (!currentRootActionId) return action.ancestors;
const index = action.ancestors.findIndex(
(ancestor: any) => ancestor.id === currentRootActionId
);
return action.ancestors.slice(index + 1);
}, [action.ancestors, currentRootActionId]);

return (
<div
ref={ref}
style={{
padding: "12px 16px",
background: active
? theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.08)"
: "rgb(240, 240, 240)"
: "transparent",
borderLeft: `2px solid ${
active ? "var(--foreground)" : "transparent"
}`,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
cursor: "pointer",
}}
>
<div
style={{
display: "flex",
gap: "8px",
alignItems: "center",
fontSize: 14,
}}
>
{action.icon && action.icon}
<div style={{ display: "flex", flexDirection: "column" }}>
<div>
{ancestors.length > 0 &&
ancestors.map((ancestor: any) => (
<React.Fragment key={ancestor.id}>
<span
style={{
opacity: 0.5,
marginRight: 8,
}}
>
{ancestor.name}
</span>
<span
style={{
marginRight: 8,
}}
>
&rsaquo;
</span>
</React.Fragment>
))}
<span>{action.name}</span>
</div>
{action.subtitle && (
<span style={{ fontSize: 12 }}>{action.subtitle}</span>
)}
</div>
</div>
{action.shortcut?.length ? (
<div
aria-hidden
style={{ display: "grid", gridAutoFlow: "column", gap: "4px" }}
>
{action.shortcut.map((sc: any) => (
<kbd
key={sc}
style={{
padding: "4px 6px",
background: "rgba(0 0 0 / .1)",
borderRadius: "4px",
fontSize: 14,
}}
>
{sc}
</kbd>
))}
</div>
) : null}
</div>
);
}
);

export default KbarResultItem;
19 changes: 19 additions & 0 deletions src/components/Kbar/SearchTableRegisterAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useRegisterActions } from "kbar";

interface TableActionObject {
id: string;
name: string;
keywords: string;
perform: () => void;
}

const SearchTableActionRegister = ({
tableObjects,
}: {
tableObjects: TableActionObject[];
}) => {
useRegisterActions([...tableObjects]);
return null;
};

export default SearchTableActionRegister;
21 changes: 21 additions & 0 deletions src/components/Table/FinalColumn/AddColumnRegister.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { columnModalAtom, tableScope } from "@src/atoms/tableScope";
import { useSetAtom } from "jotai";
import { useRegisterActions } from "kbar";

const AddColumnRegister = () => {
const openColumnModal = useSetAtom(columnModalAtom, tableScope);
useRegisterActions([
{
id: "addColumn",
name: "Add Column",
shortcut: ["x", "c"],
keywords: "Add column",
perform: () => {
openColumnModal({ type: "new" });
},
},
]);
return null;
};

export default AddColumnRegister;
2 changes: 2 additions & 0 deletions src/components/Table/FinalColumn/FinalColumnHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AddColumn as AddColumnIcon } from "@src/assets/icons";

import { tableScope, columnModalAtom } from "@src/atoms/tableScope";
import { spreadSx } from "@src/utils/ui";
import AddColumnRegister from "./AddColumnRegister";

export interface IFinalColumnHeaderProps extends Partial<BoxProps> {
focusInsideCell: boolean;
Expand Down Expand Up @@ -50,6 +51,7 @@ export default function FinalColumnHeader({
>
Add column
</Button>
<AddColumnRegister />
</Box>
);
else
Expand Down
13 changes: 13 additions & 0 deletions src/components/TableToolbar/AddRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
updateTableSchemaAtom,
} from "@src/atoms/tableScope";
import { TableIdType } from "@src/types/table";
import { useRegisterActions } from "kbar";

export default function AddRow() {
const [userRoles] = useAtom(userRolesAtom, projectScope);
Expand All @@ -46,6 +47,18 @@ export default function AddRow() {
const idType = tableSchema.idType || "decrement";
const forceRandomId = tableFilters.length > 0 || tableSorts.length > 0;

useRegisterActions([
{
id: "addRow",
name: "Add Row",
shortcut: ["x", "r"],
keywords: "Add row",
perform: () => {
handleClick()
},
},
]);

const handleSetIdType = async (idType: TableIdType) => {
// TODO(han): refactor atom - error handler
await updateTableSchema!({
Expand Down
Loading