diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx
index 1f9adbe28..d58a0cd1b 100644
--- a/src/components/Navigator/Navigator.tsx
+++ b/src/components/Navigator/Navigator.tsx
@@ -5,17 +5,19 @@ import React from "react";
import { useIntl } from "react-intl";
import useNavigator from "@saleor/hooks/useNavigator";
+import { getActions, hasActions } from "./modes/default";
+import { getViews, hasViews } from "./modes/default/views";
import NavigatorInput from "./NavigatorInput";
import NavigatorSection from "./NavigatorSection";
import { QuickSearchAction } from "./types";
import useQuickSearch from "./useQuickSearch";
-import { getViews, hasViews } from "./views";
const navigatorHotkey = "ctrl+m, command+m";
const Navigator: React.FC = () => {
const [visible, setVisible] = React.useState(false);
- const [query, change, actions] = useQuickSearch(visible);
+ const input = React.useRef(null);
+ const [query, mode, change, actions] = useQuickSearch(visible, input);
const navigate = useNavigator();
const intl = useIntl();
@@ -51,11 +53,25 @@ const Navigator: React.FC = () => {
{({ getInputProps, getItemProps, highlightedIndex }) => (
+ {hasActions(actions) && (
+
+ )}
{hasViews(actions) && (
{
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
items={getViews(actions)}
- offset={0}
+ offset={getActions(actions).length}
/>
)}
diff --git a/src/components/Navigator/NavigatorInput.tsx b/src/components/Navigator/NavigatorInput.tsx
index af78b3a93..fcb7907f9 100644
--- a/src/components/Navigator/NavigatorInput.tsx
+++ b/src/components/Navigator/NavigatorInput.tsx
@@ -1,42 +1,78 @@
import makeStyles from "@material-ui/core/styles/makeStyles";
import React from "react";
import { useIntl } from "react-intl";
+import { QuickSearchMode } from "./types";
const useStyles = makeStyles(
- theme => ({
- root: {
- background: theme.palette.background.default,
- border: "none",
+ theme => {
+ const typography = {
color: theme.palette.text.primary,
fontSize: 24,
- outline: 0,
- padding: theme.spacing(2, 3),
- width: "100%"
- }
- }),
+ lineHeight: 1.33
+ };
+
+ return {
+ adornment: {
+ ...typography,
+ color: theme.palette.text.secondary,
+ paddingRight: theme.spacing(1)
+ },
+ input: {
+ ...typography,
+ background: "transparent",
+ border: "none",
+ outline: 0,
+ padding: 0,
+ width: "100%"
+ },
+ root: {
+ background: theme.palette.background.default,
+ display: "flex",
+ padding: theme.spacing(2, 3)
+ }
+ };
+ },
{
name: "NavigatorInput"
}
);
-const NavigatorInput: React.FC<
- React.InputHTMLAttributes
-> = props => {
- const classes = useStyles(props);
- const intl = useIntl();
- return (
-
- );
-};
+interface NavigatorInputProps
+ extends React.InputHTMLAttributes {
+ mode: QuickSearchMode;
+}
+
+const NavigatorInput = React.forwardRef(
+ (props, ref) => {
+ const { mode, ...rest } = props;
+ const classes = useStyles(props);
+ const intl = useIntl();
+
+ return (
+
+ {mode === "orders" && #}
+
+
+ );
+ }
+);
NavigatorInput.displayName = "NavigatorInput";
export default NavigatorInput;
diff --git a/src/components/Navigator/modes/default/actions.ts b/src/components/Navigator/modes/default/actions.ts
new file mode 100644
index 000000000..ee7c489bd
--- /dev/null
+++ b/src/components/Navigator/modes/default/actions.ts
@@ -0,0 +1,8 @@
+import { QuickSearchAction } from "../../types";
+
+export function getActions(actions: QuickSearchAction[]): QuickSearchAction[] {
+ return actions.filter(action => action.type === "action");
+}
+export function hasActions(actions: QuickSearchAction[]): boolean {
+ return getActions(actions).length > 0;
+}
diff --git a/src/components/Navigator/modes/default/default.ts b/src/components/Navigator/modes/default/default.ts
new file mode 100644
index 000000000..41f1c3e09
--- /dev/null
+++ b/src/components/Navigator/modes/default/default.ts
@@ -0,0 +1,19 @@
+import { IntlShape } from "react-intl";
+
+import { QuickSearchAction } from "../../types";
+import searchInViews from "./views";
+
+const threshold = 0.05;
+const maxActions = 10;
+
+function getDefaultModeActions(
+ query: string,
+ intl: IntlShape
+): QuickSearchAction[] {
+ return [...searchInViews(query, intl)]
+ .filter(action => action.score >= threshold)
+ .sort((a, b) => (a.score <= b.score ? 1 : -1))
+ .slice(0, maxActions);
+}
+
+export default getDefaultModeActions;
diff --git a/src/components/Navigator/modes/default/index.ts b/src/components/Navigator/modes/default/index.ts
new file mode 100644
index 000000000..445c4ca3d
--- /dev/null
+++ b/src/components/Navigator/modes/default/index.ts
@@ -0,0 +1,4 @@
+export * from "./actions";
+export * from "./default";
+export { default } from "./default";
+export * from "./views";
diff --git a/src/components/Navigator/views.ts b/src/components/Navigator/modes/default/views.ts
similarity index 98%
rename from src/components/Navigator/views.ts
rename to src/components/Navigator/modes/default/views.ts
index 000adeaa2..8b6819967 100644
--- a/src/components/Navigator/views.ts
+++ b/src/components/Navigator/modes/default/views.ts
@@ -20,7 +20,7 @@ import { staffListUrl } from "@saleor/staff/urls";
import { countryListUrl } from "@saleor/taxes/urls";
import { languageListUrl } from "@saleor/translations/urls";
import { webhooksListUrl } from "@saleor/webhooks/urls";
-import { QuickSearchAction } from "./types";
+import { QuickSearchAction } from "../../types";
interface View {
label: string;
diff --git a/src/components/Navigator/modes/index.ts b/src/components/Navigator/modes/index.ts
new file mode 100644
index 000000000..2856aaeab
--- /dev/null
+++ b/src/components/Navigator/modes/index.ts
@@ -0,0 +1,20 @@
+import { IntlShape } from "react-intl";
+
+import { QuickSearchAction, QuickSearchMode } from "../types";
+import getDefaultModeActions from "./default";
+import getOrdersModeActions from "./orders";
+
+function getModeActions(
+ mode: QuickSearchMode,
+ query: string,
+ intl: IntlShape
+): QuickSearchAction[] {
+ switch (mode) {
+ case "orders":
+ return getOrdersModeActions(query, intl);
+ default:
+ return getDefaultModeActions(query, intl);
+ }
+}
+
+export default getModeActions;
diff --git a/src/components/Navigator/modes/messages.ts b/src/components/Navigator/modes/messages.ts
new file mode 100644
index 000000000..0fce6090f
--- /dev/null
+++ b/src/components/Navigator/modes/messages.ts
@@ -0,0 +1,10 @@
+import { defineMessages } from "react-intl";
+
+const messages = defineMessages({
+ goToOrder: {
+ defaultMessage: "Go to order #{orderNumber}",
+ description: "navigator action"
+ }
+});
+
+export default messages;
diff --git a/src/components/Navigator/modes/orders.ts b/src/components/Navigator/modes/orders.ts
new file mode 100644
index 000000000..09a6a8f79
--- /dev/null
+++ b/src/components/Navigator/modes/orders.ts
@@ -0,0 +1,27 @@
+import { IntlShape } from "react-intl";
+
+import { orderUrl } from "@saleor/orders/urls";
+import { QuickSearchAction } from "../types";
+import messages from "./messages";
+
+function getOrdersModeActions(
+ query: string,
+ intl: IntlShape
+): QuickSearchAction[] {
+ if (query === parseInt(query, 0).toString()) {
+ return [
+ {
+ label: intl.formatMessage(messages.goToOrder, {
+ orderNumber: query
+ }),
+ score: 1,
+ type: "action",
+ url: orderUrl(btoa(`Order:${query}`))
+ }
+ ];
+ }
+
+ return [];
+}
+
+export default getOrdersModeActions;
diff --git a/src/components/Navigator/types.ts b/src/components/Navigator/types.ts
index 4c15f60a9..3646a7ce9 100644
--- a/src/components/Navigator/types.ts
+++ b/src/components/Navigator/types.ts
@@ -1,4 +1,4 @@
-export type QuickSearchActionType = "view";
+export type QuickSearchActionType = "action" | "view";
export interface QuickSearchAction {
label: string;
@@ -6,3 +6,5 @@ export interface QuickSearchAction {
type: QuickSearchActionType;
url: string;
}
+
+export type QuickSearchMode = "default" | "orders" | "customers";
diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts
index dc51a9967..3a9798341 100644
--- a/src/components/Navigator/useQuickSearch.ts
+++ b/src/components/Navigator/useQuickSearch.ts
@@ -1,39 +1,70 @@
-import { useState } from "react";
+import { RefObject, useEffect, useState } from "react";
import { useIntl } from "react-intl";
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
-import { QuickSearchAction } from "./types";
-import searchInViews from "./views";
+import getModeActions from "./modes";
+import { QuickSearchAction, QuickSearchMode } from "./types";
-export type QuickSearchMode = "default" | "orders" | "customers";
-const threshold = 0.05;
-const maxActions = 10;
-
-type UseQuickSearch = [string, FormChange, QuickSearchAction[]];
-function useQuickSearch(open: boolean): UseQuickSearch {
+type UseQuickSearch = [
+ string,
+ QuickSearchMode,
+ FormChange,
+ QuickSearchAction[]
+];
+function useQuickSearch(
+ open: boolean,
+ input: RefObject
+): UseQuickSearch {
const [query, setQuery] = useState("");
const [mode, setMode] = useState("default");
const intl = useIntl();
useModalDialogOpen(open, {
- onClose: () => setQuery("")
+ onClose: () => {
+ setMode("default");
+ setQuery("");
+ }
});
+ const handleBack = (event: KeyboardEvent) => {
+ // `any` type because of poorly typed `KeyboardEvent.EventTarget` which
+ // has no `value` key. Which it would have if `KeyboardEvent` and
+ // `EventTarget` would be generic types accepting HTMLDOM element types.
+ if ((event.target as any).value === "" && event.keyCode === 8) {
+ setMode("default");
+ }
+ };
+
+ useEffect(() => {
+ setQuery("");
+ if (mode !== "default" && input.current) {
+ input.current.addEventListener("keyup", handleBack);
+
+ return () => {
+ if (input.current) {
+ input.current.removeEventListener("keyup", handleBack);
+ }
+ };
+ }
+ }, [mode, open]);
+
const change = (event: ChangeEvent) => {
const value = event.target.value;
- setQuery(value);
+ if (mode === "default") {
+ switch (value) {
+ case "# ":
+ setMode("orders");
+ default:
+ setQuery(value);
+ }
+ } else {
+ setQuery(value);
+ }
};
- return [
- query,
- change,
- [...searchInViews(query, intl)]
- .filter(action => action.score >= threshold)
- .sort((a, b) => (a.score <= b.score ? 1 : -1))
- .slice(0, maxActions)
- ];
+ return [query, mode, change, getModeActions(mode, query, intl)];
}
export default useQuickSearch;