feat: Add searching of sub actions in command menu (#2845)
This commit is contained in:
@@ -23,7 +23,9 @@ function CommandBar() {
|
||||
|
||||
const { rootAction } = useKBar((state) => ({
|
||||
rootAction: state.currentRootActionId
|
||||
? (state.actions[state.currentRootActionId] as CommandBarAction)
|
||||
? ((state.actions[
|
||||
state.currentRootActionId
|
||||
] as unknown) as CommandBarAction)
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,31 +1,55 @@
|
||||
import { BackIcon } from "outline-icons";
|
||||
import { ActionImpl } from "kbar";
|
||||
import { ArrowIcon, BackIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import styled, { css } from "styled-components";
|
||||
import Flex from "~/components/Flex";
|
||||
import Key from "~/components/Key";
|
||||
import { CommandBarAction } from "~/types";
|
||||
|
||||
type Props = {
|
||||
action: CommandBarAction;
|
||||
action: ActionImpl;
|
||||
active: boolean;
|
||||
currentRootActionId: string | null | undefined;
|
||||
};
|
||||
|
||||
function CommandBarItem(
|
||||
{ action, active }: Props,
|
||||
{ action, active, currentRootActionId }: Props,
|
||||
ref: React.RefObject<HTMLDivElement>
|
||||
) {
|
||||
const ancestors = React.useMemo(() => {
|
||||
if (!currentRootActionId) {
|
||||
return action.ancestors;
|
||||
}
|
||||
const index = action.ancestors.findIndex(
|
||||
(ancestor) => ancestor.id === currentRootActionId
|
||||
);
|
||||
|
||||
// +1 removes the currentRootAction; e.g. if we are on the "Set theme"
|
||||
// parent action, the UI should not display "Set theme… > Dark" but rather
|
||||
// just "Dark"
|
||||
return action.ancestors.slice(index + 1);
|
||||
}, [action.ancestors, currentRootActionId]);
|
||||
|
||||
return (
|
||||
<Item active={active} ref={ref}>
|
||||
<Text align="center" gap={8}>
|
||||
<Icon>
|
||||
{action.icon ? (
|
||||
// @ts-expect-error no icon on ActionImpl
|
||||
React.cloneElement(action.icon, {
|
||||
size: 22,
|
||||
color: "currentColor",
|
||||
})
|
||||
) : (
|
||||
<ForwardIcon color="currentColor" size={22} />
|
||||
<ArrowIcon color="currentColor" />
|
||||
)}
|
||||
</Icon>
|
||||
|
||||
{ancestors.map((ancestor) => (
|
||||
<React.Fragment key={ancestor.id}>
|
||||
<Ancestor>{ancestor.name}</Ancestor>
|
||||
<ForwardIcon color="currentColor" size={20} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
{action.name}
|
||||
{action.children?.length ? "…" : ""}
|
||||
</Text>
|
||||
@@ -46,9 +70,16 @@ function CommandBarItem(
|
||||
);
|
||||
}
|
||||
|
||||
const Icon = styled.div`
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
const Icon = styled(Flex)`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const Ancestor = styled.span`
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
`;
|
||||
|
||||
@@ -60,7 +91,7 @@ const Text = styled(Flex)`
|
||||
|
||||
const Item = styled.div<{ active?: boolean }>`
|
||||
font-size: 15px;
|
||||
padding: 12px 16px;
|
||||
padding: 10px 16px;
|
||||
background: ${(props) =>
|
||||
props.active ? props.theme.menuItemSelected : "none"};
|
||||
display: flex;
|
||||
@@ -72,10 +103,19 @@ const Item = styled.div<{ active?: boolean }>`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
|
||||
${(props) =>
|
||||
props.active &&
|
||||
css`
|
||||
${Icon} {
|
||||
color: ${props.theme.text};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const ForwardIcon = styled(BackIcon)`
|
||||
transform: rotate(180deg);
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
export default React.forwardRef<HTMLDivElement, Props>(CommandBarItem);
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
import { useMatches, KBarResults, Action } from "kbar";
|
||||
import { useMatches, KBarResults } from "kbar";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import CommandBarItem from "~/components/CommandBarItem";
|
||||
import { CommandBarAction } from "~/types";
|
||||
|
||||
export default function CommandBarResults() {
|
||||
const matches = useMatches();
|
||||
const items = React.useMemo(
|
||||
() =>
|
||||
matches
|
||||
.reduce((acc, curr) => {
|
||||
const { actions, name } = curr;
|
||||
acc.push(name);
|
||||
acc.push(...actions);
|
||||
return acc;
|
||||
}, [] as (Action | string)[])
|
||||
.filter((i) => i !== "none"),
|
||||
[matches]
|
||||
);
|
||||
const { results, rootActionId } = useMatches();
|
||||
|
||||
return (
|
||||
<KBarResults
|
||||
items={items}
|
||||
items={results}
|
||||
maxHeight={400}
|
||||
onRender={({ item, active }) =>
|
||||
typeof item === "string" ? (
|
||||
<Header>{item}</Header>
|
||||
) : (
|
||||
<CommandBarItem action={item as CommandBarAction} active={active} />
|
||||
<CommandBarItem
|
||||
action={item}
|
||||
active={active}
|
||||
currentRootActionId={rootActionId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BookmarkedIcon,
|
||||
BicycleIcon,
|
||||
CollectionIcon,
|
||||
CoinsIcon,
|
||||
AcademicCapIcon,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
SportIcon,
|
||||
SunIcon,
|
||||
TargetIcon,
|
||||
TerminalIcon,
|
||||
ToolsIcon,
|
||||
VehicleIcon,
|
||||
WarningIcon,
|
||||
@@ -74,6 +76,10 @@ export const icons = {
|
||||
component: AcademicCapIcon,
|
||||
keywords: "learn teach lesson guide tutorial onboarding training",
|
||||
},
|
||||
bicycle: {
|
||||
component: BicycleIcon,
|
||||
keywords: "bicycle bike cycle",
|
||||
},
|
||||
beaker: {
|
||||
component: BeakerIcon,
|
||||
keywords: "lab research experiment test",
|
||||
@@ -162,6 +168,10 @@ export const icons = {
|
||||
component: TargetIcon,
|
||||
keywords: "target goal sales",
|
||||
},
|
||||
terminal: {
|
||||
component: TerminalIcon,
|
||||
keywords: "terminal code",
|
||||
},
|
||||
tools: {
|
||||
component: ToolsIcon,
|
||||
keywords: "tool settings",
|
||||
|
||||
Reference in New Issue
Block a user