feat: Trigger cmd+k from sidebar (#3149)

* feat: Trigger cmd+k from sidebar

* Add hint when opening command bar from sidebar
This commit is contained in:
Tom Moor
2022-02-22 20:13:56 -08:00
committed by GitHub
parent d75af27267
commit 63265b49ea
15 changed files with 173 additions and 67 deletions

View File

@@ -11,7 +11,7 @@ import Sidebar from "~/components/Sidebar";
import SettingsSidebar from "~/components/Sidebar/Settings";
import history from "~/utils/history";
import {
searchUrl,
searchPath,
matchDocumentSlug as slug,
newDocumentPath,
settingsPath,
@@ -49,7 +49,7 @@ class AuthenticatedLayout extends React.Component<Props> {
if (!ev.metaKey && !ev.ctrlKey) {
ev.preventDefault();
ev.stopPropagation();
history.push(searchUrl());
history.push(searchPath());
}
};

View File

@@ -1,24 +1,24 @@
import { useKBar, KBarPositioner, KBarAnimator, KBarSearch } from "kbar";
import { observer } from "mobx-react";
import { QuestionMarkIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Portal } from "react-portal";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import CommandBarResults from "~/components/CommandBarResults";
import SearchActions from "~/components/SearchActions";
import rootActions from "~/actions/root";
import useCommandBarActions from "~/hooks/useCommandBarActions";
import useStores from "~/hooks/useStores";
import { CommandBarAction } from "~/types";
export const CommandBarOptions = {
animations: {
enterMs: 250,
exitMs: 200,
},
};
import { metaDisplay } from "~/utils/keyboard";
import Text from "./Text";
function CommandBar() {
const { t } = useTranslation();
const { ui } = useStores();
useCommandBarActions(rootActions);
const { rootAction } = useKBar((state) => ({
@@ -30,20 +30,34 @@ function CommandBar() {
}));
return (
<KBarPortal>
<Positioner>
<Animator>
<SearchInput
placeholder={`${
rootAction?.placeholder ||
rootAction?.name ||
t("Type a command or search")
}`}
/>
<CommandBarResults />
</Animator>
</Positioner>
</KBarPortal>
<>
<SearchActions />
<KBarPortal>
<Positioner>
<Animator>
<SearchInput
placeholder={`${
rootAction?.placeholder ||
rootAction?.name ||
t("Type a command or search")
}`}
/>
<CommandBarResults />
{ui.showModKHint && (
<Hint size="small" type="tertiary">
<QuestionMarkIcon size={18} color="currentColor" />
{t(
"Open search from anywhere with the {{ shortcut }} shortcut",
{
shortcut: `${metaDisplay} + k`,
}
)}
</Hint>
)}
</Animator>
</Positioner>
</KBarPortal>
</>
);
}
@@ -59,6 +73,20 @@ function KBarPortal({ children }: { children: React.ReactNode }) {
return <Portal>{children}</Portal>;
}
const Hint = styled(Text)`
display: flex;
align-items: center;
gap: 4px;
background: ${(props) => props.theme.secondaryBackground};
border-top: 1px solid ${(props) => props.theme.background};
margin: 1px 0 0;
padding: 6px 16px;
width: 100%;
position: absolute;
bottom: 0;
`;
const Positioner = styled(KBarPositioner)`
z-index: ${(props) => props.theme.depths.commandBar};
`;

View File

@@ -1,14 +1,18 @@
import { useMatches, KBarResults } from "kbar";
import { orderBy } from "lodash";
import * as React from "react";
import styled from "styled-components";
import CommandBarItem from "~/components/CommandBarItem";
import { NoSection } from "~/actions/sections";
export default function CommandBarResults() {
const { results, rootActionId } = useMatches();
return (
<KBarResults
items={results}
items={orderBy(results, (item) =>
typeof item !== "string" && item.section === NoSection ? -1 : 1
)}
maxHeight={400}
onRender={({ item, active }) =>
typeof item === "string" ? (

View File

@@ -7,7 +7,7 @@ import styled, { useTheme } from "styled-components";
import useBoolean from "~/hooks/useBoolean";
import useKeyDown from "~/hooks/useKeyDown";
import { isModKey } from "~/utils/keyboard";
import { searchUrl } from "~/utils/routeHelpers";
import { searchPath } from "~/utils/routeHelpers";
import Input from "./Input";
type Props = {
@@ -51,7 +51,7 @@ function InputSearchPage({
if (ev.key === "Enter") {
ev.preventDefault();
history.push(
searchUrl(ev.currentTarget.value, {
searchPath(ev.currentTarget.value, {
collectionId,
ref: source,
})

View File

@@ -0,0 +1,26 @@
import { useKBar } from "kbar";
import * as React from "react";
import {
navigateToRecentSearchQuery,
navigateToSearchQuery,
} from "~/actions/definitions/navigation";
import useCommandBarActions from "~/hooks/useCommandBarActions";
import useStores from "~/hooks/useStores";
export default function SearchActions() {
const { searches } = useStores();
React.useEffect(() => {
searches.fetchPage({});
}, [searches]);
const { searchQuery } = useKBar((state) => ({
searchQuery: state.searchQuery,
}));
useCommandBarActions(searchQuery ? [navigateToSearchQuery(searchQuery)] : []);
useCommandBarActions(searches.recent.map(navigateToRecentSearchQuery));
return null;
}

View File

@@ -1,3 +1,4 @@
import { useKBar } from "kbar";
import { observer } from "mobx-react";
import {
EditIcon,
@@ -10,6 +11,7 @@ import * as React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useTranslation } from "react-i18next";
import { useHistory, useLocation } from "react-router-dom";
import styled from "styled-components";
import Bubble from "~/components/Bubble";
import Flex from "~/components/Flex";
@@ -21,10 +23,10 @@ import useStores from "~/hooks/useStores";
import AccountMenu from "~/menus/AccountMenu";
import {
homePath,
searchUrl,
draftsPath,
templatesPath,
settingsPath,
searchPath,
} from "~/utils/routeHelpers";
import Sidebar from "./Sidebar";
import ArchiveLink from "./components/ArchiveLink";
@@ -38,9 +40,12 @@ import TrashLink from "./components/TrashLink";
function MainSidebar() {
const { t } = useTranslation();
const { policies, documents } = useStores();
const { ui, policies, documents } = useStores();
const team = useCurrentTeam();
const user = useCurrentUser();
const { query } = useKBar();
const location = useLocation();
const history = useHistory();
React.useEffect(() => {
documents.fetchDrafts();
@@ -57,6 +62,16 @@ function MainSidebar() {
);
const can = policies.abilities(team.id);
const handleSearch = React.useCallback(() => {
const isSearching = location.pathname.startsWith(searchPath());
if (isSearching) {
history.push(searchPath());
} else {
ui.enableModKHint();
query.toggle();
}
}, [ui, location, history, query]);
return (
<Sidebar ref={handleSidebarRef}>
{dndArea && (
@@ -81,12 +96,7 @@ function MainSidebar() {
label={t("Home")}
/>
<SidebarLink
to={{
pathname: searchUrl(),
state: {
fromMenu: true,
},
}}
onClick={handleSearch}
icon={<SearchIcon color="currentColor" />}
label={t("Search")}
exact={false}