From 32dbe76d1d4ca6ce9540c981141c504828912b0f Mon Sep 17 00:00:00 2001 From: Tyson Date: Mon, 13 Jan 2020 23:09:54 +1100 Subject: [PATCH] feat(dropdown): refactored dropdown (#177) - Moved dropdown to be called select - Created new components, ButtonBase, List, ListItem, MenuItem - No contract changes to propTypes - Custom PropType Validation for deprecation/unimplemented features - Added react-testing-library to improve unit/functional testing --- .eslintrc | 4 +- package-lock.json | 321 ++++++++- package.json | 4 +- src/components/ButtonBase/index.js | 47 ++ .../__snapshots__/Dropdown.spec.js.snap | 590 ---------------- src/components/Dropdown/index.js | 358 +--------- src/components/List/__tests__/List.spec.js | 14 + .../__tests__/__snapshots__/List.spec.js.snap | 26 + src/components/List/index.js | 40 ++ .../ListItem/__tests__/ListItem.spec.js | 11 + .../__snapshots__/ListItem.spec.js.snap | 3 + src/components/ListItem/index.js | 46 ++ src/components/MenuItem/README.md | 2 + .../MenuItem/__tests__/MenuItem.spec.js | 14 + .../__snapshots__/MenuItem.spec.js.snap | 110 +++ src/components/MenuItem/index.js | 57 ++ src/components/{Dropdown => Select}/README.md | 8 +- .../__tests__/Select.spec.js} | 73 +- .../__snapshots__/Select.spec.js.snap | 642 ++++++++++++++++++ .../components/SelectOnKeyPressContainer.js | 0 src/components/Select/index.js | 286 ++++++++ .../base/shouldNotBeDefinedPropType.js | 19 + src/utils/propTypes/deprecatedPropType.js | 7 + src/utils/propTypes/index.js | 2 + src/utils/propTypes/unimplementedPropType.js | 9 + 25 files changed, 1704 insertions(+), 989 deletions(-) create mode 100644 src/components/ButtonBase/index.js delete mode 100644 src/components/Dropdown/__tests__/__snapshots__/Dropdown.spec.js.snap create mode 100644 src/components/List/__tests__/List.spec.js create mode 100644 src/components/List/__tests__/__snapshots__/List.spec.js.snap create mode 100644 src/components/List/index.js create mode 100644 src/components/ListItem/__tests__/ListItem.spec.js create mode 100644 src/components/ListItem/__tests__/__snapshots__/ListItem.spec.js.snap create mode 100644 src/components/ListItem/index.js create mode 100644 src/components/MenuItem/README.md create mode 100644 src/components/MenuItem/__tests__/MenuItem.spec.js create mode 100644 src/components/MenuItem/__tests__/__snapshots__/MenuItem.spec.js.snap create mode 100644 src/components/MenuItem/index.js rename src/components/{Dropdown => Select}/README.md (85%) rename src/components/{Dropdown/__tests__/Dropdown.spec.js => Select/__tests__/Select.spec.js} (54%) create mode 100644 src/components/Select/__tests__/__snapshots__/Select.spec.js.snap rename src/components/{Dropdown => Select}/components/SelectOnKeyPressContainer.js (100%) create mode 100644 src/components/Select/index.js create mode 100644 src/utils/propTypes/base/shouldNotBeDefinedPropType.js create mode 100644 src/utils/propTypes/deprecatedPropType.js create mode 100644 src/utils/propTypes/index.js create mode 100644 src/utils/propTypes/unimplementedPropType.js diff --git a/.eslintrc b/.eslintrc index 6d35beec..ed6bb98a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,6 +15,8 @@ "react/destructuring-assignment": "ignoreClassFields", "react/forbid-foreign-prop-types": "off", "react/jsx-filename-extension": "off", - "prettier/prettier": ["error"] + "prettier/prettier": ["error"], + "import/prefer-default-export": "off", + "react/require-default-props": "off" } } diff --git a/package-lock.json b/package-lock.json index d312c885..0c0abbea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3070,6 +3070,178 @@ } } }, + "@sheerun/mutationobserver-shim": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", + "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==", + "dev": true + }, + "@testing-library/dom": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.11.0.tgz", + "integrity": "sha512-Pkx9LMIGshyNbfmecjt18rrAp/ayMqGH674jYER0SXj0iG9xZc+zWRjk2Pg9JgPBDvwI//xGrI/oOQkAi4YEew==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.2", + "@sheerun/mutationobserver-shim": "^0.3.2", + "@types/testing-library__dom": "^6.0.0", + "aria-query": "3.0.0", + "pretty-format": "^24.9.0", + "wait-for-expect": "^3.0.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.0.tgz", + "integrity": "sha512-Z7ti+HB0puCcLmFE3x90kzaVgbx6TRrYIReaygW6EkBEnJh1ajS4/inhF7CypzWeDV3NFl1AfWj0eMtdihojxw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + } + } + }, + "@testing-library/jest-dom": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz", + "integrity": "sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.1", + "chalk": "^2.4.1", + "css": "^2.2.3", + "css.escape": "^1.5.1", + "jest-diff": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "lodash": "^4.17.11", + "pretty-format": "^24.0.0", + "redent": "^3.0.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.0.tgz", + "integrity": "sha512-Z7ti+HB0puCcLmFE3x90kzaVgbx6TRrYIReaygW6EkBEnJh1ajS4/inhF7CypzWeDV3NFl1AfWj0eMtdihojxw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "dev": true + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.4.0.tgz", + "integrity": "sha512-XdhDWkI4GktUPsz0AYyeQ8M9qS/JFie06kcSnUVcpgOwFjAu9vhwR83qBl+lw9yZWkbECjL8Hd+n5hH6C0oWqg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.6", + "@testing-library/dom": "^6.11.0", + "@types/testing-library__react": "^9.1.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.0.tgz", + "integrity": "sha512-Z7ti+HB0puCcLmFE3x90kzaVgbx6TRrYIReaygW6EkBEnJh1ajS4/inhF7CypzWeDV3NFl1AfWj0eMtdihojxw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + } + } + }, "@types/babel__core": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz", @@ -3214,6 +3386,31 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "@types/react": { + "version": "16.9.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.17.tgz", + "integrity": "sha512-UP27In4fp4sWF5JgyV6pwVPAQM83Fj76JOcg02X5BZcpSu5Wx+fP9RMqc2v0ssBoQIFvD5JdKY41gjJJKmw6Bg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "@types/react-dom": { + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", + "integrity": "sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -3232,6 +3429,51 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, + "@types/testing-library__dom": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.11.1.tgz", + "integrity": "sha512-ImChHtQqmjwraRLqBC2sgSQFtczeFvBmBcfhTYZn/3KwXbyD07LQykEQ0xJo7QHc1GbVvf7pRyGaIe6PkCdxEw==", + "dev": true, + "requires": { + "pretty-format": "^24.3.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "dev": true + } + } + }, + "@types/testing-library__react": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.2.tgz", + "integrity": "sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q==", + "dev": true, + "requires": { + "@types/react-dom": "*", + "@types/testing-library__dom": "*" + } + }, "@types/yargs": { "version": "13.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.4.tgz", @@ -6165,6 +6407,12 @@ "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", "dev": true }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", + "dev": true + }, "cssesc": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", @@ -6704,7 +6952,7 @@ }, "downshift": { "version": "3.1.12", - "resolved": "https://registry.npmjs.org/downshift/-/downshift-3.1.12.tgz", + "resolved": "http://nexus.qcpaws.qantas.com.au/nexus/repository/npm-repo/downshift/-/downshift-3.1.12.tgz", "integrity": "sha512-dkDlHVIHFT9kFxvPcmlp/kIwV1Uq/aI4rC70nNQkDGZNrpcexfnv4sIVK0hkIuewr9ZNo+Keqcwr3ao476YqJQ==", "requires": { "@babel/runtime": "^7.1.2", @@ -8299,7 +8547,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -8323,13 +8572,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8346,19 +8597,22 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -8489,7 +8743,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -8503,6 +8758,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8519,6 +8775,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8527,13 +8784,15 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8554,6 +8813,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -8642,7 +8902,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -8656,6 +8917,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -8751,7 +9013,8 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -8793,6 +9056,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8814,6 +9078,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8862,13 +9127,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true + "dev": true, + "optional": true } } }, @@ -12896,6 +13163,12 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, + "min-indent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", + "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", + "dev": true + }, "mini-html-webpack-plugin": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/mini-html-webpack-plugin/-/mini-html-webpack-plugin-0.2.3.tgz", @@ -18062,12 +18335,20 @@ } }, "prop-types": { - "version": "15.6.2", - "resolved": "http://nexus.qcpaws.qantas.com.au/nexus/repository/npm-repo/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + }, + "dependencies": { + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" + } } }, "proxy-addr": { @@ -22307,6 +22588,12 @@ "browser-process-hrtime": "^0.1.2" } }, + "wait-for-expect": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz", + "integrity": "sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw==", + "dev": true + }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", diff --git a/package.json b/package.json index af709fe4..87abf8db 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "express": "^4.16.4", "highlight-words-core": "^1.2.2", "lodash.debounce": "4.0.8", - "prop-types": "^15.6.2", + "prop-types": "^15.7.2", "rc-input-number": "^4.5.1", "react-highlight-words": "^0.16.0", "react-switch": "4.1.0", @@ -64,6 +64,8 @@ "@semantic-release/changelog": "^3.0.1", "@semantic-release/git": "^7.0.5", "@semantic-release/npm": "^5.1.1", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.4.0", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.0.1", "babel-jest": "^23.6.0", diff --git a/src/components/ButtonBase/index.js b/src/components/ButtonBase/index.js new file mode 100644 index 00000000..b0d41b54 --- /dev/null +++ b/src/components/ButtonBase/index.js @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { css } from '@emotion/core'; + +export function buttonBaseStyles() { + return css({ + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + cursor: 'pointer', + padding: 0, + border: 0, + background: 'transparent', + color: 'inherit', + textDecoration: 'none', + userSelect: 'none', + verticalAlign: 'middle', + maxWidth: '100%' + }); +} + +export function ButtonBase(props) { + const { + component: Component = 'button', + children, + overrideClassName, + ...otherProps + } = props; + + return ( + + {children} + + ); +} + +ButtonBase.propTypes = { + component: PropTypes.string, + children: PropTypes.node, + className: PropTypes.string +}; + +export default ButtonBase; diff --git a/src/components/Dropdown/__tests__/__snapshots__/Dropdown.spec.js.snap b/src/components/Dropdown/__tests__/__snapshots__/Dropdown.spec.js.snap deleted file mode 100644 index a5ac1781..00000000 --- a/src/components/Dropdown/__tests__/__snapshots__/Dropdown.spec.js.snap +++ /dev/null @@ -1,590 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Dropdown renders correctly with defaults 1`] = ` -.emotion-6 { - color: #fff; - position: relative; - width: 100%; - height: 100%; - max-width: 100%; -} - -.emotion-5 { - width: 100%; - height: 100%; -} - -.emotion-4 { - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - box-sizing: border-box; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - padding: 0 15px; - font-size: 1rem; - font-weight: 400; - font-style: normal; - font-stretch: normal; - line-height: 1.56; - -webkit-letter-spacing: normal; - -moz-letter-spacing: normal; - -ms-letter-spacing: normal; - letter-spacing: normal; - background: #3c3c3c; - border: 0; - color: #fff; - -webkit-text-decoration: none; - text-decoration: none; - width: 100%; - height: 100%; - max-width: 100%; -} - -.emotion-0 { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.emotion-1 { - width: 24px; - height: 100%; - fill: #fff; - vertical-align: middle; - padding: 0; - box-sizing: content-box; -} - - - -
- - -
- -
-
-
-
-
-
-`; - -exports[`Dropdown renders correctly with props provided 1`] = ` -.emotion-5 { - width: 100%; - height: 100%; -} - -.emotion-0 { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.emotion-6 { - color: #8de2e0; - position: static; - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; - height: 20px; - max-width: 100%; -} - -.emotion-4 { - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - box-sizing: border-box; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - padding: 0; - font-size: 1rem; - font-weight: 700; - font-style: normal; - font-stretch: normal; - line-height: 1.56; - -webkit-letter-spacing: normal; - -moz-letter-spacing: normal; - -ms-letter-spacing: normal; - letter-spacing: normal; - background: #3c3c3c; - border: 0; - color: #8de2e0; - -webkit-text-decoration: underline; - text-decoration: underline; - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; - height: 20px; - max-width: 100%; -} - -.emotion-1 { - width: 24px; - height: 100%; - fill: #8de2e0; - vertical-align: middle; - padding: 0; - box-sizing: content-box; -} - - - -
- - -
- -
-
-
-
-
-
-`; diff --git a/src/components/Dropdown/index.js b/src/components/Dropdown/index.js index 5df6499f..d23942d7 100644 --- a/src/components/Dropdown/index.js +++ b/src/components/Dropdown/index.js @@ -1,357 +1 @@ -/* eslint-disable jsx-a11y/label-has-for, jsx-a11y/label-has-associated-control */ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import Downshift from 'downshift'; -import { css } from '@emotion/core'; -import { - CSS_SELECTOR_HOVER, - CSS_SELECTOR_ACTIVE, - CSS_SELECTOR_FOCUS -} from '../../constants/css'; - -import SelectOnKeyPressContainer from './components/SelectOnKeyPressContainer'; -import TickIcon from '../../icons/Tick'; -import ChevronDown from '../../icons/ChevronDown'; -import noop from '../../utils/noop'; -import { colours, layout, fontWeight, fontSize } from '../../theme/airways'; - -export function dropdownMenuContainerStyles({ - highlighted, - growMenu, - inline, - height -}) { - return css({ - label: 'runway-dropdown__container', - color: highlighted ? colours.highlights : colours.white, - position: growMenu ? 'static' : 'relative', - width: inline ? 'fit-content' : '100%', - height, - maxWidth: '100%' - }); -} - -export function buttonStyles({ highlighted, inline, height }) { - return css({ - label: 'runway-dropdown__button-wrapper', - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - boxSizing: 'border-box', - justifyContent: 'space-between', - padding: inline ? 0 : `0 ${layout.gutter}`, - fontSize: fontSize.label, - fontWeight: highlighted ? fontWeight.bold : fontWeight.regular, - fontStyle: 'normal', - fontStretch: 'normal', - lineHeight: 1.56, - letterSpacing: 'normal', - background: colours.mediumGrey, - border: 0, - color: highlighted ? colours.highlights : colours.white, - textDecoration: highlighted ? 'underline' : 'none', - width: inline ? 'fit-content' : '100%', - height, - maxWidth: '100%' - }); -} - -export function buttonSvgStyles({ highlighted }) { - return css({ - label: 'runway-dropdown__button-svg', - width: '24px', - height: '100%', - fill: highlighted ? colours.highlights : colours.white, - verticalAlign: 'middle', - padding: 0, - boxSizing: 'content-box' - }); -} - -export function itemContainerStyles() { - return css({ - label: 'runway-dropdown__item-container', - display: 'inline-flex' - }); -} - -export function itemSvgContainerStyles({ selected }) { - const visibility = selected ? 'visible' : 'hidden'; - - return css({ - label: 'runway-dropdown__item-svg-container', - visibility, - marginRight: '10px' - }); -} - -export function itemSvgStyles() { - return css({ - label: 'runway-dropdown__item-svg', - width: '24px', - fill: colours.darkerGrey - }); -} - -export function menuStyles({ width }) { - return css({ - label: 'runway-dropdown__menu', - minWidth: '240px', - borderRadius: '4px', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.2)', - border: 'solid 1px #dadada', - backgroundColor: '#ffffff', - listStyle: 'none', - position: 'absolute', - zIndex: '5', - margin: 0, - top: '0', - left: '0', - width: width || '100%', - boxSizing: 'border-box', - padding: '10px' - }); -} - -export function menuItemsStyles({ highlighted }) { - return css({ - label: 'runway-dropdown__menu-item', - fontWeight: 400, - backgroundColor: highlighted ? colours.lightGrey : 'none', - color: colours.darkerGrey, - boxSizing: 'border-box', - padding: '10px', - minHeight: '50px', - [`${CSS_SELECTOR_HOVER}, ${CSS_SELECTOR_ACTIVE}, ${CSS_SELECTOR_FOCUS}`]: { - backgroundColor: colours.lightGrey - } - }); -} - -function DropdownMenu(props) { - const { items, renderItem, downshiftProps, menuWidth, ariaLabel } = props; - - const { - isOpen, - getItemProps, - selectedItem, - highlightedIndex, - getToggleButtonProps, - getMenuProps, - getInputProps - } = downshiftProps; - - const inputProps = getInputProps(); - - return ( -
- - {!isOpen ? null : ( - - )} -
- ); -} - -DropdownMenu.propTypes = { - items: PropTypes.arrayOf(PropTypes.object), - renderItem: PropTypes.func, - label: PropTypes.string, - menuWidth: PropTypes.string, - ariaLabel: PropTypes.string, - height: PropTypes.string, - highlighted: PropTypes.bool, - inline: PropTypes.bool, - growMenu: PropTypes.bool, - downshiftProps: PropTypes.shape({ - isOpen: PropTypes.bool, - getItemProps: PropTypes.func, - selectedItem: PropTypes.object, - highlightedIndex: PropTypes.number, - getToggleButtonProps: PropTypes.func, - getMenuProps: PropTypes.func, - getInputProps: PropTypes.func, - itemToString: PropTypes.func - }).isRequired -}; - -DropdownMenu.defaultProps = { - items: [], - renderItem: noop, - label: '', - menuWidth: null, - ariaLabel: '', - height: null, - highlighted: false, - inline: false, - growMenu: false -}; - -export default class Dropdown extends React.Component { - componentDidUpdate(prevProps) { - if (prevProps.initialSelectedItem !== this.props.initialSelectedItem) { - /* eslint-disable no-console */ - console.warn( - 'Runway Dropdown: initialSelectedItem should not change, please use selectedItem if you want a controlled component' - ); - } - } - - getInitialSelectedItem = () => - this.validItem(this.props.initialSelectedItem) - ? this.props.initialSelectedItem - : this.props.items[0]; - - getSelectedItem = () => - this.validItem(this.props.selectedItem) && this.props.selectedItem; - - validItem = item => { - const { items } = this.props; - return ( - item && - typeof item.name === 'string' && - typeof item.value === 'string' && - items.includes(item) - ); - }; - - render() { - const { props, isOpen } = this; - - return ( - { - if (typeof props.onChange === 'function') { - props.onChange(selectedItem, stateAndHelpers); - } - }} - initialSelectedItem={this.getInitialSelectedItem()} - selectedItem={this.getSelectedItem()} - > - {downshiftProps => { - const renderProps = { - ...props, - isOpen, - downshiftProps - }; - return ( -
- - - -
- ); - }} -
- ); - } -} - -function renderDefaultItem(item, index, props) { - return ( - - - - - - {item.name} - - - ); -} - -Dropdown.propTypes = { - /** Array of list items for the dropdown */ - items: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired - }) - ).isRequired, - /** Prop to render each list item. Must return an element. */ - renderItem: PropTypes.func, - downShiftProps: PropTypes.shape({ - itemToString: PropTypes.func - }), - /** Optional string to set the width of the menu */ - menuWidth: PropTypes.string, - /** Optional string set the aria label to call dropdown name */ - ariaLabel: PropTypes.string, - /** Optional string to set the height of the dropdown */ - height: PropTypes.string, - /** Triggered when the user changes the value - * - * @param {Object} selectedItem New value - * @param {Object} stateAndHelpers stateAndHelpers object from Downshift */ - onChange: PropTypes.func, - /** Optional flag to use highlighted styles */ - highlighted: PropTypes.bool, - /** Optional flag to make the dropdown inline rather than full width */ - inline: PropTypes.bool, - /** - * Optional flag to allow the dropdown menu to grow larger than the dropdown container - * Setting this flag to true requires a parent to have `position:absolute;` applied */ - growMenu: PropTypes.bool -}; - -Dropdown.defaultProps = { - renderItem: renderDefaultItem, - downShiftProps: { - itemToString: selectedItem => { - if (!selectedItem || !selectedItem.name) { - return ''; - } - return String(selectedItem.name); - } - }, - menuWidth: null, - ariaLabel: '', - height: '100%', - onChange: null, - highlighted: false, - inline: false, - growMenu: false -}; +export * from '../Select'; diff --git a/src/components/List/__tests__/List.spec.js b/src/components/List/__tests__/List.spec.js new file mode 100644 index 00000000..523402dc --- /dev/null +++ b/src/components/List/__tests__/List.spec.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { mount } from 'enzyme'; + +import { List } from '..'; + +jest.mock('../../ListItem', () => () =>
  • Test
  • ); + +describe('List', () => { + it('should match the snapshot when no children are given', () => { + const component = mount(); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/List/__tests__/__snapshots__/List.spec.js.snap b/src/components/List/__tests__/__snapshots__/List.spec.js.snap new file mode 100644 index 00000000..f4e7621c --- /dev/null +++ b/src/components/List/__tests__/__snapshots__/List.spec.js.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`List should match the snapshot when no children are given 1`] = ` +.emotion-0 { + min-width: 240px; + border-radius: 4px; + box-shadow: 0 1px 2px 0 rgba(0,0,0,0.2); + border: solid 1px #dadada; + background-color: #ffffff; + list-style: none; + position: absolute; + z-index: 5; + margin: 0; + top: 0; + left: 0; + width: 100%; + box-sizing: border-box; + padding: 10px; +} + + +
      + +`; diff --git a/src/components/List/index.js b/src/components/List/index.js new file mode 100644 index 00000000..fffa32ef --- /dev/null +++ b/src/components/List/index.js @@ -0,0 +1,40 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { css } from '@emotion/core'; + +export function styles({ width = '100%' }) { + return css({ + minWidth: '240px', + borderRadius: '4px', + boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.2)', + border: 'solid 1px #dadada', + backgroundColor: '#ffffff', + listStyle: 'none', + position: 'absolute', + zIndex: '5', + margin: 0, + top: '0', + left: '0', + width, + boxSizing: 'border-box', + padding: '10px' + }); +} + +export function List(props) { + const { component: Component = 'ul', children, ...otherProps } = props; + + return ( + + {children} + + ); +} + +List.propTypes = { + children: PropTypes.node, + component: PropTypes.elementType, + width: PropTypes.string +}; + +export default List; diff --git a/src/components/ListItem/__tests__/ListItem.spec.js b/src/components/ListItem/__tests__/ListItem.spec.js new file mode 100644 index 00000000..67a8c477 --- /dev/null +++ b/src/components/ListItem/__tests__/ListItem.spec.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { ListItem } from '..'; + +describe('ListItem', () => { + it('should match the snapshot when a simple textual child is given', () => { + const component = shallow(Economy); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/ListItem/__tests__/__snapshots__/ListItem.spec.js.snap b/src/components/ListItem/__tests__/__snapshots__/ListItem.spec.js.snap new file mode 100644 index 00000000..6c364125 --- /dev/null +++ b/src/components/ListItem/__tests__/__snapshots__/ListItem.spec.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ListItem should match the snapshot when a simple textual child is given 1`] = `"Economy"`; diff --git a/src/components/ListItem/index.js b/src/components/ListItem/index.js new file mode 100644 index 00000000..e98aca16 --- /dev/null +++ b/src/components/ListItem/index.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { css } from '@emotion/core'; + +import { colours } from '../../theme/airways'; +import { + CSS_SELECTOR_HOVER, + CSS_SELECTOR_ACTIVE, + CSS_SELECTOR_FOCUS +} from '../../constants/css'; + +export function styles({ highlighted }) { + return css({ + label: 'runway-dropdown__menu-item', + fontWeight: 400, + backgroundColor: highlighted ? colours.lightGrey : 'none', + color: colours.darkerGrey, + boxSizing: 'border-box', + padding: '10px', + minHeight: '50px', + [`${CSS_SELECTOR_HOVER}, ${CSS_SELECTOR_ACTIVE}, ${CSS_SELECTOR_FOCUS}`]: { + backgroundColor: colours.lightGrey + } + }); +} + +export function ListItem(props) { + const { component: Component = 'li', children, ...otherProps } = props; + + const componentProps = { + ...otherProps + }; + + return ( + + {children} + + ); +} + +ListItem.propTypes = { + children: PropTypes.node, + highlighted: PropTypes.bool // TODO: deprecate this as its a crappy api name should be selected which is way more dope +}; + +export default ListItem; diff --git a/src/components/MenuItem/README.md b/src/components/MenuItem/README.md new file mode 100644 index 00000000..c84c4bb4 --- /dev/null +++ b/src/components/MenuItem/README.md @@ -0,0 +1,2 @@ +# TODO: Documentation and purpose +REMIND ME TO DO SOMETHING HERE :D diff --git a/src/components/MenuItem/__tests__/MenuItem.spec.js b/src/components/MenuItem/__tests__/MenuItem.spec.js new file mode 100644 index 00000000..ced0fd8e --- /dev/null +++ b/src/components/MenuItem/__tests__/MenuItem.spec.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { mount } from 'enzyme'; + +import { MenuItem } from '..'; + +jest.mock('shortid', () => ({ generate: () => 'mockId' })); + +describe('MenuItem', () => { + it('should match the snapshot when a simple textual child is given', () => { + const component = mount(Economy); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/MenuItem/__tests__/__snapshots__/MenuItem.spec.js.snap b/src/components/MenuItem/__tests__/__snapshots__/MenuItem.spec.js.snap new file mode 100644 index 00000000..9717b739 --- /dev/null +++ b/src/components/MenuItem/__tests__/__snapshots__/MenuItem.spec.js.snap @@ -0,0 +1,110 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MenuItem should match the snapshot when a simple textual child is given 1`] = ` +.emotion-5 { + font-weight: 400; + background-color: none; + color: #323232; + box-sizing: border-box; + padding: 10px; + min-height: 50px; +} + +.emotion-5:hover, +.emotion-5:active, +.emotion-5:focus { + background-color: #f4f5f6; +} + +.emotion-4 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; +} + +.emotion-2 { + visibility: visible; + margin-right: 10px; +} + +.emotion-0 { + width: 24px; + fill: #323232; +} + +.emotion-3 { + white-space: normal; + word-wrap: break-word; +} + + + +
    • + + + + + + + + + + + + + + + + + + + + + Economy + + +
    • +
      +
      +`; diff --git a/src/components/MenuItem/index.js b/src/components/MenuItem/index.js new file mode 100644 index 00000000..d09e529f --- /dev/null +++ b/src/components/MenuItem/index.js @@ -0,0 +1,57 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { css } from '@emotion/core'; + +import { colours } from '../../theme/airways'; +import TickIcon from '../../icons/Tick'; +import { ListItem } from '../ListItem'; + +export function itemContainerStyles() { + return css({ + label: 'runway-dropdown__item-container', + display: 'inline-flex' + }); +} + +export function itemSvgContainerStyles({ selected }) { + const visibility = selected ? 'visible' : 'hidden'; + + return css({ + label: 'runway-dropdown__item-svg-container', + visibility, + marginRight: '10px' + }); +} + +export function itemSvgStyles() { + return css({ + label: 'runway-dropdown__item-svg', + width: '24px', + fill: colours.darkerGrey + }); +} + +export function MenuItem(props) { + const { component: Component = 'span', children, ...otherProps } = props; + + return ( + + + + + + + {children} + + + + ); +} + +MenuItem.propTypes = { + children: PropTypes.node, + component: PropTypes.elementType, + selected: PropTypes.bool +}; + +export default MenuItem; diff --git a/src/components/Dropdown/README.md b/src/components/Select/README.md similarity index 85% rename from src/components/Dropdown/README.md rename to src/components/Select/README.md index 5e3feb86..1ad85b10 100644 --- a/src/components/Dropdown/README.md +++ b/src/components/Select/README.md @@ -16,11 +16,7 @@ const itemsArray = [ const selectedItem = itemsArray.find((value, index) => index === 0); -; +); + expect(component).toMatchSnapshot(); }); it('renders correctly with props provided', () => { - component = mount(); + const component = mount( ); const button = component.find('button'); + expect(button.text()).toEqual(orange.name); }); it('initially renders with first available item selected when initialSelectedItem not valid', () => { const missingName = { value: 'value:bad' }; const mockItems = [...items, missingName]; - component = mount( - + const component = mount( + ); const nextItems = [pear, orange]; + component.setProps({ items: nextItems }); component.update(); + const button = component.find('button'); + expect(button.text()).toEqual(orange.name); }); }); }); }); + +// TODO: This doesn't work the way i want it to yet but react-testing-library works +/* +import { render, cleanup } from '@testing-library/react' + +afterEach(cleanup) + +describe("Select - React Testing Library", () => { + let component; + + const apple = { name: 'name:apple', value: 'value:apple' }; + const orange = { name: 'name:orange', value: 'name:orange' }; + const banana = { name: 'name:banana', value: 'name:banana' }; + const pear = { name: 'name:pear', value: 'value:pear' }; + + const items = [apple, orange, banana]; + + const props = { + items, + renderItem: () =>
      mockRenderItemResult
      , + withPadding: true, + focus: true, + downShiftProps: { itemToString: item => item.name }, + menuWidth: '800px', + height: '20px', + onChange: () => {}, + highlighted: true, + inline: true, + growMenu: true + }; + + it("should change styles appropriate for focus", () => { + const { container, getByRole } = render( + +
      + + + + + + + +
      +
      + +`; + +exports[`Select renders correctly with props provided 1`] = ` +.emotion-0 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.emotion-5 { + color: #8de2e0; + position: static; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + height: 20px; + max-width: 100%; +} + +.emotion-4 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + position: relative; + cursor: pointer; + padding: 0; + border: 0; + background: transparent; + color: inherit; + -webkit-text-decoration: none; + text-decoration: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + vertical-align: middle; + max-width: 100%; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 0; + font-size: 1rem; + font-weight: 700; + line-height: 1.56; + background: #3c3c3c; + color: #8de2e0; + -webkit-text-decoration: underline; + text-decoration: underline; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + height: 20px; +} + +.emotion-1 { + width: 24px; + height: 100%; + fill: #8de2e0; + vertical-align: middle; + padding: 0; +} + + +`; diff --git a/src/components/Dropdown/components/SelectOnKeyPressContainer.js b/src/components/Select/components/SelectOnKeyPressContainer.js similarity index 100% rename from src/components/Dropdown/components/SelectOnKeyPressContainer.js rename to src/components/Select/components/SelectOnKeyPressContainer.js diff --git a/src/components/Select/index.js b/src/components/Select/index.js new file mode 100644 index 00000000..9693570f --- /dev/null +++ b/src/components/Select/index.js @@ -0,0 +1,286 @@ +/* eslint-disable jsx-a11y/label-has-for, jsx-a11y/label-has-associated-control */ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Downshift from 'downshift'; +import { css } from '@emotion/core'; + +import SelectOnKeyPressContainer from './components/SelectOnKeyPressContainer'; + +import { ButtonBase } from '../ButtonBase'; +import { List } from '../List'; +import { MenuItem } from '../MenuItem'; +import ChevronDown from '../../icons/ChevronDown'; +import noop from '../../utils/noop'; +import { colours, layout, fontWeight, fontSize } from '../../theme/airways'; + +export function dropdownMenuContainerStyles({ + highlighted, + growMenu, + inline, + height +}) { + return css({ + label: 'runway-dropdown__container', + color: highlighted ? colours.highlights : colours.white, + position: growMenu ? 'static' : 'relative', + width: inline ? 'fit-content' : '100%', + height, + maxWidth: '100%' + }); +} + +export function buttonStyles({ highlighted, inline, height }) { + return css({ + justifyContent: 'space-between', + padding: inline ? 0 : `0 ${layout.gutter}`, // TODO: remove variants + fontSize: fontSize.label, // TODO: better name this variable + fontWeight: highlighted ? fontWeight.bold : fontWeight.regular, // TODO: remove variants + lineHeight: 1.56, // TODO: Line heights are bad + background: colours.mediumGrey, // TODO: theme this bad boy + color: highlighted ? colours.highlights : colours.white, // TODO: remove variants + textDecoration: highlighted ? 'underline' : 'none', // TODO: remove variants + width: inline ? 'fit-content' : '100%', // TODO: remove variants + height // TODO: why, if anything should be padding :scream: + }); +} + +export function buttonSvgStyles({ highlighted }) { + return css({ + label: 'runway-dropdown__button-svg', + width: '24px', + height: '100%', + fill: highlighted ? colours.highlights : colours.white, + verticalAlign: 'middle', + padding: 0 + }); +} + +function DropdownMenu(props) { + const { + items, + renderItem, + downshiftProps, + menuWidth, + ariaLabel, + overrideClassName + } = props; + + const { + isOpen, + getItemProps, + selectedItem, + highlightedIndex, + getToggleButtonProps, + getMenuProps, + getInputProps + } = downshiftProps; + + const inputProps = getInputProps(); + + const toggleButtonProps = getToggleButtonProps({ + 'aria-activedescendant': inputProps['aria-activedescendant'], + 'aria-label': + selectedItem && `${ariaLabel} Menu, ${selectedItem.name} selected` + }); + + return ( + + + + {selectedItem && selectedItem.name} + + + + + + {!isOpen ? null : ( + + {items.map((item, index) => { + const itemProps = getItemProps({ + highlighted: highlightedIndex === index, + selected: selectedItem && selectedItem.name === item.name, + key: index, + item + }); + + return ( + + {renderItem(item, index, itemProps)} + + ); + })} + + )} + + ); +} + +DropdownMenu.propTypes = { + items: PropTypes.arrayOf(PropTypes.object), + renderItem: PropTypes.func, + label: PropTypes.string, + menuWidth: PropTypes.string, + ariaLabel: PropTypes.string, + height: PropTypes.string, + highlighted: PropTypes.bool, + inline: PropTypes.bool, + growMenu: PropTypes.bool, + downshiftProps: PropTypes.shape({ + isOpen: PropTypes.bool, + getItemProps: PropTypes.func, + selectedItem: PropTypes.object, + highlightedIndex: PropTypes.number, + getToggleButtonProps: PropTypes.func, + getMenuProps: PropTypes.func, + getInputProps: PropTypes.func, + itemToString: PropTypes.func + }).isRequired +}; + +DropdownMenu.defaultProps = { + items: [], + renderItem: noop, + label: '', + menuWidth: null, + ariaLabel: '', + height: null, + highlighted: false, + inline: false, + growMenu: false +}; + +export class Select extends React.Component { + componentDidUpdate(prevProps) { + if (prevProps.initialSelectedItem !== this.props.initialSelectedItem) { + /* eslint-disable no-console */ + console.warn( + 'Runway Dropdown: initialSelectedItem should not change, please use selectedItem if you want a controlled component' + ); + } + } + + getInitialSelectedItem = () => + this.validItem(this.props.initialSelectedItem) + ? this.props.initialSelectedItem + : this.props.items[0]; + + getSelectedItem = () => + this.validItem(this.props.selectedItem) && this.props.selectedItem; + + validItem = item => { + const { items } = this.props; + return ( + item && + typeof item.name === 'string' && + typeof item.value === 'string' && + items.includes(item) + ); + }; + + render() { + const { props, isOpen } = this; + + return ( + { + if (typeof props.onChange === 'function') { + props.onChange(selectedItem, stateAndHelpers); + } + }} + initialSelectedItem={this.getInitialSelectedItem()} + selectedItem={this.getSelectedItem()} + > + {downshiftProps => { + const renderProps = { + ...props, + isOpen, + downshiftProps + }; + return ( +
      + + + +
      + ); + }} +
      + ); + } +} + +function renderDefaultItem(item) { + return item.name; +} + +Select.propTypes = { + /** Array of list items for the dropdown */ + items: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired + }) + ).isRequired, + /** Prop to render each list item. Must return an element. */ + renderItem: PropTypes.func, + downShiftProps: PropTypes.shape({ + itemToString: PropTypes.func + }), + /** Optional string to set the width of the menu */ + menuWidth: PropTypes.string, + /** Optional string set the aria label to call dropdown name */ + ariaLabel: PropTypes.string, + /** Optional string to set the height of the dropdown */ + height: PropTypes.string, + /** Triggered when the user changes the value + * + * @param {Object} selectedItem New value + * @param {Object} stateAndHelpers stateAndHelpers object from Downshift */ + onChange: PropTypes.func, + /** Optional flag to use highlighted styles */ + highlighted: PropTypes.bool, + /** Optional flag to make the dropdown inline rather than full width */ + inline: PropTypes.bool, + /** + * Optional flag to allow the dropdown menu to grow larger than the dropdown container + * Setting this flag to true requires a parent to have `position:absolute;` applied */ + growMenu: PropTypes.bool +}; + +Select.defaultProps = { + renderItem: renderDefaultItem, + downShiftProps: { + itemToString: selectedItem => { + if (!selectedItem || !selectedItem.name) { + return ''; + } + return String(selectedItem.name); + } + }, + menuWidth: null, + ariaLabel: '', + height: '100%', + onChange: null, + highlighted: false, + inline: false, + growMenu: false +}; + +export default Select; diff --git a/src/utils/propTypes/base/shouldNotBeDefinedPropType.js b/src/utils/propTypes/base/shouldNotBeDefinedPropType.js new file mode 100644 index 00000000..dcb2fd56 --- /dev/null +++ b/src/utils/propTypes/base/shouldNotBeDefinedPropType.js @@ -0,0 +1,19 @@ +export function shouldNotBeDefinedPropType(type) { + return (validator, reason) => { + if (process.env.NODE_ENV === 'production') { + return () => null; + } + + return (props, propName, componentName, location, propFullName) => { + const componentNameSafe = componentName || '<>'; + const propFullNameSafe = propFullName || propName; + + return propName in props + ? new Error( + `The ${location} \`${propFullNameSafe}\` of ` + + `\`${componentNameSafe}\` is ${type}. ${reason}` + ) + : null; + }; + }; +} diff --git a/src/utils/propTypes/deprecatedPropType.js b/src/utils/propTypes/deprecatedPropType.js new file mode 100644 index 00000000..3bbae440 --- /dev/null +++ b/src/utils/propTypes/deprecatedPropType.js @@ -0,0 +1,7 @@ +import { shouldNotBeDefinedPropType } from './base/shouldNotBeDefinedPropType'; + +const deprecatedPropTypeFactory = shouldNotBeDefinedPropType('deprecated'); + +export function deprecatedPropType(...args) { + return deprecatedPropTypeFactory(...args); +} diff --git a/src/utils/propTypes/index.js b/src/utils/propTypes/index.js new file mode 100644 index 00000000..56279478 --- /dev/null +++ b/src/utils/propTypes/index.js @@ -0,0 +1,2 @@ +export * as deprecatedPropType from './deprecatedPropType'; +export * as unimplementedPropType from './unimplementedPropType'; diff --git a/src/utils/propTypes/unimplementedPropType.js b/src/utils/propTypes/unimplementedPropType.js new file mode 100644 index 00000000..732c1ff9 --- /dev/null +++ b/src/utils/propTypes/unimplementedPropType.js @@ -0,0 +1,9 @@ +import { shouldNotBeDefinedPropType } from './base/shouldNotBeDefinedPropType'; + +const unimplementedPropTypeFactory = shouldNotBeDefinedPropType( + 'not implemented yet' +); + +export function unimplementedPropType(...args) { + return unimplementedPropTypeFactory(...args); +}