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:
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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};
|
||||
`;
|
||||
|
||||
@@ -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" ? (
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
26
app/components/SearchActions.ts
Normal file
26
app/components/SearchActions.ts
Normal 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;
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user