From e5b4186faacfa22ce975d4f7d813da242fc3e0c4 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 11 Dec 2021 09:34:16 -0800 Subject: [PATCH] feat: Add searching of sub actions in command menu (#2845) --- app/actions/index.ts | 12 +----- app/components/CommandBar.tsx | 4 +- app/components/CommandBarItem.tsx | 60 +++++++++++++++++++++++----- app/components/CommandBarResults.tsx | 25 ++++-------- app/components/IconPicker.tsx | 10 +++++ package.json | 4 +- yarn.lock | 25 ++++++------ 7 files changed, 88 insertions(+), 52 deletions(-) diff --git a/app/actions/index.ts b/app/actions/index.ts index 622f448da..2084eeb25 100644 --- a/app/actions/index.ts +++ b/app/actions/index.ts @@ -102,20 +102,12 @@ export function actionToKBar( name: resolvedName, section: resolvedSection, placeholder: resolvedPlaceholder, - keywords: `${action.keywords || ""} ${children - .filter((c) => !!c.keywords) - .map((c) => c.keywords) - .join(" ")}`, + keywords: `${action.keywords}`, shortcut: action.shortcut || [], - icon: resolvedIcon - ? React.cloneElement(resolvedIcon, { - color: "currentColor", - }) - : undefined, + icon: resolvedIcon, perform: action.perform ? () => action.perform && action.perform(context) : undefined, - children: children.length ? children.map((a) => a.id) : undefined, }, // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. ].concat(children.map((child) => ({ ...child, parent: action.id }))); diff --git a/app/components/CommandBar.tsx b/app/components/CommandBar.tsx index 5df108ee6..1e182ef8c 100644 --- a/app/components/CommandBar.tsx +++ b/app/components/CommandBar.tsx @@ -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, })); diff --git a/app/components/CommandBarItem.tsx b/app/components/CommandBarItem.tsx index 837178cf2..7977d4d40 100644 --- a/app/components/CommandBarItem.tsx +++ b/app/components/CommandBarItem.tsx @@ -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 ) { + 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 ( {action.icon ? ( + // @ts-expect-error no icon on ActionImpl React.cloneElement(action.icon, { size: 22, + color: "currentColor", }) ) : ( - + )} + + {ancestors.map((ancestor) => ( + + {ancestor.name} + + + ))} {action.name} {action.children?.length ? "…" : ""} @@ -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(CommandBarItem); diff --git a/app/components/CommandBarResults.tsx b/app/components/CommandBarResults.tsx index e497ea4f8..7c2b59ee1 100644 --- a/app/components/CommandBarResults.tsx +++ b/app/components/CommandBarResults.tsx @@ -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 ( typeof item === "string" ? (
{item}
) : ( - + ) } /> diff --git a/app/components/IconPicker.tsx b/app/components/IconPicker.tsx index 6c192e9db..574e4b5b0 100644 --- a/app/components/IconPicker.tsx +++ b/app/components/IconPicker.tsx @@ -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", diff --git a/package.json b/package.json index 470fcde3f..cbc860b82 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "json-loader": "0.5.4", "jsonwebtoken": "^8.5.0", "jszip": "^3.7.1", - "kbar": "^0.1.0-beta.16", + "kbar": "^0.1.0-beta.23", "koa": "^2.10.0", "koa-body": "^4.2.0", "koa-compress": "2.0.0", @@ -115,7 +115,7 @@ "mobx-react": "^6.3.1", "natural-sort": "^1.0.0", "nodemailer": "^6.6.1", - "outline-icons": "^1.35.0", + "outline-icons": "^1.37.0", "oy-vey": "^0.10.0", "passport": "^0.4.1", "passport-google-oauth2": "^0.2.0", diff --git a/yarn.lock b/yarn.lock index 262272d6b..590b6d39a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9682,15 +9682,16 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -kbar@^0.1.0-beta.16: - version "0.1.0-beta.16" - resolved "https://registry.yarnpkg.com/kbar/-/kbar-0.1.0-beta.16.tgz#ed8517c6f3ec1994dc965bff288a0df25b64bfde" - integrity sha512-e/jOX5XZRoACgMMQylXdmOko2+2gvKNRC7H6xqZ/6qP9uJfPoh7Tg66RIzGlmms0OVcYkgMHy9Z0a66SNby3Ig== +kbar@^0.1.0-beta.23: + version "0.1.0-beta.23" + resolved "https://registry.yarnpkg.com/kbar/-/kbar-0.1.0-beta.23.tgz#90f0b1280fdec6790a11447049fbb66fb6840fe6" + integrity sha512-incORLzU/VwmSLiRnvTk3Figxs0iAXoeuBU8ul0457AlMZtqqCu3KrKLiQwmtFe/wooy6Br6MiYOBGXA44aAJQ== dependencies: "@reach/portal" "^0.16.0" fast-equals "^2.0.3" match-sorter "^6.3.0" react-virtual "^2.8.2" + tiny-invariant "^1.2.0" keygrip@~1.1.0: version "1.1.0" @@ -11183,10 +11184,10 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -outline-icons@^1.26.1, outline-icons@^1.35.0: - version "1.35.0" - resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.35.0.tgz#3249d8e8a05b491e8820ca182175336240d1cfa5" - integrity sha512-QWPrft2WKy6eIGBbzeaO/0sYXHuLvbQoBa7NrGQM5j7R9/AoxItnJdolYWKiZJAH8tUxfp2yuBdg+H9/z1Zf+g== +outline-icons@^1.26.1, outline-icons@^1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.37.0.tgz#d189a07519d10da913fd4fc2942dd49db804fe82" + integrity sha512-rWgGbPIrAtRSPHx81NkuR9dKWO1yKbrvT0WcFq5XgetX+Jsri8wQxwt3unSVWVQlkK1AwJR3o5b/FT/b8ifj9w== oy-vey@^0.10.0: version "0.10.0" @@ -14372,10 +14373,10 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== -tiny-invariant@^1.0.1, tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" - integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== +tiny-invariant@^1.0.1, tiny-invariant@^1.0.2, tiny-invariant@^1.0.6, tiny-invariant@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" + integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== tiny-warning@^0.0.3: version "0.0.3"