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 #1443

Closed
wants to merge 3 commits into from
Closed
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
1 change: 1 addition & 0 deletions .yarn/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/install-state.gz
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
21 changes: 12 additions & 9 deletions src/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +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 +53,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;
137 changes: 137 additions & 0 deletions src/contexts/CommandKProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useTheme } from "@mui/material";
import {
navOpenAtom,
projectScope,
tableSettingsDialogAtom,
} from "@src/atoms/projectScope";
import KbarRenderResults from "@src/components/Kbar/KbarRenderResults";
import { useSetAtom } from "jotai";
import {
KBarAnimator,
KBarPortal,
KBarPositioner,
KBarProvider,
KBarSearch,
} from "kbar";
import { ReactNode } from "react";
import { useNavigate } from "react-router-dom";

const CommandKProvider = ({ children }: { children: ReactNode }) => {
const theme = useTheme();
const setTableSettingsDialog = useSetAtom(
tableSettingsDialogAtom,
projectScope
);
const setNavOpen = useSetAtom(navOpenAtom, projectScope);
const navigate = useNavigate();

// styles according to kbar docs
const searchStyle = {
padding: "16px 16px",
fontSize: "16px",
width: "100%",
boxSizing: "border-box" as React.CSSProperties["boxSizing"],
outline: "none",
border: "none",
background: theme.palette.mode === "dark" ? "#1C1C1F" : "#FFFFFF", //bgColor based on theme
color: theme.palette.text.primary,
zIndex: "2000",
};

const animatorStyle = {
maxWidth: "600px",
width: "100%",
background: theme.palette.mode === "dark" ? "#1C1C1F" : "#FFFFFF",
color: theme.palette.text.primary,
borderRadius: "8px",
overflow: "hidden",
boxShadow: "0px 6px 20px rgb(0 0 0 / 30%)",
zIndex: "2000",
};

// Static actions
const actions = [
{
id: "tables",
name: "Tables",
shortcut: ["t"], // hotkey for this action
keywords: "Go to tables page",
perform: () => navigate("/tables"),
},
{
id: "tableTutorial",
name: "Tables Tutorial",
shortcut: ["j"],
keywords: "Go through a tutorial",
perform: () => navigate("/tutorial/table"),
},
{
id: "settings",
name: "Settings",
shortcut: ["s"],
keywords: "Go to settings",
perform: () => navigate("/settings"),
},
{
id: "userSettings",
name: "User settings",
shortcut: ["u"],
keywords: "Go to user settings",
perform: () => navigate("/settings/user"),
},
{
id: "projectSettings",
name: "Project settings",
shortcut: ["p"],
keywords: "Go to project settings",
perform: () => navigate("/settings/project"),
},
{
id: "members",
name: "Members page",
shortcut: ["m"],
keywords: "Go to Members page",
perform: () => navigate("/members"),
},
{
id: "debug",
name: "Debug",
shortcut: ["d"],
keywords: "Debug",
perform: () => navigate("/debug"),
},
{
id: "createTable",
name: "Create Table",
shortcut: ["c"],
keywords: "create",
perform: () => {
setTableSettingsDialog({ open: true });
},
},
{
id: "toggleNav",
name: "Toggle Nav Drawer",
shortcut: ["n"],
keywords: "toggle",
perform: () => {
setNavOpen((value) => !value);
},
},
];
return (
<KBarProvider actions={actions} options={{ enableHistory: true }}>
<KBarPortal>
<KBarPositioner style={{ zIndex: 1200 }}>
<KBarAnimator style={animatorStyle}>
<KBarSearch style={searchStyle} />
<KbarRenderResults />
</KBarAnimator>
</KBarPositioner>
</KBarPortal>
{children}
</KBarProvider>
);
};

export default CommandKProvider;
Loading