From 929509d0886bb8126674346c6653dcbf17159b0e Mon Sep 17 00:00:00 2001 From: Wojciech Mista Date: Mon, 1 Jul 2024 17:14:22 +0200 Subject: [PATCH] Improve local search + improve Navigator Search UI (#5009) * migrate from fuzzaldrin to fusejs * fix types * improve navigator search + port to new macaw-ui * fix type issue * fixes * final fixes * add fuzzySearch separate test * cr fixes * cr fixes --- .changeset/perfect-crews-speak.md | 5 + package-lock.json | 1066 +---------------- package.json | 3 - .../ChannelsAvailabilityContent.tsx | 12 +- .../ChannelsAvailabilityDialog/utils.ts | 6 +- .../MultiAutocompleteSelectField.tsx | 6 +- src/components/Navigator/Navigator.tsx | 176 +-- src/components/Navigator/NavigatorInput.tsx | 174 +-- src/components/Navigator/NavigatorSection.tsx | 108 +- src/components/Navigator/modes/catalog.ts | 90 +- .../Navigator/modes/commands/actions.test.ts | 43 + .../Navigator/modes/commands/actions.ts | 14 +- src/components/Navigator/modes/customers.ts | 1 - .../Navigator/modes/default/default.ts | 18 +- .../Navigator/modes/default/views.ts | 5 +- src/components/Navigator/modes/utils.ts | 72 +- src/components/Navigator/types.ts | 1 - src/components/Navigator/useQuickSearch.ts | 4 +- .../DiscountCountrySelectDialog.tsx | 85 +- src/hooks/useChannelsSearch.ts | 7 +- src/hooks/useChoiceSearch.ts | 4 +- src/misc.ts | 19 + .../ShippingZoneCountriesAssignDialog.tsx | 9 +- src/utils/misc.test.ts | 27 + 24 files changed, 484 insertions(+), 1471 deletions(-) create mode 100644 .changeset/perfect-crews-speak.md create mode 100644 src/components/Navigator/modes/commands/actions.test.ts create mode 100644 src/utils/misc.test.ts diff --git a/.changeset/perfect-crews-speak.md b/.changeset/perfect-crews-speak.md new file mode 100644 index 00000000000..df401ff5dad --- /dev/null +++ b/.changeset/perfect-crews-speak.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Searching for countries and other items is now more efficient, making it easier to find what you need. Additionally, the Dashboard Navigator UI has been improved to match the rest of the application, providing a more consistent experience. diff --git a/package-lock.json b/package-lock.json index ea15baaafd4..9fe362f775c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,6 @@ "fast-array-diff": "^0.2.0", "find-test-names": "^1.17.1", "fuse.js": "^6.6.2", - "fuzzaldrin": "^2.1.0", "graphiql": "^2.2.0", "graphql": "^15.4.0", "hotkeys-js": "^3.8.1", @@ -120,9 +119,7 @@ "@types/apollo-upload-client": "^17.0.2", "@types/color-convert": "^2.0.0", "@types/debug": "^4.1.7", - "@types/fuzzaldrin": "^2.1.4", "@types/is-ci": "^3.0.0", - "@types/jscodeshift": "^0.11.3", "@types/lodash-es": "^4.17.3", "@types/react": "^17.0.50", "@types/react-dom": "^17.0.17", @@ -1332,8 +1329,8 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1376,8 +1373,8 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1417,8 +1414,8 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.12.13", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -11936,15 +11933,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/jscodeshift": { - "version": "0.11.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.14.1", - "recast": "^0.20.3" - } - }, "node_modules/@types/json-schema": { "version": "7.0.11", "devOptional": true, @@ -13731,14 +13719,6 @@ "node": ">=8" } }, - "node_modules/array-unique": { - "version": "0.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array.prototype.flat": { "version": "1.2.4", "devOptional": true, @@ -13796,14 +13776,6 @@ "node": ">=0.8" } }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ast-module-types": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.0.tgz", @@ -13856,17 +13828,6 @@ "node": ">= 4.0.0" } }, - "node_modules/atob": { - "version": "2.1.2", - "dev": true, - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/attr-accept": { "version": "2.2.2", "license": "MIT", @@ -14261,69 +14222,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/base": { - "version": "0.11.2", - "dev": true, - "license": "MIT", - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "devOptional": true, @@ -15362,31 +15260,6 @@ "license": "MIT", "optional": true }, - "node_modules/class-utils": { - "version": "0.3.6", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/classnames": { "version": "2.3.1", "license": "MIT" @@ -15629,18 +15502,6 @@ "license": "MIT", "optional": true }, - "node_modules/collection-visit": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "license": "MIT", @@ -15670,14 +15531,6 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "devOptional": true }, - "node_modules/colors": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "devOptional": true, @@ -20344,58 +20197,6 @@ "node": ">= 0.8.0" } }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/expect": { "version": "27.5.1", "license": "MIT", @@ -20501,29 +20302,6 @@ "version": "3.0.2", "license": "MIT" }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/extendable-error": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", @@ -20543,81 +20321,6 @@ "node": ">=4" } }, - "node_modules/extglob": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/extract-files": { "version": "11.0.0", "license": "MIT", @@ -23216,28 +22919,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -25342,151 +25023,6 @@ "license": "MIT", "optional": true }, - "node_modules/jscodeshift": { - "version": "0.13.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "colors": "^1.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^3.1.10", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.20.4", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - } - }, - "node_modules/jscodeshift/node_modules/braces": { - "version": "2.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/fill-range": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/is-number": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/micromatch": { - "version": "3.1.10", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/to-regex-range": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jsdom": { "version": "16.7.0", "license": "MIT", @@ -27631,29 +27167,6 @@ "node": ">=8" } }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/mixme": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.5.tgz", @@ -28200,27 +27713,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/nanomatch": { - "version": "1.2.13", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "license": "MIT", @@ -28530,17 +28022,6 @@ "node": ">= 0.4" } }, - "node_modules/object-visit": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object.assign": { "version": "4.1.4", "devOptional": true, @@ -29185,14 +28666,6 @@ "tslib": "^2.0.3" } }, - "node_modules/pascalcase": { - "version": "0.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-case": { "version": "3.0.4", "dev": true, @@ -29345,8 +28818,8 @@ }, "node_modules/pirates": { "version": "4.0.5", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 6" } @@ -30964,28 +30437,6 @@ "node": ">=8.10.0" } }, - "node_modules/recast": { - "version": "0.20.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "0.14.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/recast/node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rechoir": { "version": "0.6.2", "devOptional": true, @@ -32115,14 +31566,6 @@ "node": ">=4" } }, - "node_modules/ret": { - "version": "0.1.15", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12" - } - }, "node_modules/retes": { "version": "0.33.0", "dev": true, @@ -32352,14 +31795,6 @@ ], "license": "MIT" }, - "node_modules/safe-regex": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ret": "~0.1.10" - } - }, "node_modules/safe-regex-test": { "version": "1.0.0", "devOptional": true, @@ -33208,18 +32643,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "dev": true, - "license": "MIT", - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "node_modules/source-map-support": { "version": "0.5.21", "devOptional": true, @@ -33237,11 +32660,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-url": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, "node_modules/sourcemap-codec": { "version": "1.4.8", "dev": true, @@ -36023,11 +35441,6 @@ "punycode": "^2.1.0" } }, - "node_modules/urix": { - "version": "0.1.0", - "dev": true, - "license": "MIT" - }, "node_modules/url-join": { "version": "4.0.1", "license": "MIT" @@ -38207,7 +37620,7 @@ }, "@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "devOptional": true, + "optional": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -38235,7 +37648,7 @@ }, "@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "devOptional": true, + "optional": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -38260,7 +37673,7 @@ }, "@babel/plugin-syntax-typescript": { "version": "7.12.13", - "devOptional": true, + "optional": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -45737,14 +45150,6 @@ "version": "4.0.5", "dev": true }, - "@types/jscodeshift": { - "version": "0.11.3", - "dev": true, - "requires": { - "ast-types": "^0.14.1", - "recast": "^0.20.3" - } - }, "@types/json-schema": { "version": "7.0.11", "devOptional": true @@ -47092,10 +46497,6 @@ "array-union": { "version": "2.1.0" }, - "array-unique": { - "version": "0.3.2", - "dev": true - }, "array.prototype.flat": { "version": "1.2.4", "devOptional": true, @@ -47136,10 +46537,6 @@ "version": "1.0.0", "optional": true }, - "assign-symbols": { - "version": "1.0.0", - "dev": true - }, "ast-module-types": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.0.tgz", @@ -47178,10 +46575,6 @@ "version": "1.0.0", "devOptional": true }, - "atob": { - "version": "2.1.2", - "dev": true - }, "attr-accept": { "version": "2.2.2" }, @@ -47442,51 +46835,6 @@ "version": "1.0.2", "devOptional": true }, - "base": { - "version": "0.11.2", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "base64-js": { "version": "1.5.1", "devOptional": true @@ -48204,25 +47552,6 @@ "version": "1.2.2", "optional": true }, - "class-utils": { - "version": "0.3.6", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "classnames": { "version": "2.3.1" }, @@ -48372,14 +47701,6 @@ "version": "1.0.1", "optional": true }, - "collection-visit": { - "version": "1.0.0", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color-convert": { "version": "2.0.1", "requires": { @@ -48401,10 +47722,6 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "devOptional": true }, - "colors": { - "version": "1.4.0", - "dev": true - }, "combined-stream": { "version": "1.0.8", "devOptional": true, @@ -51520,46 +50837,6 @@ "version": "0.1.2", "optional": true }, - "expand-brackets": { - "version": "2.1.4", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, "expect": { "version": "27.5.1", "optional": true, @@ -51647,23 +50924,6 @@ "extend": { "version": "3.0.2" }, - "extend-shallow": { - "version": "3.0.2", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "extendable-error": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", @@ -51679,59 +50939,6 @@ "tmp": "^0.0.33" } }, - "extglob": { - "version": "2.0.4", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "extract-files": { "version": "11.0.0" }, @@ -53508,22 +52715,6 @@ "has": "^1.0.3" } }, - "is-data-descriptor": { - "version": "0.1.4", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -54871,120 +54062,6 @@ "version": "0.1.1", "optional": true }, - "jscodeshift": { - "version": "0.13.0", - "dev": true, - "requires": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "colors": "^1.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^3.1.10", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.20.4", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, "jsdom": { "version": "16.7.0", "optional": true, @@ -56517,23 +55594,6 @@ } } }, - "mixin-deep": { - "version": "1.3.2", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "mixme": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.5.tgz", @@ -56915,23 +55975,6 @@ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, - "nanomatch": { - "version": "1.2.13", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, "natural-compare": { "version": "1.4.0", "optional": true @@ -57146,13 +56189,6 @@ "version": "1.1.1", "devOptional": true }, - "object-visit": { - "version": "1.0.1", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, "object.assign": { "version": "4.1.4", "devOptional": true, @@ -57590,10 +56626,6 @@ } } }, - "pascalcase": { - "version": "0.1.1", - "dev": true - }, "path-case": { "version": "3.0.4", "dev": true, @@ -57699,7 +56731,7 @@ }, "pirates": { "version": "4.0.5", - "devOptional": true + "optional": true }, "pixelmatch": { "version": "5.3.0", @@ -58860,22 +57892,6 @@ "picomatch": "^2.2.1" } }, - "recast": { - "version": "0.20.4", - "dev": true, - "requires": { - "ast-types": "0.14.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "dev": true - } - } - }, "rechoir": { "version": "0.6.2", "devOptional": true, @@ -59643,10 +58659,6 @@ } } }, - "ret": { - "version": "0.1.15", - "dev": true - }, "retes": { "version": "0.33.0", "dev": true, @@ -59777,13 +58789,6 @@ "version": "5.2.1", "devOptional": true }, - "safe-regex": { - "version": "1.1.0", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safe-regex-test": { "version": "1.0.0", "devOptional": true, @@ -60438,17 +59443,6 @@ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true }, - "source-map-resolve": { - "version": "0.5.3", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "source-map-support": { "version": "0.5.21", "devOptional": true, @@ -60463,10 +59457,6 @@ } } }, - "source-map-url": { - "version": "0.4.1", - "dev": true - }, "sourcemap-codec": { "version": "1.4.8", "dev": true @@ -62223,42 +61213,6 @@ } } }, - "unset-value": { - "version": "1.0.0", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "dev": true - } - } - }, "untildify": { "version": "4.0.0", "optional": true @@ -62350,10 +61304,6 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "dev": true - }, "url-join": { "version": "4.0.1" }, diff --git a/package.json b/package.json index 427e811b9e0..384ad7534e7 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "fast-array-diff": "^0.2.0", "find-test-names": "^1.17.1", "fuse.js": "^6.6.2", - "fuzzaldrin": "^2.1.0", "graphiql": "^2.2.0", "graphql": "^15.4.0", "hotkeys-js": "^3.8.1", @@ -127,9 +126,7 @@ "@types/apollo-upload-client": "^17.0.2", "@types/color-convert": "^2.0.0", "@types/debug": "^4.1.7", - "@types/fuzzaldrin": "^2.1.4", "@types/is-ci": "^3.0.0", - "@types/jscodeshift": "^0.11.3", "@types/lodash-es": "^4.17.3", "@types/react": "^17.0.50", "@types/react-dom": "^17.0.17", diff --git a/src/components/ChannelsAvailabilityContent/ChannelsAvailabilityContent.tsx b/src/components/ChannelsAvailabilityContent/ChannelsAvailabilityContent.tsx index 52768a740da..fc0b7b09db4 100644 --- a/src/components/ChannelsAvailabilityContent/ChannelsAvailabilityContent.tsx +++ b/src/components/ChannelsAvailabilityContent/ChannelsAvailabilityContent.tsx @@ -1,8 +1,8 @@ import { Channel } from "@dashboard/channels/utils"; import { ControlledCheckbox } from "@dashboard/components/ControlledCheckbox"; import Hr from "@dashboard/components/Hr"; +import { fuzzySearch } from "@dashboard/misc"; import { TextField, Typography } from "@material-ui/core"; -import { filter } from "fuzzaldrin"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -19,7 +19,9 @@ export interface ChannelsAvailabilityContentProps { toggleAll?: (items: Channel[], selected: number) => void; } -export const ChannelsAvailabilityContent: React.FC = ({ +export const ChannelsAvailabilityContent: React.FC< + ChannelsAvailabilityContentProps +> = ({ isSelected, channels, contentType = "", @@ -35,7 +37,7 @@ export const ChannelsAvailabilityContent: React.FC @@ -85,8 +87,8 @@ export const ChannelsAvailabilityContent: React.FC - {filteredChannels?.length ? ( - filteredChannels.map(option => ( + {searchResults.length ? ( + searchResults.map(option => (
( +export const useChannelsSearch = function ( channels: T[], ) { const [query, onQueryChange] = React.useState(""); - const filteredChannels = filter(channels, query, { key: "name" }); + const filteredChannels = fuzzySearch(channels, query, ["name"]); return { query, onQueryChange, filteredChannels }; }; diff --git a/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.tsx b/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.tsx index e2f4e7d327e..8240323e6be 100644 --- a/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.tsx +++ b/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore import Debounce, { DebounceProps } from "@dashboard/components/Debounce"; +import { fuzzySearch } from "@dashboard/misc"; import { FetchMoreProps } from "@dashboard/types"; import { Popper, @@ -11,7 +12,6 @@ import CloseIcon from "@material-ui/icons/Close"; import { ChevronIcon, IconButton } from "@saleor/macaw-ui"; import clsx from "clsx"; import Downshift, { ControllerStateAndHelpers } from "downshift"; -import { filter } from "fuzzaldrin"; import React from "react"; import MultiAutocompleteSelectFieldContent, { @@ -284,9 +284,7 @@ const MultiAutocompleteSelectField: React.FC< return ( setQuery(q || "")} - choices={filter(choices, query, { - key: "label", - })} + choices={fuzzySearch(choices, query, ["label"])} {...props} /> ); diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index 887af97c4d6..e9ad5912eac 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -1,12 +1,13 @@ // @ts-strict-ignore import useLocalStorage from "@dashboard/hooks/useLocalStorage"; import useNotifier from "@dashboard/hooks/useNotifier"; -import { Divider, Fade, Modal, Paper } from "@material-ui/core"; +import { Fade, Modal, Paper } from "@material-ui/core"; import { makeStyles, useTheme } from "@saleor/macaw-ui"; -import Downshift from "downshift"; +import { Box, Divider } from "@saleor/macaw-ui/next"; +import Downshift, { GetItemPropsOptions } from "downshift"; import hotkeys from "hotkeys-js"; import React from "react"; -import { useIntl } from "react-intl"; +import { IntlShape, useIntl } from "react-intl"; import { getActions, @@ -26,13 +27,6 @@ import useQuickSearch from "./useQuickSearch"; const navigatorHotkey = "ctrl+k, command+k"; const navigatorNotificationStorageKey = "notifiedAboutNavigator"; -function getItemOffset( - actions: QuickSearchAction[], - cbs: Array, -): number { - return cbs.reduce((acc, cb) => cb(actions).length + acc, 0); -} - const useStyles = makeStyles( theme => ({ modal: { @@ -59,13 +53,79 @@ const useStyles = makeStyles( }, ); +const Sections = ({ + actions, + intl, + getItemProps, + highlightedIndex, +}: { + actions: QuickSearchAction[]; + intl: IntlShape; + getItemProps: (options: GetItemPropsOptions) => any; + highlightedIndex: number; +}) => { + const sectionsToRender = [ + { + label: intl.formatMessage({ + id: "YYkkhx", + defaultMessage: "Navigate to", + description: "navigator section header", + }), + check: hasViews, + fn: getViews, + }, + { + label: intl.formatMessage({ + id: "me585h", + defaultMessage: "Quick Actions", + description: "navigator section header", + }), + check: hasActions, + fn: getActions, + }, + { + label: intl.formatMessage({ + id: "4gT3eD", + defaultMessage: "Search in Customers", + description: "navigator section header", + }), + check: hasCustomers, + fn: getCustomers, + }, + { + label: intl.formatMessage({ + id: "7Oorx5", + defaultMessage: "Search in Catalog", + description: "navigator section header", + }), + check: hasCatalog, + fn: getCatalog, + }, + ].filter(({ check }) => check(actions)); + + return ( + <> + {sectionsToRender.map(({ label, fn }, index) => ( + + ))} + + ); +}; + export interface NavigatorProps { visible: boolean; setVisibility: (state: boolean) => void; } const Navigator: React.FC = ({ visible, setVisibility }) => { - const input = React.useRef(null); + const input = React.useRef(null); const [query, mode, change, actions] = useQuickSearch(visible, input); const intl = useIntl(); const notify = useNotifier(); @@ -130,7 +190,8 @@ const Navigator: React.FC = ({ visible, setVisibility }) => { item ? item.label : "" } onSelect={(item: QuickSearchAction) => { - const shouldRemainVisible = item.onClick(); + const shouldRemainVisible = item?.onClick(); + if (!shouldRemainVisible) { setVisibility(false); } @@ -145,70 +206,43 @@ const Navigator: React.FC = ({ visible, setVisibility }) => { } defaultHighlightedIndex={0} > - {({ getInputProps, getItemProps, highlightedIndex }) => ( -
- )} - ref={input} - /> - {hasAnything && } - {hasViews(actions) && ( - ( + + + - )} - {hasActions(actions) && ( - - )} - {hasCustomers(actions) && ( - - )} - {hasCatalog(actions) && ( - + + {hasAnything && } + + + - )} -
+ + )} diff --git a/src/components/Navigator/NavigatorInput.tsx b/src/components/Navigator/NavigatorInput.tsx index 8a76538691c..7e139568856 100644 --- a/src/components/Navigator/NavigatorInput.tsx +++ b/src/components/Navigator/NavigatorInput.tsx @@ -1,135 +1,65 @@ -// @ts-strict-ignore -import { makeStyles, SearchLargeIcon } from "@saleor/macaw-ui"; +import { + Box, + InputProps, + SearchIcon, + sprinkles, + Text, +} from "@saleor/macaw-ui/next"; import React from "react"; import { useIntl } from "react-intl"; +import { getModePlaceholder, getModeSymbol } from "./modes/utils"; import { QuickSearchMode } from "./types"; -const useStyles = makeStyles( - theme => { - const typography = { - ...theme.typography.body1, - color: theme.palette.saleor.main[1], - fontWeight: 500, - letterSpacing: "0.02rem", - }; - - return { - adornment: { - ...typography, - alignSelf: "center", - color: theme.palette.text.secondary, - marginRight: theme.spacing(1), - textAlign: "center", - width: 32, - }, - input: { - "&::placeholder": { - color: theme.palette.saleor.main[3], - }, - ...typography, - background: "transparent", - border: "none", - outline: 0, - padding: 0, - width: "100%", - }, - root: { - background: theme.palette.background.paper, - display: "flex", - padding: theme.spacing(2, 3), - height: 72, - }, - searchIcon: { - alignSelf: "center", - width: 32, - height: 32, - marginRight: theme.spacing(1), - }, - }; - }, - { - name: "NavigatorInput", - }, -); - -interface NavigatorInputProps - extends React.InputHTMLAttributes { +interface NavigatorSearchInputProps + extends Omit< + InputProps, + "size" | "height" | "width" | "label" | "as" | "type" | "color" + > { mode: QuickSearchMode; } -const NavigatorInput = React.forwardRef( - (props, ref) => { - const { mode, ...rest } = props; - const classes = useStyles(props); - const intl = useIntl(); +const NavigatorInput = React.forwardRef< + HTMLInputElement, + NavigatorSearchInputProps +>((props, ref) => { + const { mode, ...rest } = props; + const intl = useIntl(); - return ( -
- {mode !== "default" ? ( - - {mode === "orders" - ? "#" - : mode === "customers" - ? "@" - : mode === "catalog" - ? "$" - : mode === "help" - ? "?" - : ">"} - - ) : ( - - )} - + {mode !== "default" ? ( + + {getModeSymbol(mode)} + + ) : ( + -
- ); - }, -); + )} + + + ); +}); NavigatorInput.displayName = "NavigatorInput"; export default NavigatorInput; diff --git a/src/components/Navigator/NavigatorSection.tsx b/src/components/Navigator/NavigatorSection.tsx index 678fdb3ba02..c54a58744a9 100644 --- a/src/components/Navigator/NavigatorSection.tsx +++ b/src/components/Navigator/NavigatorSection.tsx @@ -1,11 +1,10 @@ -import { MenuItem, Typography } from "@material-ui/core"; -import { makeStyles } from "@saleor/macaw-ui"; +import { Box, Text } from "@saleor/macaw-ui/next"; import { GetItemPropsOptions } from "downshift"; import React from "react"; import { QuickSearchAction } from "./types"; -interface NavigatorSectionProps { +interface NavigatorSearchSectionProps { getItemProps: (options: GetItemPropsOptions) => any; highlightedIndex: number; label: string; @@ -13,57 +12,20 @@ interface NavigatorSectionProps { offset: number; } -const useStyles = makeStyles( - theme => ({ - item: { - "&&&&": { - color: theme.palette.text.secondary, - fontWeight: 400, - }, - display: "flex", - margin: theme.spacing(1, 0), - }, - itemLabel: { - display: "inline-block", - }, - label: { - paddingLeft: theme.spacing(2), - textTransform: "uppercase", - }, - root: { - "&:last-child": { - marginBottom: 0, - }, - margin: theme.spacing(2, 0), - }, - spacer: { - flex: 1, - }, - symbol: { - display: "inline-block", - fontWeight: 600, - width: theme.spacing(4), - }, - }), - { - name: "NavigatorSection", - }, -); - -const NavigatorSection: React.FC = props => { +const NavigatorSearchSection: React.FC = props => { const { getItemProps, highlightedIndex, label, items, offset } = props; - const classes = useStyles(props); - return ( -
- + {label} - + + {items.map((item, itemIndex) => { const index = offset + itemIndex; const itemProps = getItemProps({ @@ -72,29 +34,53 @@ const NavigatorSection: React.FC = props => { }); return ( - - + {item.symbol && ( - {item.symbol} + + {item.symbol} + )} - {item.label} + + {item.label} + {item.caption && ( - {item.caption} + + {item.caption} + )} - - + + + + {item.extraInfo} - + ); })} -
+ ); }; -NavigatorSection.displayName = "NavigatorSection"; -export default NavigatorSection; +NavigatorSearchSection.displayName = "NavigatorSection"; +export default NavigatorSearchSection; diff --git a/src/components/Navigator/modes/catalog.ts b/src/components/Navigator/modes/catalog.ts index 8f149ed0ea0..8a1171eb146 100644 --- a/src/components/Navigator/modes/catalog.ts +++ b/src/components/Navigator/modes/catalog.ts @@ -3,16 +3,13 @@ import { categoryUrl } from "@dashboard/categories/urls"; import { collectionUrl } from "@dashboard/collections/urls"; import { SearchCatalogQuery } from "@dashboard/graphql"; import { UseNavigatorResult } from "@dashboard/hooks/useNavigator"; +import { fuzzySearch } from "@dashboard/misc"; import { productUrl } from "@dashboard/products/urls"; import { mapEdgesToItems } from "@dashboard/utils/maps"; -import { score } from "fuzzaldrin"; import { IntlShape } from "react-intl"; import { QuickSearchAction, QuickSearchActionInput } from "../types"; import messages from "./messages"; -import { sortScores } from "./utils"; - -const maxActions = 5; export function searchInCatalog( search: string, @@ -22,65 +19,48 @@ export function searchInCatalog( ): QuickSearchAction[] { const categories: QuickSearchActionInput[] = ( mapEdgesToItems(catalog?.categories) || [] - ) - .map(category => ({ - caption: intl.formatMessage(messages.category), - label: category.name, - onClick: () => { - navigate(categoryUrl(category.id)); - return false; - }, - score: score(category.name, search), - text: category.name, - type: "catalog", - })) - .sort(sortScores); + ).map(category => ({ + caption: intl.formatMessage(messages.category), + label: category.name, + onClick: () => { + navigate(categoryUrl(category.id)); + return false; + }, + text: category.name, + type: "catalog", + })); const collections: QuickSearchActionInput[] = ( mapEdgesToItems(catalog?.collections) || [] - ) - .map(collection => ({ - caption: intl.formatMessage(messages.collection), - label: collection.name, - onClick: () => { - navigate(collectionUrl(collection.id)); - return false; - }, - score: score(collection.name, search), - text: collection.name, - type: "catalog", - })) - .sort(sortScores); + ).map(collection => ({ + caption: intl.formatMessage(messages.collection), + label: collection.name, + onClick: () => { + navigate(collectionUrl(collection.id)); + return false; + }, + text: collection.name, + type: "catalog", + })); const products: QuickSearchActionInput[] = ( mapEdgesToItems(catalog?.products) || [] - ) - .map(product => ({ - caption: intl.formatMessage(messages.product), - extraInfo: product.category.name, - label: product.name, - onClick: () => { - navigate(productUrl(product.id)); - return false; - }, - score: score(product.name, search), - text: product.name, - type: "catalog", - })) - .sort(sortScores); + ).map(product => ({ + caption: intl.formatMessage(messages.product), + extraInfo: product.category.name, + label: product.name, + onClick: () => { + navigate(productUrl(product.id)); + + return false; + }, + text: product.name, + type: "catalog", + })); - const baseActions = [ - ...categories.slice(0, 1), - ...collections.slice(0, 1), - ...products.slice(0, 1), - ]; + const searchableItems = [...categories, ...collections, ...products]; - return [ - ...baseActions, - ...[...categories.slice(1), ...collections.slice(1), ...products.slice(1)] - .sort(sortScores) - .slice(0, maxActions - baseActions.length), - ].sort(sortScores); + return fuzzySearch(searchableItems, search, ["label"]); } function getCatalogModeActions( diff --git a/src/components/Navigator/modes/commands/actions.test.ts b/src/components/Navigator/modes/commands/actions.test.ts new file mode 100644 index 00000000000..9a63f6eb7df --- /dev/null +++ b/src/components/Navigator/modes/commands/actions.test.ts @@ -0,0 +1,43 @@ +import { useIntl } from "react-intl"; + +import getCommandModeActions from "./actions"; + +jest.mock("react-intl", () => ({ + useIntl: jest.fn(() => ({ + formatMessage: jest.fn(x => x.defaultMessage), + })), + defineMessages: jest.fn(message => message), +})); + +describe("actions search (scoring)", () => { + it("returns options when no query is given", () => { + // Arrange & Act + const intl = useIntl(); + const actions = getCommandModeActions( + "", + intl, + jest.fn(), + jest.fn(), + jest.fn(), + ); + + // Assert + expect(actions.length).toBe(5); + }); + + it("searches, returns only related results", () => { + // Arrange & Act + const intl = useIntl(); + const actions = getCommandModeActions( + "Category", + intl, + jest.fn(), + jest.fn(), + jest.fn(), + ); + + // Assert + expect(actions.length).toBe(1); + expect(actions[0].label).toBe("Create Category"); + }); +}); diff --git a/src/components/Navigator/modes/commands/actions.ts b/src/components/Navigator/modes/commands/actions.ts index 9c878b34844..fb41db968b8 100644 --- a/src/components/Navigator/modes/commands/actions.ts +++ b/src/components/Navigator/modes/commands/actions.ts @@ -5,16 +5,14 @@ import { customerAddUrl } from "@dashboard/customers/urls"; import { voucherAddUrl } from "@dashboard/discounts/urls"; import { OrderDraftCreateMutation } from "@dashboard/graphql"; import { UseNavigatorResult } from "@dashboard/hooks/useNavigator"; +import { fuzzySearch } from "@dashboard/misc"; import { permissionGroupAddUrl } from "@dashboard/permissionGroups/urls"; import { productAddUrl } from "@dashboard/products/urls"; -import { score } from "fuzzaldrin"; import { IntlShape } from "react-intl"; import { QuickSearchActionInput, QuickSearchMode } from "../../types"; import messages from "../messages"; -import { sortScores } from "../utils"; -const threshold = 0.05; const maxActions = 5; interface Command { @@ -87,10 +85,9 @@ export function searchInCommands( }, ]; - return actions.map(action => ({ + return fuzzySearch(actions, search, ["label"]).map(action => ({ label: action.label, onClick: action.onClick, - score: score(action.label, search), text: action.label, type: "action", })); @@ -103,10 +100,9 @@ function getCommandModeActions( createOrder: MutationFunction, setMode: (mode: QuickSearchMode) => void, ): QuickSearchActionInput[] { - return [...searchInCommands(query, intl, navigate, createOrder, setMode)] - .filter(action => action.score >= threshold) - .sort(sortScores) - .slice(0, maxActions); + return [ + ...searchInCommands(query, intl, navigate, createOrder, setMode), + ].slice(0, maxActions); } export default getCommandModeActions; diff --git a/src/components/Navigator/modes/customers.ts b/src/components/Navigator/modes/customers.ts index e3c1abaa281..95d590bcd65 100644 --- a/src/components/Navigator/modes/customers.ts +++ b/src/components/Navigator/modes/customers.ts @@ -26,7 +26,6 @@ export function searchInCustomers( navigate(customerUrl(customer.id)); return false; }, - score: 1, type: "customer", })); } diff --git a/src/components/Navigator/modes/default/default.ts b/src/components/Navigator/modes/default/default.ts index 7642ad9ed9e..c000a70218f 100644 --- a/src/components/Navigator/modes/default/default.ts +++ b/src/components/Navigator/modes/default/default.ts @@ -1,14 +1,13 @@ import { MutationFunction } from "@apollo/client"; import { OrderDraftCreateMutation } from "@dashboard/graphql"; import { UseNavigatorResult } from "@dashboard/hooks/useNavigator"; +import { fuzzySearch } from "@dashboard/misc"; import { IntlShape } from "react-intl"; import { QuickSearchAction, QuickSearchMode } from "../../types"; import { searchInCommands } from "../commands"; -import { sortScores } from "../utils"; import searchInViews from "./views"; -const threshold = 0.05; const maxActions = 5; function getDefaultModeActions( @@ -18,13 +17,14 @@ function getDefaultModeActions( createOrder: MutationFunction, setMode: (mode: QuickSearchMode) => void, ): QuickSearchAction[] { - return [ - ...searchInViews(query, intl, navigate), - ...searchInCommands(query, intl, navigate, createOrder, setMode), - ] - .filter(action => action.score >= threshold) - .sort(sortScores) - .slice(0, maxActions); + return fuzzySearch( + [ + ...searchInViews(query, intl, navigate), + ...searchInCommands(query, intl, navigate, createOrder, setMode), + ], + query, + ["label"], + ).slice(0, maxActions); } export default getDefaultModeActions; diff --git a/src/components/Navigator/modes/default/views.ts b/src/components/Navigator/modes/default/views.ts index bc8357009ba..89c72c8f7a3 100644 --- a/src/components/Navigator/modes/default/views.ts +++ b/src/components/Navigator/modes/default/views.ts @@ -6,6 +6,7 @@ import { customerListUrl } from "@dashboard/customers/urls"; import { saleListUrl, voucherListUrl } from "@dashboard/discounts/urls"; import { UseNavigatorResult } from "@dashboard/hooks/useNavigator"; import { sectionNames } from "@dashboard/intl"; +import { fuzzySearch } from "@dashboard/misc"; import { menuListUrl } from "@dashboard/navigation/urls"; import { orderDraftListUrl, orderListUrl } from "@dashboard/orders/urls"; import { pageListUrl } from "@dashboard/pages/urls"; @@ -19,7 +20,6 @@ import { staffListUrl } from "@dashboard/staff/urls"; import { taxConfigurationListUrl } from "@dashboard/taxes/urls"; import { languageListUrl } from "@dashboard/translations/urls"; import { warehouseListUrl } from "@dashboard/warehouses/urls"; -import { score } from "fuzzaldrin"; import { IntlShape } from "react-intl"; import { QuickSearchActionInput } from "../../types"; @@ -124,13 +124,12 @@ function searchInViews( }, ]; - return views.map(view => ({ + return fuzzySearch(views, search, ["label"]).map(view => ({ label: view.label, onClick: () => { navigate(view.url); return false; }, - score: score(view.label, search), text: view.label, type: "view", })); diff --git a/src/components/Navigator/modes/utils.ts b/src/components/Navigator/modes/utils.ts index 1192b9a0cbd..79b18fde755 100644 --- a/src/components/Navigator/modes/utils.ts +++ b/src/components/Navigator/modes/utils.ts @@ -1,9 +1,7 @@ // @ts-strict-ignore -import { - QuickSearchAction, - QuickSearchActionInput, - QuickSearchMode, -} from "../types"; +import { IntlShape } from "react-intl"; + +import { QuickSearchAction, QuickSearchMode } from "../types"; export function getActions(actions: QuickSearchAction[]): QuickSearchAction[] { return actions.filter(action => action.type === "action"); @@ -35,13 +33,6 @@ export function hasCatalog(actions: QuickSearchAction[]): boolean { return getCatalog(actions).length > 0; } -export function sortScores( - a: QuickSearchActionInput, - b: QuickSearchActionInput, -) { - return a.score <= b.score ? 1 : -1; -} - export function getMode(command: string): QuickSearchMode { switch (command) { case ">": @@ -59,3 +50,60 @@ export function getMode(command: string): QuickSearchMode { return null; } } + +export const getModePlaceholder = (mode: QuickSearchMode, intl: IntlShape) => { + switch (mode) { + case "orders": + return intl.formatMessage({ + id: "8B8E+3", + defaultMessage: "Order Number", + description: "navigator placeholder", + }); + case "commands": + return intl.formatMessage({ + id: "NqxvFh", + defaultMessage: "Type Command", + description: "navigator placeholder", + }); + case "catalog": + return intl.formatMessage({ + id: "AOI4LW", + defaultMessage: "Search in Catalog", + description: "navigator placeholder", + }); + case "customers": + return intl.formatMessage({ + id: "TpPx7V", + defaultMessage: "Search Customer", + description: "navigator placeholder", + }); + case "default": + return intl.formatMessage( + { + id: "BooQvo", + defaultMessage: "Type {key} to see available actions", + description: "navigator placeholder", + }, + { + key: "'?'", + }, + ); + default: + return null; + } +}; + +export const getModeSymbol = (mode: QuickSearchMode) => { + switch (mode) { + case "orders": + return "#"; + case "customers": + return "@"; + case "catalog": + return "$"; + case "help": + return "?"; + default: + return ">"; + } +}; diff --git a/src/components/Navigator/types.ts b/src/components/Navigator/types.ts index a333e29a397..927f9afaa09 100644 --- a/src/components/Navigator/types.ts +++ b/src/components/Navigator/types.ts @@ -11,7 +11,6 @@ export interface QuickSearchAction { } export interface QuickSearchActionInput extends QuickSearchAction { - score: number; text: string; } diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts index 2a3c447f978..b85a22f5dba 100644 --- a/src/components/Navigator/useQuickSearch.ts +++ b/src/components/Navigator/useQuickSearch.ts @@ -36,11 +36,11 @@ function useQuickSearch( const { result: customers, search: searchCustomers } = useCustomerSearch({ variables: { ...DEFAULT_INITIAL_SEARCH_DATA, - first: 5, + first: 10, }, skip: !query, }); - const [{ data: catalog }, searchCatalog] = useSearchCatalog(5); + const [{ data: catalog }, searchCatalog] = useSearchCatalog(10); const [createOrder] = useOrderDraftCreateMutation({ onCompleted: result => { if (result.draftOrderCreate.errors.length === 0) { diff --git a/src/discounts/components/DiscountCountrySelectDialog/DiscountCountrySelectDialog.tsx b/src/discounts/components/DiscountCountrySelectDialog/DiscountCountrySelectDialog.tsx index 8af139f0e79..ec53aeae727 100644 --- a/src/discounts/components/DiscountCountrySelectDialog/DiscountCountrySelectDialog.tsx +++ b/src/discounts/components/DiscountCountrySelectDialog/DiscountCountrySelectDialog.tsx @@ -12,6 +12,7 @@ import ResponsiveTable from "@dashboard/components/ResponsiveTable"; import TableRowLink from "@dashboard/components/TableRowLink"; import { CountryWithCodeFragment } from "@dashboard/graphql"; import { SubmitPromise } from "@dashboard/hooks/useForm"; +import { fuzzySearch } from "@dashboard/misc"; import useScrollableDialogStyle from "@dashboard/styles/useScrollableDialogStyle"; import { Dialog, @@ -23,7 +24,6 @@ import { TextField, Typography, } from "@material-ui/core"; -import { filter } from "fuzzaldrin"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -123,48 +123,49 @@ const DiscountCountrySelectDialog: React.FC< - {filter(countries, data.query, { - key: "country", - }).map(country => { - const isChecked = countrySelectionMap[country.code]; + {fuzzySearch(countries, data.query, ["country"]).map( + country => { + const isChecked = countrySelectionMap[country.code]; - return ( - - - {country.country} - - - - isChecked - ? change({ - target: { - name: "countries" as keyof FormData, - value: data.countries.filter( - selectedCountries => - selectedCountries !== country.code, - ), - }, - } as any) - : change({ - target: { - name: "countries" as keyof FormData, - value: [ - ...data.countries, - country.code, - ], - }, - } as any) - } - /> - - - ); - })} + return ( + + + {country.country} + + + + isChecked + ? change({ + target: { + name: "countries" as keyof FormData, + value: data.countries.filter( + selectedCountries => + selectedCountries !== + country.code, + ), + }, + } as any) + : change({ + target: { + name: "countries" as keyof FormData, + value: [ + ...data.countries, + country.code, + ], + }, + } as any) + } + /> + + + ); + }, + )} diff --git a/src/hooks/useChannelsSearch.ts b/src/hooks/useChannelsSearch.ts index 8534ba0a316..5325cfa4c9a 100644 --- a/src/hooks/useChannelsSearch.ts +++ b/src/hooks/useChannelsSearch.ts @@ -1,14 +1,13 @@ import { ChannelDetailsFragment } from "@dashboard/graphql"; +import { fuzzySearch } from "@dashboard/misc"; import { FetchMoreProps, Search, SearchProps } from "@dashboard/types"; -import { filter } from "fuzzaldrin"; import React from "react"; -export const useChannelsSearch = function( +export const useChannelsSearch = function ( channels: T[], ) { const [query, onQueryChange] = React.useState(""); - const filteredChannels = - filter(channels, query, { key: "name" }) || []; + const filteredChannels = fuzzySearch(channels, query, ["name"]) || []; return { query, onQueryChange, filteredChannels }; }; diff --git a/src/hooks/useChoiceSearch.ts b/src/hooks/useChoiceSearch.ts index 91602ac4f11..b34bee4937c 100644 --- a/src/hooks/useChoiceSearch.ts +++ b/src/hooks/useChoiceSearch.ts @@ -1,12 +1,12 @@ import { Choice } from "@dashboard/components/SingleSelectField"; -import { filter } from "fuzzaldrin"; +import { fuzzySearch } from "@dashboard/misc"; import { useMemo, useState } from "react"; function useChoiceSearch(choices: Array>) { const [query, setQuery] = useState(""); const sortedChoices = useMemo( - () => filter(choices, query, { key: "label" }) || [], + () => fuzzySearch(choices, query, ["label"]) || [], [choices, query], ); diff --git a/src/misc.ts b/src/misc.ts index e7020837269..98a69e83519 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -10,6 +10,7 @@ import { import { Node, SlugNode } from "@dashboard/types"; import { ThemeType } from "@saleor/macaw-ui"; import { DefaultTheme, ThemeTokensValues } from "@saleor/macaw-ui/next"; +import Fuse from "fuse.js"; import uniqBy from "lodash/uniqBy"; import moment from "moment-timezone"; import { IntlShape } from "react-intl"; @@ -603,3 +604,21 @@ export const getDatagridRowDataIndex = ( ) => rowIndex + getAllRemovedRowsBeforeRowIndex(rowIndex, removedRowsIndexs).length; + +export const fuzzySearch = ( + array: T[], + query: string | undefined, + keys: string[], +) => { + if (!query) { + return array; + } + + const fuse = new Fuse(array, { + keys, + includeScore: true, + threshold: 0.3, + }); + + return fuse.search(query.toLocaleLowerCase()).map(({ item }) => item); +}; diff --git a/src/shipping/components/ShippingZoneCountriesAssignDialog/ShippingZoneCountriesAssignDialog.tsx b/src/shipping/components/ShippingZoneCountriesAssignDialog/ShippingZoneCountriesAssignDialog.tsx index 6c72c1b1e74..7ff8dedafc4 100644 --- a/src/shipping/components/ShippingZoneCountriesAssignDialog/ShippingZoneCountriesAssignDialog.tsx +++ b/src/shipping/components/ShippingZoneCountriesAssignDialog/ShippingZoneCountriesAssignDialog.tsx @@ -11,6 +11,7 @@ import Hr from "@dashboard/components/Hr"; import ResponsiveTable from "@dashboard/components/ResponsiveTable"; import TableRowLink from "@dashboard/components/TableRowLink"; import { CountryWithCodeFragment } from "@dashboard/graphql"; +import { fuzzySearch } from "@dashboard/misc"; import { getCountrySelectionMap, isRestWorldCountriesSelected, @@ -26,7 +27,6 @@ import { TextField, Typography, } from "@material-ui/core"; -import { filter } from "fuzzaldrin"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -100,6 +100,9 @@ const ShippingZoneCountriesAssignDialog: React.FC< restWorldCountries, change, ); + const displayCountries = fuzzySearch(countries, data.query, [ + "country", + ]); return ( <> @@ -172,9 +175,7 @@ const ShippingZoneCountriesAssignDialog: React.FC< - {filter(countries, data.query, { - key: "country", - }).map(country => { + {displayCountries.map(country => { const isChecked = countrySelectionMap[country.code]; return ( diff --git a/src/utils/misc.test.ts b/src/utils/misc.test.ts new file mode 100644 index 00000000000..5d0cd57c5c7 --- /dev/null +++ b/src/utils/misc.test.ts @@ -0,0 +1,27 @@ +import { fuzzySearch } from "@dashboard/misc"; + +describe("fuzzySearch", () => { + it("searches for a result in array of objects", () => { + // Arrange + const array = [{ name: "banana" }, { name: "apple" }, { name: "orange" }]; + const search = "ban"; + + // Act + const result = fuzzySearch(array, search, ["name"]); + + // Assert + expect(result).toStrictEqual([{ name: "banana" }]); + }); + + it("returns empty array if no results found", () => { + // Assert + const array = [{ name: "banana" }, { name: "apple" }, { name: "orange" }]; + const search = "kiwi"; + + // Act + const result = fuzzySearch(array, search, ["name"]); + + // Assert + expect(result).toStrictEqual([]); + }); +});