diff --git a/babel.config.js b/babel.config.js index 7679cc1e837041..f950b6603a5127 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,6 +3,6 @@ module.exports = function( api ) { return { presets: [ '@wordpress/babel-preset-default' ], - plugins: [ 'babel-plugin-inline-json-import' ], + plugins: [ 'babel-plugin-emotion', 'babel-plugin-inline-json-import' ], }; }; diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index 91bab63f6b8864..75573dd5ea1337 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -605,6 +605,12 @@ "markdown_source": "../packages/components/src/button/README.md", "parent": "components" }, + { + "title": "Card", + "slug": "card", + "markdown_source": "../packages/components/src/card/README.md", + "parent": "components" + }, { "title": "CheckboxControl", "slug": "checkbox-control", diff --git a/package-lock.json b/package-lock.json index 8424989e170c8c..ac846e0ec88c08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -206,7 +206,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", - "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -1662,7 +1661,6 @@ "version": "10.0.19", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.19.tgz", "integrity": "sha512-BoiLlk4vEsGBg2dAqGSJu0vJl/PgVtCYLBFJaEO8RmQzPugXewQCXZJNXTDFaRlfCs0W+quesayav4fvaif5WQ==", - "dev": true, "requires": { "@emotion/sheet": "0.9.3", "@emotion/stylis": "0.8.4", @@ -1671,27 +1669,47 @@ } }, "@emotion/core": { - "version": "10.0.21", - "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.21.tgz", - "integrity": "sha512-U9zbc7ovZ2ceIwbLXYZPJy6wPgnOdTNT4jENZ31ee6v2lojetV5bTbCVk6ciT8G3wQRyVaTTfUCH9WCrMzpRIw==", - "dev": true, + "version": "10.0.22", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.22.tgz", + "integrity": "sha512-7eoP6KQVUyOjAkE6y4fdlxbZRA4ILs7dqkkm6oZUJmihtHv0UBq98VgPirq9T8F9K2gKu0J/au/TpKryKMinaA==", "requires": { "@babel/runtime": "^7.5.5", "@emotion/cache": "^10.0.17", - "@emotion/css": "^10.0.14", - "@emotion/serialize": "^0.11.10", + "@emotion/css": "^10.0.22", + "@emotion/serialize": "^0.11.12", "@emotion/sheet": "0.9.3", "@emotion/utils": "0.11.2" }, "dependencies": { "@babel/runtime": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", - "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", - "dev": true, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", "requires": { "regenerator-runtime": "^0.13.2" } + }, + "@emotion/css": { + "version": "10.0.22", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.22.tgz", + "integrity": "sha512-8phfa5mC/OadBTmGpMpwykIVH0gFCbUoO684LUkyixPq4F1Wwri7fK5Xlm8lURNBrd2TuvTbPUGxFsGxF9UacA==", + "requires": { + "@emotion/serialize": "^0.11.12", + "@emotion/utils": "0.11.2", + "babel-plugin-emotion": "^10.0.22" + } + }, + "@emotion/serialize": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.14.tgz", + "integrity": "sha512-6hTsySIuQTbDbv00AnUO6O6Xafdwo5GswRlMZ5hHqiFx+4pZ7uGWXUQFW46Kc2taGhP89uXMXn/lWQkdyTosPA==", + "requires": { + "@emotion/hash": "0.7.3", + "@emotion/memoize": "0.7.3", + "@emotion/unitless": "0.7.4", + "@emotion/utils": "0.11.2", + "csstype": "^2.5.7" + } } } }, @@ -1709,14 +1727,12 @@ "@emotion/hash": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", - "integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw==", - "dev": true + "integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw==" }, "@emotion/is-prop-valid": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.3.tgz", - "integrity": "sha512-We7VBiltAJ70KQA0dWkdPMXnYoizlxOXpvtjmu5/MBnExd+u0PGgV27WCYanmLAbCwAU30Le/xA0CQs/F/Otig==", - "dev": true, + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.5.tgz", + "integrity": "sha512-6ZODuZSFofbxSbcxwsFz+6ioPjb0ISJRRPLZ+WIbjcU2IMU0Io+RGQjjaTgOvNQl007KICBm7zXQaYQEC1r6Bg==", "requires": { "@emotion/memoize": "0.7.3" } @@ -1724,8 +1740,7 @@ "@emotion/memoize": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.3.tgz", - "integrity": "sha512-2Md9mH6mvo+ygq1trTeVp2uzAKwE2P7In0cRpD/M9Q70aH8L+rxMLbb3JCN2JoSWsV2O+DdFjfbbXoMoLBczow==", - "dev": true + "integrity": "sha512-2Md9mH6mvo+ygq1trTeVp2uzAKwE2P7In0cRpD/M9Q70aH8L+rxMLbb3JCN2JoSWsV2O+DdFjfbbXoMoLBczow==" }, "@emotion/serialize": { "version": "0.11.11", @@ -1743,65 +1758,69 @@ "@emotion/sheet": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.3.tgz", - "integrity": "sha512-c3Q6V7Df7jfwSq5AzQWbXHa5soeE4F5cbqi40xn0CzXxWW9/6Mxq48WJEtqfWzbZtW9odZdnRAkwCQwN12ob4A==", - "dev": true + "integrity": "sha512-c3Q6V7Df7jfwSq5AzQWbXHa5soeE4F5cbqi40xn0CzXxWW9/6Mxq48WJEtqfWzbZtW9odZdnRAkwCQwN12ob4A==" }, "@emotion/styled": { - "version": "10.0.17", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.0.17.tgz", - "integrity": "sha512-zHMgWjHDMNjD+ux64POtDnjLAObniu3znxFBLSdV/RiEhSLjHIowfvSbbd/C33/3uwtI6Uzs2KXnRZtka/PpAQ==", - "dev": true, + "version": "10.0.23", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.0.23.tgz", + "integrity": "sha512-gNr04eqBQ2iYUx8wFLZDfm3N8/QUOODu/ReDXa693uyQGy2OqA+IhPJk+kA7id8aOfwAsMuvZ0pJImEXXKtaVQ==", "requires": { - "@emotion/styled-base": "^10.0.17", - "babel-plugin-emotion": "^10.0.17" + "@emotion/styled-base": "^10.0.23", + "babel-plugin-emotion": "^10.0.23" } }, "@emotion/styled-base": { - "version": "10.0.19", - "resolved": "https://registry.npmjs.org/@emotion/styled-base/-/styled-base-10.0.19.tgz", - "integrity": "sha512-Sz6GBHTbOZoeZQKvkE9gQPzaJ6/qtoQ/OPvyG2Z/6NILlYk60Es1cEcTgTkm26H8y7A0GSgp4UmXl+srvsnFPg==", - "dev": true, + "version": "10.0.24", + "resolved": "https://registry.npmjs.org/@emotion/styled-base/-/styled-base-10.0.24.tgz", + "integrity": "sha512-AnBImerf0h4dGAJVo0p0VE8KoAns71F28ErGFK474zbNAHX6yqSWQUasb+1jvg/VPwZjCp19+tAr6oOB0pwmLQ==", "requires": { "@babel/runtime": "^7.5.5", - "@emotion/is-prop-valid": "0.8.3", - "@emotion/serialize": "^0.11.11", + "@emotion/is-prop-valid": "0.8.5", + "@emotion/serialize": "^0.11.14", "@emotion/utils": "0.11.2" }, "dependencies": { "@babel/runtime": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", - "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", - "dev": true, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", "requires": { "regenerator-runtime": "^0.13.2" } + }, + "@emotion/serialize": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.14.tgz", + "integrity": "sha512-6hTsySIuQTbDbv00AnUO6O6Xafdwo5GswRlMZ5hHqiFx+4pZ7uGWXUQFW46Kc2taGhP89uXMXn/lWQkdyTosPA==", + "requires": { + "@emotion/hash": "0.7.3", + "@emotion/memoize": "0.7.3", + "@emotion/unitless": "0.7.4", + "@emotion/utils": "0.11.2", + "csstype": "^2.5.7" + } } } }, "@emotion/stylis": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.4.tgz", - "integrity": "sha512-TLmkCVm8f8gH0oLv+HWKiu7e8xmBIaokhxcEKPh1m8pXiV/akCiq50FvYgOwY42rjejck8nsdQxZlXZ7pmyBUQ==", - "dev": true + "integrity": "sha512-TLmkCVm8f8gH0oLv+HWKiu7e8xmBIaokhxcEKPh1m8pXiV/akCiq50FvYgOwY42rjejck8nsdQxZlXZ7pmyBUQ==" }, "@emotion/unitless": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.4.tgz", - "integrity": "sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ==", - "dev": true + "integrity": "sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ==" }, "@emotion/utils": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.2.tgz", - "integrity": "sha512-UHX2XklLl3sIaP6oiMmlVzT0J+2ATTVpf0dHQVyPJHTkOITvXfaSqnRk6mdDhV9pR8T/tHc3cex78IKXssmzrA==", - "dev": true + "integrity": "sha512-UHX2XklLl3sIaP6oiMmlVzT0J+2ATTVpf0dHQVyPJHTkOITvXfaSqnRk6mdDhV9pR8T/tHc3cex78IKXssmzrA==" }, "@emotion/weak-memoize": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.4.tgz", - "integrity": "sha512-6PYY5DVdAY1ifaQW6XYTnOMihmBVT27elqSjEoodchsGjzYlEsTQMcEhSud99kVawatyTZRTiVkJ/c6lwbQ7nA==", - "dev": true + "integrity": "sha512-6PYY5DVdAY1ifaQW6XYTnOMihmBVT27elqSjEoodchsGjzYlEsTQMcEhSud99kVawatyTZRTiVkJ/c6lwbQ7nA==" }, "@evocateur/libnpmaccess": { "version": "3.1.2", @@ -7283,6 +7302,8 @@ "version": "file:packages/components", "requires": { "@babel/runtime": "^7.4.4", + "@emotion/core": "10.0.22", + "@emotion/styled": "10.0.23", "@wordpress/a11y": "file:packages/a11y", "@wordpress/compose": "file:packages/compose", "@wordpress/deprecated": "file:packages/deprecated", @@ -7296,6 +7317,7 @@ "classnames": "^2.2.5", "clipboard": "^2.0.1", "dom-scroll-into-view": "^1.2.1", + "hex-rgb": "^4.1.0", "lodash": "^4.17.15", "memize": "^1.0.5", "moment": "^2.22.1", @@ -8297,7 +8319,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" }, @@ -8305,8 +8326,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" } } }, @@ -9238,21 +9258,34 @@ } }, "babel-plugin-emotion": { - "version": "10.0.21", - "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.21.tgz", - "integrity": "sha512-03o+T6sfVAJhNDcSdLapgv4IeewcFPzxlvBUVdSf7o5PI57ZSxoDvmy+ZulVWSu+rOWAWkEejNcsb29TuzJHbg==", - "dev": true, + "version": "10.0.23", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.23.tgz", + "integrity": "sha512-1JiCyXU0t5S2xCbItejCduLGGcKmF3POT0Ujbexog2MI4IlRcIn/kWjkYwCUZlxpON0O5FC635yPl/3slr7cKQ==", "requires": { "@babel/helper-module-imports": "^7.0.0", "@emotion/hash": "0.7.3", "@emotion/memoize": "0.7.3", - "@emotion/serialize": "^0.11.11", + "@emotion/serialize": "^0.11.14", "babel-plugin-macros": "^2.0.0", "babel-plugin-syntax-jsx": "^6.18.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^1.0.5", "find-root": "^1.1.0", "source-map": "^0.5.7" + }, + "dependencies": { + "@emotion/serialize": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.14.tgz", + "integrity": "sha512-6hTsySIuQTbDbv00AnUO6O6Xafdwo5GswRlMZ5hHqiFx+4pZ7uGWXUQFW46Kc2taGhP89uXMXn/lWQkdyTosPA==", + "requires": { + "@emotion/hash": "0.7.3", + "@emotion/memoize": "0.7.3", + "@emotion/unitless": "0.7.4", + "@emotion/utils": "0.11.2", + "csstype": "^2.5.7" + } + } } }, "babel-plugin-extract-import-names": { @@ -9369,7 +9402,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz", "integrity": "sha512-6W2nwiXme6j1n2erPOnmRiWfObUhWH7Qw1LMi9XZy8cj+KtESu3T6asZvtk5bMQQjX8te35o7CFueiSdL/2NmQ==", - "dev": true, "requires": { "@babel/runtime": "^7.4.2", "cosmiconfig": "^5.2.0", @@ -9380,7 +9412,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, "requires": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -9392,7 +9423,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dev": true, "requires": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -9402,7 +9432,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -9411,14 +9440,12 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -9554,8 +9581,7 @@ "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" }, "babel-plugin-syntax-trailing-function-commas": { "version": "7.0.0-beta.0", @@ -10894,7 +10920,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, "requires": { "callsites": "^2.0.0" }, @@ -10902,8 +10927,7 @@ "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" } } }, @@ -10911,7 +10935,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, "requires": { "caller-callsite": "^2.0.0" } @@ -12274,8 +12297,7 @@ "convert-source-map": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" }, "cookie": { "version": "0.3.1", @@ -12907,8 +12929,7 @@ "csstype": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", - "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==", - "dev": true + "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==" }, "currently-unhandled": { "version": "0.4.1", @@ -14026,7 +14047,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -15571,8 +15591,7 @@ "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "find-up": { "version": "2.1.0", @@ -17312,6 +17331,11 @@ "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", "dev": true }, + "hex-rgb": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.1.0.tgz", + "integrity": "sha512-n7xsIfyBkFChITGPh6FLtxNzAt2HxZLcQIY9hYH4gm2gmMQJHMguMH3E+jnmvUbSTF5QrmFnGab5Ippi+D7e/g==" + }, "highlight.js": { "version": "9.12.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz", @@ -18240,8 +18264,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -18354,8 +18377,7 @@ "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" }, "is-equal-shallow": { "version": "0.1.3", @@ -19995,7 +20017,6 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -20004,8 +20025,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" } } }, @@ -20109,8 +20129,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema": { "version": "0.2.3", @@ -28442,8 +28461,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, "resolve-url": { "version": "0.2.1", @@ -29509,8 +29527,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-loader": { "version": "0.2.4", diff --git a/package.json b/package.json index d3bd64b3472042..1f727512198429 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "@wordpress/postcss-themes": "file:packages/postcss-themes", "@wordpress/scripts": "file:packages/scripts", "babel-loader": "8.0.6", + "babel-plugin-emotion": "10.0.23", "babel-plugin-inline-json-import": "0.3.2", "babel-plugin-react-native-classname-to-style": "1.2.2", "babel-plugin-react-native-platform-specific-extensions": "1.1.1", diff --git a/packages/components/package.json b/packages/components/package.json index c542fc3a79a7e0..3e5876a0c8d6eb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -22,6 +22,8 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", + "@emotion/core": "10.0.22", + "@emotion/styled": "10.0.23", "@wordpress/a11y": "file:../a11y", "@wordpress/compose": "file:../compose", "@wordpress/deprecated": "file:../deprecated", @@ -35,6 +37,7 @@ "classnames": "^2.2.5", "clipboard": "^2.0.1", "dom-scroll-into-view": "^1.2.1", + "hex-rgb": "^4.1.0", "lodash": "^4.17.15", "memize": "^1.0.5", "moment": "^2.22.1", diff --git a/packages/components/src/card/README.md b/packages/components/src/card/README.md new file mode 100644 index 00000000000000..4a401b021a842d --- /dev/null +++ b/packages/components/src/card/README.md @@ -0,0 +1,97 @@ +# Card + +Card provides a flexible and extensible content container. + +## Usage + +```jsx +import { Card, CardBody } from '@wordpress/components'; + +const Example = () => ( + + ... + +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`isBorderless` | `boolean` | `false` | Determines the border style of the card. +`isElevated` | `boolean` | `false` | Determines the elevation style of the card. +`size` | `string` | `medium` | Determines the amount of padding within the card. + +## Sub-Components + +This component provides a collection of sub-component that can be used to compose various interfaces. + +- [``](./docs/body.md) +- [``](./docs/divider.md) +- [``](./docs/footer.md) +- [``](./docs/header.md) +- [``](./docs/media.md) + +### Sub-Components Example + +```jsx +import { + Card, + CardBody, + CardDivider, + CardFooter, + CardHeader, + CardMedia +} from '@wordpress/components'; + +const Example = () => ( + + + ... + + + ... + + + + ... + + + + + + ... + + +); +``` + +### Context + +``'s sub-components are connected to `` using [Context](https://reactjs.org/docs/context.html). Certain props like `size` and `variant` are passed through to the sub-components. + +In the following example, the `` will render with a size of `small`: + +```jsx +import { Card, CardBody } from '@wordpress/components'; + +const Example = () => ( + + ... + +); +``` + +These sub-components are designed to be flexible. The Context props can be overridden by the sub-component(s) as required. In the following example, the last `` will render it's specified size: + +```jsx +import { Card, CardBody } from '@wordpress/components'; + +const Example = () => ( + + ... + ... + ... + +); +``` diff --git a/packages/components/src/card/body.js b/packages/components/src/card/body.js new file mode 100644 index 00000000000000..9194d601169ea9 --- /dev/null +++ b/packages/components/src/card/body.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { BodyUI } from './styles/card-styles'; +import { useCardContext } from './context'; + +export const defaultProps = { + isShady: false, + size: 'medium', +}; + +export function CardBody( props ) { + const { className, isShady, ...additionalProps } = props; + const mergedProps = { ...defaultProps, ...useCardContext(), ...props }; + const { size } = mergedProps; + + const classes = classnames( + 'components-card__body', + isShady && 'is-shady', + size && `is-size-${ size }`, + className + ); + + return ; +} + +export default CardBody; diff --git a/packages/components/src/card/context.js b/packages/components/src/card/context.js new file mode 100644 index 00000000000000..2bcc31eaf65d4e --- /dev/null +++ b/packages/components/src/card/context.js @@ -0,0 +1,7 @@ +/** + * WordPress dependencies + */ +import { createContext, useContext } from '@wordpress/element'; + +export const CardContext = createContext( {} ); +export const useCardContext = () => useContext( CardContext ); diff --git a/packages/components/src/card/divider.js b/packages/components/src/card/divider.js new file mode 100644 index 00000000000000..09a4a59c726643 --- /dev/null +++ b/packages/components/src/card/divider.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { DividerUI } from './styles/card-styles'; + +export function CardDivider( props ) { + const { className, ...additionalProps } = props; + + const classes = classnames( 'components-card__divider', className ); + + return ( + + ); +} + +export default CardDivider; diff --git a/packages/components/src/card/docs/body.md b/packages/components/src/card/docs/body.md new file mode 100644 index 00000000000000..6693bf644b27c4 --- /dev/null +++ b/packages/components/src/card/docs/body.md @@ -0,0 +1,24 @@ +# CardBody + +CardBody renders an optional content area for a [``](../). + +## Usage + +```jsx +import { Card, CardBody } from '@wordpress/components'; + +const Example = () => ( + + ... + +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`isShady` | `boolean` | `false` | Renders with a light gray background color. +`size` | `string` | `medium` | Determines the amount of padding within the component. + +Note: This component is connected to [``'s Context](../README.md#context). Passing props directly to this component will override the props derived from context. diff --git a/packages/components/src/card/docs/divider.md b/packages/components/src/card/docs/divider.md new file mode 100644 index 00000000000000..feac94af529568 --- /dev/null +++ b/packages/components/src/card/docs/divider.md @@ -0,0 +1,17 @@ +# CardDivider + +CardDivider renders an optional divider within a [``](../). + +## Usage + +```jsx +import { Card, CardBody, CardDivider } from '@wordpress/components'; + +const Example = () => ( + + ... + + ... + +); +``` diff --git a/packages/components/src/card/docs/footer.md b/packages/components/src/card/docs/footer.md new file mode 100644 index 00000000000000..f109db255672af --- /dev/null +++ b/packages/components/src/card/docs/footer.md @@ -0,0 +1,26 @@ +# CardFooter + +CardFooter renders an optional footer within a [``](../). + +## Usage + +```jsx +import { Card, CardFooter } from '@wordpress/components'; + +const Example = () => ( + + ... + +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`isBorderless` | `boolean` | `false` | Determines the border style of the card. +`isElevated` | `boolean` | `false` | Determines the elevation style of the card. +`isShady` | `boolean` | `false` | Renders with a light gray background color. +`size` | `string` | `medium` | Determines the amount of padding within the component. + +Note: This component is connected to [``'s Context](../README.md#context). Passing props directly to this component will override the props derived from context. diff --git a/packages/components/src/card/docs/header.md b/packages/components/src/card/docs/header.md new file mode 100644 index 00000000000000..b3901f15625cca --- /dev/null +++ b/packages/components/src/card/docs/header.md @@ -0,0 +1,26 @@ +# CardHeader + +CardHeader renders an optional header within a [``](../). + +## Usage + +```jsx +import { Card, CardHeader } from '@wordpress/components'; + +const Example = () => ( + + ... + +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`isBorderless` | `boolean` | `false` | Determines the border style of the card. +`isElevated` | `boolean` | `false` | Determines the elevation style of the card. +`isShady` | `boolean` | `false` | Renders with a light gray background color. +`size` | `string` | `medium` | Determines the amount of padding within the component. + +Note: This component is connected to [``'s Context](../README.md#context). Passing props directly to this component will override the props derived from context. diff --git a/packages/components/src/card/docs/media.md b/packages/components/src/card/docs/media.md new file mode 100644 index 00000000000000..980e32127792ec --- /dev/null +++ b/packages/components/src/card/docs/media.md @@ -0,0 +1,84 @@ +# CardMedia + +CardMedia provides a container for media elements, and renders within a [``](../). + +## Usage + +```jsx +import { Card, CardBody, CardMedia } from '@wordpress/components'; + +const Example = () => ( + + + + + ... + +); +``` + +## Placement + +`` can be placed in any order as a direct child of a ``. The styles will automatically round the corners of the inner media element. + +### Top + +```jsx +import { Card, CardBody, CardMedia } from '@wordpress/components'; + +const Example = () => ( + + + + + ... + +); +``` + +### Center + +```jsx +import { Card, CardBody, CardMedia } from '@wordpress/components'; + +const Example = () => ( + + ... + + + + ... + +); +``` + +### Bottom + +```jsx +import { Card, CardBody, CardMedia } from '@wordpress/components'; + +const Example = () => ( + + ... + + + + +); +``` + +### Only + +`` can also exist as the only child component of ``. + +```jsx +import { Card, CardMedia } from '@wordpress/components'; + +const Example = () => ( + + + + + +); +``` diff --git a/packages/components/src/card/footer.js b/packages/components/src/card/footer.js new file mode 100644 index 00000000000000..e4b66ce4022c78 --- /dev/null +++ b/packages/components/src/card/footer.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { FooterUI } from './styles/card-styles'; +import { useCardContext } from './context'; + +export const defaultProps = { + isBorderless: false, + isShady: false, + size: 'medium', +}; + +export function CardFooter( props ) { + const { className, isShady, ...additionalProps } = props; + const mergedProps = { ...defaultProps, ...useCardContext(), ...props }; + const { isBorderless, size } = mergedProps; + + const classes = classnames( + 'components-card__footer', + isBorderless && 'is-borderless', + isShady && 'is-shady', + size && `is-size-${ size }`, + className + ); + + return ; +} + +export default CardFooter; diff --git a/packages/components/src/card/header.js b/packages/components/src/card/header.js new file mode 100644 index 00000000000000..ece890cddd4604 --- /dev/null +++ b/packages/components/src/card/header.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { HeaderUI } from './styles/card-styles'; +import { useCardContext } from './context'; + +export const defaultProps = { + isBorderless: false, + isShady: false, + size: 'medium', +}; + +export function CardHeader( props ) { + const { className, isShady, ...additionalProps } = props; + const mergedProps = { ...defaultProps, ...useCardContext(), ...props }; + const { isBorderless, size } = mergedProps; + + const classes = classnames( + 'components-card__header', + isBorderless && 'is-borderless', + isShady && 'is-shady', + size && `is-size-${ size }`, + className + ); + + return ; +} + +export default CardHeader; diff --git a/packages/components/src/card/index.js b/packages/components/src/card/index.js new file mode 100644 index 00000000000000..765b980567abf5 --- /dev/null +++ b/packages/components/src/card/index.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { CardContext } from './context'; +import { CardUI } from './styles/card-styles'; + +export const defaultProps = { + isBorderless: false, + isElevated: false, + size: 'medium', +}; + +export function Card( props ) { + const { + className, + isBorderless, + isElevated, + size, + ...additionalProps + } = props; + const { Provider } = CardContext; + + const contextProps = { + isBorderless, + isElevated, + size, + }; + + const classes = classnames( + 'components-card', + isBorderless && 'is-borderless', + isElevated && 'is-elevated', + size && `is-size-${ size }`, + className + ); + + return ( + + + + ); +} + +Card.defaultProps = defaultProps; + +export default Card; diff --git a/packages/components/src/card/media.js b/packages/components/src/card/media.js new file mode 100644 index 00000000000000..c660f6705649de --- /dev/null +++ b/packages/components/src/card/media.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { MediaUI } from './styles/card-styles'; + +export function CardMedia( props ) { + const { className, ...additionalProps } = props; + + const classes = classnames( 'components-card__media', className ); + + return ; +} + +export default CardMedia; diff --git a/packages/components/src/card/stories/_index.js b/packages/components/src/card/stories/_index.js new file mode 100644 index 00000000000000..590a988df931d9 --- /dev/null +++ b/packages/components/src/card/stories/_index.js @@ -0,0 +1,43 @@ +/** + * External dependencies + */ + +/* eslint-disable import/no-extraneous-dependencies */ +import { text, boolean } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardBody from '../body'; +import CardFooter from '../footer'; +import CardHeader from '../header'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card', component: Card }; + +export const _default = () => { + const props = getCardStoryProps(); + + const body = text( 'Body: children', 'Code is Poetry' ); + const isBodyShady = boolean( 'Body: isShady', false ); + + const header = text( 'Header: children', 'Header' ); + const isHeaderShady = boolean( 'Header: isShady', false ); + + const footer = text( 'Footer: children', 'Footer' ); + const isFooterShady = boolean( 'Footer: isShady', false ); + + return ( + + { header && ( + { header } + ) } + { body && { body } } + { footer && ( + { footer } + ) } + + ); +}; diff --git a/packages/components/src/card/stories/_utils.js b/packages/components/src/card/stories/_utils.js new file mode 100644 index 00000000000000..8e34f08d16a814 --- /dev/null +++ b/packages/components/src/card/stories/_utils.js @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, select } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +export const getCardProps = ( props = {} ) => { + const { size } = props; + + return { + isBorderless: boolean( 'Card: isBorderless', false ), + isElevated: boolean( 'Card: isElevated', false ), + size: select( + 'Card: size', + { + large: 'large', + medium: 'medium', + small: 'small', + extraSmall: 'extraSmall', + }, + size || 'medium' + ), + }; +}; + +export const getCardStyleProps = ( props = {} ) => { + const width = select( + 'Example: Width', + { + '640px': 640, + '360px': 360, + '240px': 240, + '50%': '50%', + '100%': '100%', + }, + props.width || '360px' + ); + + return { + style: { + width, + }, + }; +}; + +export const getCardStoryProps = () => { + return { + ...getCardStyleProps(), + ...getCardProps(), + }; +}; diff --git a/packages/components/src/card/stories/body.js b/packages/components/src/card/stories/body.js new file mode 100644 index 00000000000000..de226b72e7e89f --- /dev/null +++ b/packages/components/src/card/stories/body.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, text } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardBody from '../body'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Body', component: CardBody }; + +export const _default = () => { + const props = getCardStoryProps(); + const content = text( 'Body: children', 'Content' ); + const isShady = boolean( 'Body: isShady', false ); + + return ( + + { content } + + ); +}; diff --git a/packages/components/src/card/stories/divider.js b/packages/components/src/card/stories/divider.js new file mode 100644 index 00000000000000..c506cef28e7d58 --- /dev/null +++ b/packages/components/src/card/stories/divider.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import Card from '../index'; +import CardBody from '../body'; +import CardDivider from '../divider'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Divider', component: CardDivider }; + +export const _default = () => { + const props = getCardStoryProps(); + + return ( + + ... + + ... + + ); +}; diff --git a/packages/components/src/card/stories/footer.js b/packages/components/src/card/stories/footer.js new file mode 100644 index 00000000000000..f21e640550c2e3 --- /dev/null +++ b/packages/components/src/card/stories/footer.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, text } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardFooter from '../footer'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Footer', component: CardFooter }; + +export const _default = () => { + const props = getCardStoryProps(); + const content = text( 'Footer: children', 'Content' ); + const isShady = boolean( 'Footer: isShady', false ); + + return ( + + { content } + + ); +}; diff --git a/packages/components/src/card/stories/header.js b/packages/components/src/card/stories/header.js new file mode 100644 index 00000000000000..1314b701a93681 --- /dev/null +++ b/packages/components/src/card/stories/header.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, text } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardHeader from '../header'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Header', component: CardHeader }; + +export const _default = () => { + const props = getCardStoryProps(); + const content = text( 'Footer: children', 'Content' ); + const isShady = boolean( 'Footer: isShady', false ); + + return ( + + { content } + + ); +}; diff --git a/packages/components/src/card/stories/media.js b/packages/components/src/card/stories/media.js new file mode 100644 index 00000000000000..4ecade543fc720 --- /dev/null +++ b/packages/components/src/card/stories/media.js @@ -0,0 +1,166 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, number, select, text } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ +import styled from '@emotion/styled'; + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardBody from '../body'; +import CardFooter from '../footer'; +import CardHeader from '../header'; +import CardMedia from '../media'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Media', component: CardMedia }; + +const DummyImage = () => ( + SMELLING MARSHMELLOW ICECREAM CONE +); + +export const _default = () => { + const mediaOnTop = boolean( 'Example: On Top', true ); + const props = getCardStoryProps(); + + const showTopContent = ! mediaOnTop; + const showBottomContent = ! showTopContent; + const content = text( 'Body: children', 'Content' ); + + return ( + + { showTopContent && { content } } + + + + { showBottomContent && { content } } + + ); +}; + +export const onTop = () => { + const props = getCardStoryProps(); + + return ( + + + + + +
Content
+
+
+ ); +}; + +export const onBottom = () => { + const props = getCardStoryProps(); + + return ( + + Content + + + + + ); +}; + +export const headerAndFooter = () => { + const props = getCardStoryProps(); + + return ( + + Header Content + + + + Caption + + ); +}; + +export const iframeEmbed = () => { + const props = getCardStoryProps(); + + return ( + + Header Content + + + + + ); +}; + +/** + * Referring to other styled components: + * https://www.styled-components.com/docs/advanced#referring-to-other-components + * https://www.styled-components.com/docs/advanced#caveat + */ +const StyledCardMedia = styled( CardMedia )( '' ); +const StyledCardBody = styled( CardBody )( '' ); + +const HorizontallyAlignedCard = styled( Card )` + display: flex; + + &.is-right { + flex-direction: row-reverse; + } + + ${ StyledCardBody } { + flex: 1; + } + + ${ StyledCardMedia } { + &.is-left { + border-radius: 3px 0 0 3px; + } + &.is-right { + border-radius: 0 3px 3px 0; + } + } +`; + +export const horizontallyAligned = () => { + const align = select( + 'Example: Align', + { + left: 'left', + right: 'right', + }, + 'left' + ); + const maxWidth = number( 'Example: Media Width', 200 ); + const content = text( 'Body: children', 'Content' ); + const classes = `is-${ align }`; + + return ( + <> +

+ Note: This story demonstrates how this UI may be created. It + requires extra styling. +

+ + + + + { content } + + + ); +}; diff --git a/packages/components/src/card/styles/card-styles.js b/packages/components/src/card/styles/card-styles.js new file mode 100644 index 00000000000000..150ff2ba1f57c9 --- /dev/null +++ b/packages/components/src/card/styles/card-styles.js @@ -0,0 +1,156 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; + +/** + * Internal dependencies + */ +import { HorizontalRule } from '../../primitives'; +import { color } from '../../utils/colors'; + +export const styleProps = { + borderColor: color( 'lightGray.500' ), + borderRadius: '3px', + backgroundShady: color( 'lightGray.200' ), +}; + +const { borderColor, borderRadius, backgroundShady } = styleProps; + +export const CardUI = styled.div` + background: ${ color( 'white' ) }; + box-sizing: border-box; + border-radius: ${ borderRadius }; + border: 1px solid ${ borderColor }; + + ${ handleBorderless }; + + &.is-elevated { + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), + 0px 2px 1px -1px rgba(0, 0, 0, 0.12); + } +`; + +export const HeaderUI = styled.div` + border-bottom: 1px solid ${ borderColor }; + border-top-left-radius: ${ borderRadius }; + border-top-right-radius: ${ borderRadius }; + box-sizing: border-box; + + &:last-child { + border-bottom: none; + } + + ${ headerFooterSizes }; + ${ handleBorderless }; + ${ handleShady }; +`; + +export const MediaUI = styled.div` + box-sizing: border-box; + overflow: hidden; + + & > img, + & > iframe { + display: block; + height: auto; + max-width: 100%; + width: 100%; + } + + &:first-of-type { + border-top-left-radius: ${ borderRadius }; + border-top-right-radius: ${ borderRadius }; + } + + &:last-of-type { + border-bottom-left-radius: ${ borderRadius }; + border-bottom-right-radius: ${ borderRadius }; + } +`; + +export const BodyUI = styled.div` + box-sizing: border-box; + + ${ bodySize }; + ${ handleShady }; +`; + +export const FooterUI = styled.div` + border-top: 1px solid ${ borderColor }; + border-bottom-left-radius: ${ borderRadius }; + border-bottom-right-radius: ${ borderRadius }; + box-sizing: border-box; + + &:first-of-type { + border-top: none; + } + + ${ headerFooterSizes }; + ${ handleBorderless }; + ${ handleShady }; +`; + +export const DividerUI = styled( HorizontalRule )` + all: unset; + border-top: 1px solid ${ borderColor }; + box-sizing: border-box; + display: block; + height: 0; + width: 100%; +`; + +export function bodySize() { + return ` + &.is-size { + &-large { + padding: 28px; + } + &-medium { + padding: 20px; + } + &-small { + padding: 12px; + } + &-extraSmall { + padding: 8px; + } + } + `; +} + +export function headerFooterSizes() { + return ` + &.is-size { + &-large { + padding: 20px 28px; + } + &-medium { + padding: 12px 20px; + } + &-small { + padding: 8px 12px; + } + &-extraSmall { + padding: 4px 8px; + } + } + `; +} + +export function handleBorderless() { + return ` + &.is-borderless { + border: none; + } + `; +} + +export function handleShady() { + return ` + &.is-shady { + background: ${ backgroundShady }; + } + `; +} diff --git a/packages/components/src/card/test/body.js b/packages/components/src/card/test/body.js new file mode 100644 index 00000000000000..dbbe21cdd6028a --- /dev/null +++ b/packages/components/src/card/test/body.js @@ -0,0 +1,48 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardBody from '../body'; + +describe( 'CardBody', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( ); + const cardBody = wrapper.find( '.components-card__body' ); + + expect( cardBody.length ).toBe( 1 ); + } ); + + test( 'should be able to render content', () => { + const cardBody = shallow( + +
Hello
+
+ ); + const content = cardBody.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); + + describe( 'modifiers', () => { + test( 'should be able to render size modifier', () => { + const wrapper = shallow( ); + const cardBody = wrapper.find( '.components-card__body' ); + + expect( cardBody.hasClass( 'is-size-large' ) ).toBe( true ); + } ); + + test( 'should be able to render shady modifier', () => { + const wrapper = shallow( ); + const cardBody = wrapper.find( '.components-card__body' ); + + expect( cardBody.hasClass( 'is-shady' ) ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/divider.js b/packages/components/src/card/test/divider.js new file mode 100644 index 00000000000000..81f6012c9cd59a --- /dev/null +++ b/packages/components/src/card/test/divider.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardDivider from '../divider'; + +describe( 'CardDivider', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( ); + const cardDivider = wrapper.find( '.components-card__divider' ); + + expect( cardDivider.length ).toBe( 1 ); + } ); + + test( 'should not render child content', () => { + const cardDivider = shallow( + +
Hello
+
+ ); + const content = cardDivider.find( '.content' ); + + expect( content.length ).toBe( 0 ); + } ); + + test( 'should have role of separator', () => { + const wrapper = shallow( ); + const cardDivider = wrapper.find( '.components-card__divider' ); + + expect( cardDivider.prop( 'role' ) ).toBe( 'separator' ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/footer.js b/packages/components/src/card/test/footer.js new file mode 100644 index 00000000000000..bcd4308782437a --- /dev/null +++ b/packages/components/src/card/test/footer.js @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardFooter from '../footer'; + +describe( 'CardFooter', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( ); + const cardFooter = wrapper.find( '.components-card__footer' ); + + expect( cardFooter.length ).toBe( 1 ); + } ); + + test( 'should be able to render content', () => { + const cardFooter = shallow( + +
Hello
+
+ ); + const content = cardFooter.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); + + describe( 'modifiers', () => { + test( 'should be able to render size modifier', () => { + const wrapper = shallow( ); + const cardFooter = wrapper.find( '.components-card__footer' ); + + expect( cardFooter.hasClass( 'is-size-large' ) ).toBe( true ); + } ); + + test( 'should be able to render shady modifier', () => { + const wrapper = shallow( ); + const cardFooter = wrapper.find( '.components-card__footer' ); + + expect( cardFooter.hasClass( 'is-shady' ) ).toBe( true ); + } ); + + test( 'should be able to render borderless modifier', () => { + const wrapper = shallow( ); + const cardFooter = wrapper.find( '.components-card__footer' ); + + expect( cardFooter.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/header.js b/packages/components/src/card/test/header.js new file mode 100644 index 00000000000000..3323658aaa7b6b --- /dev/null +++ b/packages/components/src/card/test/header.js @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardHeader from '../header'; + +describe( 'CardHeader', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( ); + const cardHeader = wrapper.find( '.components-card__header' ); + + expect( cardHeader.length ).toBe( 1 ); + } ); + + test( 'should be able to render content', () => { + const cardHeader = shallow( + +
Hello
+
+ ); + const content = cardHeader.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); + + describe( 'modifiers', () => { + test( 'should be able to render size modifier', () => { + const wrapper = shallow( ); + const cardHeader = wrapper.find( '.components-card__header' ); + + expect( cardHeader.hasClass( 'is-size-large' ) ).toBe( true ); + } ); + + test( 'should be able to render shady modifier', () => { + const wrapper = shallow( ); + const cardHeader = wrapper.find( '.components-card__header' ); + + expect( cardHeader.hasClass( 'is-shady' ) ).toBe( true ); + } ); + + test( 'should be able to render borderless modifier', () => { + const wrapper = shallow( ); + const cardHeader = wrapper.find( '.components-card__header' ); + + expect( cardHeader.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/index.js b/packages/components/src/card/test/index.js new file mode 100644 index 00000000000000..16b4d3158971a0 --- /dev/null +++ b/packages/components/src/card/test/index.js @@ -0,0 +1,195 @@ +/** + * External dependencies + */ +import { mount, shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import Card from '../'; +import CardBody from '../body'; +import CardDivider from '../divider'; +import CardFooter from '../footer'; +import CardHeader from '../header'; +import CardMedia from '../media'; + +describe( 'Card', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( ); + const card = wrapper.find( '.components-card' ); + + expect( card.length ).toBeTruthy(); + } ); + + test( 'should be able to render content', () => { + const card = shallow( + +
Hello
+
+ ); + const content = card.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); + + describe( 'styles', () => { + test( 'should render borderless', () => { + const wrapper = shallow( ); + const card = wrapper.find( '.components-card' ); + + expect( card.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + + test( 'should render elevated styles', () => { + const wrapper = shallow( ); + const card = wrapper.find( '.components-card' ); + + expect( card.hasClass( 'is-elevated' ) ).toBe( true ); + } ); + } ); + + describe( 'CardBody', () => { + test( 'should be able to render', () => { + const card = mount( + + Hello + + ); + const cardBody = card.find( 'CardBody' ); + + expect( cardBody.length ).toBe( 1 ); + expect( cardBody.text() ).toBe( 'Hello' ); + } ); + + test( 'should receive modifier props from context', () => { + const card = mount( + + Hello + + ); + const cardBody = card.find( '.components-card__body' ).first(); + + expect( cardBody.hasClass( 'is-size-extraSmall' ) ).toBe( true ); + } ); + + test( 'should be able to override props from context', () => { + const card = mount( + + Hello + + ); + const cardBody = card.find( '.components-card__body' ).first(); + + expect( cardBody.hasClass( 'is-size-large' ) ).toBe( true ); + } ); + } ); + + describe( 'CardHeader', () => { + test( 'should be able to render', () => { + const card = mount( + + Hello + + ); + const cardHeader = card.find( 'CardHeader' ); + + expect( cardHeader.length ).toBe( 1 ); + expect( cardHeader.text() ).toBe( 'Hello' ); + } ); + + test( 'should receive modifier props from context', () => { + const card = mount( + + Hello + + ); + const cardHeader = card.find( '.components-card__header' ).first(); + + expect( cardHeader.hasClass( 'is-size-extraSmall' ) ).toBe( true ); + expect( cardHeader.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + + test( 'should be able to override props from context', () => { + const card = mount( + + + Hello + + + ); + const cardHeader = card.find( '.components-card__header' ).first(); + + expect( cardHeader.hasClass( 'is-size-large' ) ).toBe( true ); + expect( cardHeader.hasClass( 'is-borderless' ) ).toBe( false ); + } ); + } ); + + describe( 'CardFooter', () => { + test( 'should be able to render', () => { + const card = mount( + + Hello + + ); + const cardFooter = card.find( 'CardFooter' ); + + expect( cardFooter.length ).toBe( 1 ); + expect( cardFooter.text() ).toBe( 'Hello' ); + } ); + + test( 'should receive modifier props from context', () => { + const card = mount( + + Hello + + ); + const cardFooter = card.find( '.components-card__footer' ).first(); + + expect( cardFooter.hasClass( 'is-size-extraSmall' ) ).toBe( true ); + expect( cardFooter.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + + test( 'should be able to override props from context', () => { + const card = mount( + + + Hello + + + ); + const cardFooter = card.find( '.components-card__footer' ).first(); + + expect( cardFooter.hasClass( 'is-size-large' ) ).toBe( true ); + expect( cardFooter.hasClass( 'is-borderless' ) ).toBe( false ); + } ); + } ); + + describe( 'CardDivider', () => { + test( 'should be able to render', () => { + const card = mount( + + + + ); + const cardBody = card.find( 'CardDivider' ); + + expect( cardBody.length ).toBe( 1 ); + } ); + } ); + + describe( 'CardMedia', () => { + test( 'should be able to render', () => { + const card = mount( + + + + ); + const cardBody = card.find( 'CardMedia' ); + + expect( cardBody.length ).toBe( 1 ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/media.js b/packages/components/src/card/test/media.js new file mode 100644 index 00000000000000..935134d281dc21 --- /dev/null +++ b/packages/components/src/card/test/media.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardMedia from '../media'; + +describe( 'CardMedia', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( ); + const cardMedia = wrapper.find( '.components-card__media' ); + + expect( cardMedia.length ).toBe( 1 ); + } ); + + test( 'should be able to render content', () => { + const cardBody = shallow( + +
Hello
+
+ ); + const content = cardBody.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); +} ); diff --git a/packages/components/src/index.js b/packages/components/src/index.js index ac9694a49ba57b..3803719c2d8d1b 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -5,6 +5,12 @@ export { default as Autocomplete } from './autocomplete'; export { default as BaseControl } from './base-control'; export { default as Button } from './button'; export { default as ButtonGroup } from './button-group'; +export { default as Card } from './card'; +export { default as CardBody } from './card/body'; +export { default as CardDivider } from './card/divider'; +export { default as CardFooter } from './card/footer'; +export { default as CardHeader } from './card/header'; +export { default as CardMedia } from './card/media'; export { default as CheckboxControl } from './checkbox-control'; export { default as ClipboardButton } from './clipboard-button'; export { default as ColorIndicator } from './color-indicator'; @@ -65,14 +71,28 @@ export { default as Tooltip } from './tooltip'; export { default as TreeSelect } from './tree-select'; export { default as VisuallyHidden } from './visually-hidden'; export { default as IsolatedEventContainer } from './isolated-event-container'; -export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; +export { + createSlotFill, + Slot, + Fill, + Provider as SlotFillProvider, +} from './slot-fill'; // Higher-Order Components export { default as navigateRegions } from './higher-order/navigate-regions'; -export { default as withConstrainedTabbing } from './higher-order/with-constrained-tabbing'; -export { default as withFallbackStyles } from './higher-order/with-fallback-styles'; +export { + default as withConstrainedTabbing, +} from './higher-order/with-constrained-tabbing'; +export { + default as withFallbackStyles, +} from './higher-order/with-fallback-styles'; export { default as withFilters } from './higher-order/with-filters'; export { default as withFocusOutside } from './higher-order/with-focus-outside'; -export { default as withFocusReturn, Provider as FocusReturnProvider } from './higher-order/with-focus-return'; +export { + default as withFocusReturn, + Provider as FocusReturnProvider, +} from './higher-order/with-focus-return'; export { default as withNotices } from './higher-order/with-notices'; -export { default as withSpokenMessages } from './higher-order/with-spoken-messages'; +export { + default as withSpokenMessages, +} from './higher-order/with-spoken-messages'; diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js new file mode 100644 index 00000000000000..8dfca09543f5e9 --- /dev/null +++ b/packages/components/src/utils/colors-values.js @@ -0,0 +1,115 @@ +/** + * Internal dependencies + */ +import { rgba } from './colors'; +export const BASE = { + black: '#000', + white: '#fff', +}; + +export const DARK_GRAY = { + 900: '#191e23', + 800: '#23282d', + 700: '#32373c', + 600: '#40464d', + 500: '#555d66', // Use this most of the time for dark items. + 400: '#606a73', + 300: '#6c7781', // Lightest gray that can be used for AA text contrast. + 200: '#7e8993', + 150: '#8d96a0', // Lightest gray that can be used for AA non-text contrast. + 100: '#8f98a1', +}; + +export const DARK_OPACITY = { + 900: rgba( '#000510', 0.9 ), + 800: rgba( '#00000a', 0.85 ), + 700: rgba( '#06060b', 0.8 ), + 600: rgba( '#000913', 0.75 ), + 500: rgba( '#0a1829', 0.7 ), + 400: rgba( '#0a1829', 0.65 ), + 300: rgba( '#0e1c2e', 0.62 ), + 200: rgba( '#162435', 0.55 ), + 100: rgba( '#223443', 0.5 ), + backgroundFill: rgba( DARK_GRAY[ 700 ], 0.7 ), +}; + +export const DARK_OPACITY_LIGHT = { + 900: rgba( '#304455', 0.45 ), + 800: rgba( '#425863', 0.4 ), + 700: rgba( '#667886', 0.35 ), + 600: rgba( '#7b86a2', 0.3 ), + 500: rgba( '#9197a2', 0.25 ), + 400: rgba( '#95959c', 0.2 ), + 300: rgba( '#829493', 0.15 ), + 200: rgba( '#8b8b96', 0.1 ), + 100: rgba( '#747474', 0.05 ), +}; + +export const LIGHT_GRAY = { + 900: '#a2aab2', + 800: '#b5bcc2', + 700: '#ccd0d4', + 600: '#d7dade', + 500: '#e2e4e7', // Good for "grayed" items and borders. + 400: '#e8eaeb', // Good for "readonly" input fields and special text selection. + 300: '#edeff0', + 200: '#f3f4f5', + 100: '#f8f9f9', +}; + +export const LIGHT_OPACITY_LIGHT = { + 900: rgba( BASE.white, 0.5 ), + 800: rgba( BASE.white, 0.45 ), + 700: rgba( BASE.white, 0.4 ), + 600: rgba( BASE.white, 0.35 ), + 500: rgba( BASE.white, 0.3 ), + 400: rgba( BASE.white, 0.25 ), + 300: rgba( BASE.white, 0.2 ), + 200: rgba( BASE.white, 0.15 ), + 100: rgba( BASE.white, 0.1 ), + backgroundFill: rgba( LIGHT_GRAY[ 300 ], 0.8 ), +}; + +// Additional colors. +// Some are from https://make.wordpress.org/design/handbook/foundations/colors/. + +export const BLUE = { + wordpress: { + 700: '#00669b', + }, + dark: { + 900: '#0071a1', + }, + medium: { + 900: '#006589', + 800: '#00739c', + 700: '#007fac', + 600: '#008dbe', + 500: '#00a0d2', + 400: '#33b3db', + 300: '#66c6e4', + 200: '#bfe7f3', + 100: '#e5f5fa', + highlight: '#b3e7fe', + focus: '#007cba', + }, +}; + +export const ALERT = { + yellow: '#f0b849', + red: '#d94f4f', + green: '#4ab866', +}; + +export const COLORS = { + ...BASE, + darkGrey: DARK_GRAY, + darkOpacity: DARK_OPACITY, + darkOpacityLight: DARK_OPACITY_LIGHT, + lightGray: LIGHT_GRAY, + lightGrayLight: LIGHT_OPACITY_LIGHT, + blue: BLUE, + alert: ALERT, +}; + +export default COLORS; diff --git a/packages/components/src/utils/colors.js b/packages/components/src/utils/colors.js new file mode 100644 index 00000000000000..c4a1c6dae93324 --- /dev/null +++ b/packages/components/src/utils/colors.js @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; +import hexRgb from 'hex-rgb'; +/** + * Internal dependencies + */ +import { COLORS } from './colors-values'; + +/** + * Generating a CSS complient rgba() color value. + * + * @param {string} hexValue The hex value to convert to rgba(). + * @param {number} alpha The alpha value for opacity. + * @return {string} The converted rgba() color value. + * + * @example + * rgba( '#000000', 0.5 ) + * // rgba(0, 0, 0, 0.5) + */ +export function rgba( hexValue = '', alpha = 1 ) { + const [ r, g, b ] = hexRgb( hexValue, { format: 'array' } ); + return `rgba(${ r }, ${ g }, ${ b }, ${ alpha })`; +} + +/** + * Retrieves a color from the color palette. + * + * @param {string} value The value to retrieve. + * @return {string} The color (or fallback, if not found). + * + * @example + * color( 'blue.wordpress.700' ) + * // #00669b + */ +export function color( value ) { + const fallbackColor = '#000'; + return get( COLORS, value, fallbackColor ); +} diff --git a/storybook/.babelrc b/storybook/.babelrc index c97694a2f21a53..fe77bd84598748 100644 --- a/storybook/.babelrc +++ b/storybook/.babelrc @@ -1,6 +1,7 @@ { "presets": [ "@wordpress/babel-preset-default" ], "plugins": [ + "babel-plugin-emotion", "babel-plugin-inline-json-import" ] }