diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c52b508..0d55914 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v2 diff --git a/package-lock.json b/package-lock.json index 1302363..35482a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,13 @@ }, "devDependencies": { "@carbonplan/prettier": "^1.2.0", + "@types/node": "^22.13.10", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", "microbundle": "^0.13.0", "prettier": "^2.2.1", - "rimraf": "3.0.2" + "rimraf": "3.0.2", + "typescript": "^5.8.2" }, "peerDependencies": { "@emotion/react": ">=11.1.5", @@ -2595,16 +2599,48 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.8.tgz", - "integrity": "sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A==", - "dev": true + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -3368,6 +3404,7 @@ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, + "license": "MIT", "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -3391,6 +3428,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -3417,6 +3455,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -3447,9 +3486,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/generic-names": { "version": "4.0.0", @@ -3511,10 +3554,11 @@ "dev": true }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" }, "node_modules/gzip-size": { "version": "6.0.0", @@ -3764,6 +3808,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, + "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -3805,6 +3850,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -3868,6 +3914,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^6.0.0" }, @@ -3977,6 +4024,7 @@ "resolved": "https://registry.npmjs.org/microbundle/-/microbundle-0.13.3.tgz", "integrity": "sha512-nlP20UmyqGGeh6jhk8VaVFEoRlF+JAvnwixPLQUwHEcAF59ROJCyh34eylJzUAVNvF3yrCaHxIBv8lYcphDM1g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.12.10", "@babel/plugin-proposal-class-properties": "7.12.1", @@ -4023,6 +4071,20 @@ "microbundle": "dist/cli.js" } }, + "node_modules/microbundle/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4216,6 +4278,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -4231,6 +4294,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -4271,6 +4335,7 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4308,6 +4373,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4368,6 +4434,7 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -5318,6 +5385,7 @@ "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.29.0.tgz", "integrity": "sha512-YytahBSZCIjn/elFugEGQR5qTsVhxhUwGZIsA9TmrSsC88qroGo65O5HZP/TTArH2dm0vUmYWhKchhwi2wL9bw==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^3.1.0", "find-cache-dir": "^3.3.1", @@ -5335,6 +5403,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, + "license": "MIT", "dependencies": { "path-parse": "^1.0.6" }, @@ -5346,7 +5415,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/rollup-pluginutils": { "version": "2.8.2", @@ -5685,18 +5755,26 @@ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -5742,6 +5820,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } @@ -7611,16 +7690,42 @@ "dev": true }, "@types/node": { - "version": "18.11.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.8.tgz", - "integrity": "sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A==", - "dev": true + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "dev": true, + "requires": { + "undici-types": "~6.20.0" + } }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "dev": true + }, + "@types/react": { + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, + "requires": {} + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -8227,9 +8332,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "generic-names": { "version": "4.0.0", @@ -8279,9 +8384,9 @@ "dev": true }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "gzip-size": { @@ -8693,6 +8798,14 @@ "tiny-glob": "^0.2.8", "tslib": "^2.0.3", "typescript": "^4.1.3" + }, + "dependencies": { + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } } }, "minimatch": { @@ -9838,9 +9951,15 @@ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true + }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true }, "unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index b00e3e8..860241f 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,12 @@ "description": "shared components for our websites", "main": "dst/index.js", "module": "dst/index.esm.js", + "types": "dst/types/index.d.ts", "scripts": { - "build": "rimraf dst && microbundle src/index.js -o dst/index.js --jsx React.createElement -f modern,es,cjs --jsxFragment React.Fragment", - "watch": "microbundle watch src/index.js -o dst/index.js --jsx React.createElement -f modern,es,cjs --jsxFragment React.Fragment", - "format": "prettier --write 'src/**/*.js' '*.css'" + "clean": "rimraf dst", + "build": "npm run clean && tsc --emitDeclarationOnly && microbundle src/index.ts -o dst/index.js --jsx React.createElement -f modern,es,cjs --jsxFragment React.Fragment", + "watch": "npm run clean && microbundle watch src/index.ts -o dst/index.js --jsx React.createElement -f modern,es,cjs --jsxFragment React.Fragment & tsc --watch --emitDeclarationOnly", + "format": "prettier --write 'src/**/*.{ts,tsx,js}' '*.css'" }, "repository": { "type": "git", @@ -45,8 +47,12 @@ }, "devDependencies": { "@carbonplan/prettier": "^1.2.0", + "@types/node": "^22.13.10", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", "microbundle": "^0.13.0", "prettier": "^2.2.1", - "rimraf": "3.0.2" + "rimraf": "3.0.2", + "typescript": "^5.8.2" } } diff --git a/src/avatar-group.js b/src/avatar-group.tsx similarity index 51% rename from src/avatar-group.js rename to src/avatar-group.tsx index 776d070..cdd7f6e 100644 --- a/src/avatar-group.js +++ b/src/avatar-group.tsx @@ -1,9 +1,9 @@ import React from 'react' -import { Box } from 'theme-ui' -import Avatar from './avatar' -import Row from './row' +import { Box, ResponsiveStyleValue } from 'theme-ui' +import Avatar, { AvatarProps } from './avatar' +import Row, { RowProps } from './row' import Column from './column' -import Group from './group' +import Group, { GroupProps } from './group' const sizes = { xs: [1], @@ -13,7 +13,14 @@ const sizes = { xl: [9], } -const Blank = ({ overflow, maxWidth }) => { +type SizeKey = keyof typeof sizes + +type BlankProps = { + overflow: number + maxWidth?: string | number +} + +const Blank = ({ overflow, maxWidth }: BlankProps) => { return ( { ) } +type Alignment = 'left' | 'right' + +type StartValue = 'auto' | number | (number | 'auto')[] + +export interface AvatarGroupProps extends RowProps, GroupProps { + members: AvatarProps[] + direction?: 'horizontal' | 'vertical' + align?: Alignment | Alignment[] + spacing?: SizeKey | ResponsiveStyleValue + limit?: number + width?: string + maxWidth?: string | number + fixedCount?: number +} + const AvatarGroup = ({ members, direction = 'horizontal', @@ -56,34 +78,37 @@ const AvatarGroup = ({ fixedCount, sx, ...props -}) => { - let gap - if (sizes.hasOwnProperty(spacing)) { - gap = sizes[spacing] +}: AvatarGroupProps) => { + let gap: ResponsiveStyleValue + + if (typeof spacing === 'string' && spacing in sizes) { + gap = sizes[spacing as SizeKey] } else { - gap = spacing + gap = spacing as ResponsiveStyleValue } - let start = (idx) => 'auto' + let start = (idx: number): StartValue => 'auto' if (align) { if (!Array.isArray(align)) { align = [align] } - start = (idx) => - align.map((d) => { + start = (idx: number): StartValue => { + const alignArray = align as Alignment[] + return alignArray.map((d: Alignment) => { if (d === 'left') { return 'auto' } else if (d === 'right') { - const offset = Math.max(1, fixedCount - members.length + 1) - return (offset + idx) % fixedCount + const offset = Math.max(1, (fixedCount ?? 0) - members.length + 1) + return (offset + idx) % (fixedCount ?? 1) } else { - throw Error(`alignment '${align}' not recognized`) + throw Error(`alignment '${d}' not recognized`) } }) + } } - const excess = members.length > limit - const overflow = members.length - limit + 1 + const excess = limit !== undefined && members.length > limit + const overflow = limit !== undefined ? members.length - limit + 1 : 0 return ( <> @@ -91,10 +116,10 @@ const AvatarGroup = ({ {members.map((props, idx) => ( - {(!excess || idx < limit - 1) && ( + {(!excess || (limit !== undefined && idx < limit - 1)) && ( )} - {excess && idx === limit - 1 && ( + {excess && limit !== undefined && idx === limit - 1 && ( )} diff --git a/src/avatar.js b/src/avatar.tsx similarity index 84% rename from src/avatar.js rename to src/avatar.tsx index 223c566..539a643 100644 --- a/src/avatar.js +++ b/src/avatar.tsx @@ -1,5 +1,15 @@ import React from 'react' -import { Box, Image } from 'theme-ui' +import { Box, Image, BoxProps } from 'theme-ui' + +export interface AvatarProps extends BoxProps { + color?: string + width?: string + maxWidth?: string | number + name?: string + github?: string + alt?: string + src?: string +} const Avatar = ({ color = 'transparent', @@ -11,7 +21,7 @@ const Avatar = ({ src, sx, ...props -}) => { +}: AvatarProps) => { if (!name && !src && !github) { console.warn('must specify either name, github, or src') } diff --git a/src/badge.js b/src/badge.tsx similarity index 75% rename from src/badge.js rename to src/badge.tsx index f72f2eb..5fee12d 100644 --- a/src/badge.js +++ b/src/badge.tsx @@ -1,8 +1,14 @@ import React from 'react' -import { Box } from 'theme-ui' +import { Box, BoxProps, ThemeUIStyleObject } from 'theme-ui' import { transparentize } from '@theme-ui/color' -const Badge = ({ sx, children, ...props }) => { +export interface BadgeProps extends BoxProps { + sx?: ThemeUIStyleObject & { + color?: string // ThemeUIStyleObject doesn't have a color property + } +} + +const Badge = ({ sx, children, ...props }: BadgeProps) => { const color = sx && sx.color ? sx.color : 'primary' return ( + const specialChars = ['“', '"', "'", '‘'] -const Blockquote = ({ children }) => { +const Blockquote = ({ children }: BlockquoteProps) => { return ( {Children.map(children, (d, i) => { let firstChar = '' let remaining = children - - if (d.props && typeof d.props.children === 'string') { + if (React.isValidElement(d) && typeof d.props.children === 'string') { firstChar = d.props.children.slice(0, 1) remaining = d.props.children.slice(1) } else if (typeof d === 'string') { diff --git a/src/button.js b/src/button.tsx similarity index 71% rename from src/button.js rename to src/button.tsx index 8b190be..068c3d9 100644 --- a/src/button.js +++ b/src/button.tsx @@ -1,8 +1,33 @@ import React, { forwardRef, cloneElement } from 'react' -import { Box } from 'theme-ui' -import Link from './link' +import { Box, BoxProps, ThemeUIStyleObject } from 'theme-ui' +import Link, { LinkProps } from './link' import getSizeStyles from './utils/get-size-styles' +const hasCustomHover = (comp: any): comp is { hover: ThemeUIStyleObject } => + !!comp?.hover + +export interface ButtonProps extends Omit { + size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' + align?: + | 'baseline' + | 'sub' + | 'super' + | 'text-top' + | 'text-bottom' + | 'middle' + | 'top' + | 'bottom' + | 'initial' + suffix?: React.ReactElement & { props?: { sx?: ThemeUIStyleObject } } + prefix?: React.ReactElement & { props?: { sx?: ThemeUIStyleObject } } + inverted?: boolean + href?: string + internal?: boolean + sx?: ThemeUIStyleObject & { + color?: string // ThemeUIStyleObject doesn't have a color property + } +} + const Button = ( { size = 'sm', @@ -15,14 +40,14 @@ const Button = ( href, internal, ...props - }, - ref + }: ButtonProps, + ref: React.Ref ) => { if (!['xs', 'sm', 'md', 'lg', 'xl'].includes(size)) { throw new Error('Size must be xs, sm, md, lg, or xl') } - let offset, margin, top, height, width, strokeWidth + let offset, margin, height, width, strokeWidth const { color, ...sxProp } = sx || {} @@ -99,14 +124,16 @@ const Button = ( suffixOffset = offset } + let clonedPrefix, clonedSuffix + if (prefix) { prefixHover = { '&:hover > #prefix-span > #prefix': { color: hoverColor, - ...prefix.type.hover, + ...(hasCustomHover(prefix.type) ? prefix.type.hover : {}), }, } - prefix = cloneElement(prefix, { + clonedPrefix = cloneElement(prefix, { id: 'prefix', sx: { position: 'relative', @@ -116,7 +143,7 @@ const Button = ( strokeWidth: strokeWidth, verticalAlign: prefixAlign, transition: 'color 0.15s, transform 0.15s', - ...prefix.props.sx, + ...prefix.props?.sx, }, }) } @@ -125,10 +152,10 @@ const Button = ( suffixHover = { '&:hover > #suffix-span >#suffix': { color: hoverColor, - ...suffix.type.hover, + ...(hasCustomHover(suffix.type) ? suffix.type.hover : {}), }, } - suffix = cloneElement(suffix, { + clonedSuffix = cloneElement(suffix, { id: 'suffix', sx: { height: height, @@ -137,7 +164,7 @@ const Button = ( strokeWidth: strokeWidth, verticalAlign: suffixAlign, transition: 'color 0.15s, transform 0.15s', - ...suffix.props.sx, + ...suffix.props?.sx, }, }) } @@ -152,7 +179,7 @@ const Button = ( display: 'block', color: baseColor, padding: [0], - textAlign: 'left', + textAlign: 'left' as const, cursor: 'pointer', width: 'fit-content', '@media (hover: hover) and (pointer: fine)': { @@ -172,7 +199,7 @@ const Button = ( id='prefix-span' sx={{ display: 'inline-block', ...prefixOffset }} > - {prefix && prefix} + {clonedPrefix} {children} @@ -182,7 +209,7 @@ const Button = ( id='suffix-span' sx={{ display: 'inline-block', ...suffixOffset }} > - {suffix && suffix} + {clonedSuffix} ) @@ -190,25 +217,31 @@ const Button = ( if (href) { return ( } internal={internal} sx={{ ...style, textDecoration: 'none', }} - {...props} + {...(props as LinkProps)} > {Inner} ) } else { return ( - + } + as='button' + sx={style} + {...props} + > {Inner} ) } } -export default forwardRef(Button) +export default forwardRef( + Button +) diff --git a/src/callout.js b/src/callout.tsx similarity index 66% rename from src/callout.js rename to src/callout.tsx index af2468a..c74c88e 100644 --- a/src/callout.js +++ b/src/callout.tsx @@ -1,11 +1,33 @@ -import React, { forwardRef } from 'react' -import { Box } from 'theme-ui' +import React, { forwardRef, ReactNode } from 'react' +import { Box, ThemeUIStyleObject } from 'theme-ui' +// @ts-ignore import { Arrow } from '@carbonplan/icons' import Link from './link' +export interface CalloutProps { + label: ReactNode + children: ReactNode + inverted?: boolean + color: string + href?: string + internal?: boolean + sx?: ThemeUIStyleObject +} + +type RefType = React.Ref + const Callout = ( - { label, children, inverted, color, href, internal, sx, ...props }, - ref + { + label, + children, + inverted, + color, + href, + internal, + sx, + ...props + }: CalloutProps, + ref: RefType ) => { const baseColor = color || (inverted ? 'secondary' : 'primary') const hoverColor = color ? 'primary' : inverted ? 'primary' : 'secondary' @@ -22,7 +44,7 @@ const Callout = ( letterSpacing: 'body', width: 'fit-content', cursor: 'pointer', - textAlign: 'left', + textAlign: 'left' as const, mb: [1], '@media (hover: hover) and (pointer: fine)': { '&:hover > #container > #arrow': { @@ -78,17 +100,30 @@ const Callout = ( if (href) { return ( - + } + href={href} + internal={internal} + sx={style} + {...props} + > {Inner} ) } else { return ( - + } + as='button' + sx={style} + {...props} + > {Inner} ) } } -export default forwardRef(Callout) +export default forwardRef( + Callout +) diff --git a/src/caption.js b/src/caption.tsx similarity index 71% rename from src/caption.js rename to src/caption.tsx index 28688c1..d80dd98 100644 --- a/src/caption.js +++ b/src/caption.tsx @@ -1,7 +1,19 @@ -import React from 'react' -import { Box } from 'theme-ui' +import React, { ReactNode } from 'react' +import { Box, BoxProps } from 'theme-ui' -const Caption = ({ as = 'figcaption', number, children, label = 'figure' }) => { +export interface CaptionProps { + as?: BoxProps['as'] + number?: number + children: ReactNode + label?: string +} + +const Caption = ({ + as = 'figcaption', + number, + children, + label = 'figure', +}: CaptionProps) => { return ( [number, number]) => any + +export interface ColorbarProps extends FlexProps { + colormap: string[] + label: ReactNode + clim: [number, number] + setClim?: SetClim + setClimStep?: number + units: ReactNode + width: string + height: string + format?: (d: number) => ReactNode + discrete?: boolean + horizontal?: boolean + bottom?: boolean + sx?: ThemeUIStyleObject + sxClim?: ThemeUIStyleObject +} const styles = { - clim: (setClim) => { + clim: (setClim?: SetClim): ThemeUIStyleObject & { userSelect: any } => { return { bg: 'unset', border: 'none', @@ -25,7 +49,7 @@ const DIMENSIONS = { height: ['80px', '110px', '110px', '130px'], } -const hexToRgb = (hex) => { +const hexToRgb = (hex: string) => { let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt( @@ -35,7 +59,16 @@ const hexToRgb = (hex) => { : null } -const Gradient = ({ colormap, discrete, horizontal, width, height }) => { +const Gradient = ({ + colormap, + discrete, + horizontal, + width, + height, +}: Pick< + ColorbarProps, + 'colormap' | 'discrete' | 'horizontal' | 'width' | 'height' +>) => { const step = (1 / colormap.length) * 100 const isHex = String(colormap[0]).startsWith('#') const values = colormap.map((color, i) => { @@ -63,20 +96,26 @@ const Gradient = ({ colormap, discrete, horizontal, width, height }) => { minHeight: height || DIMENSIONS.height, }), mt: horizontal ? ['1px', '1px', '1px', 0] : 0, - border: ({ colors }) => `solid 1px ${colors.hinted}`, + border: (t) => `solid 1px ${get(t, 'colors.hinted')}`, background: css, }} /> ) } -const Label = ({ label, units, horizontal }) => ( +const Label = ({ + label, + units, + horizontal, +}: Pick) => ( { +}: ColorbarProps) => { if (!Array.isArray(colormap)) { throw new Error(`expected array for colormap, got '${colormap}'.`) } - const climRef = [useRef(), useRef()] + const climRef = [useRef(), useRef()] const [climMinDragging, setClimMinDragging] = useState(false) const [climMaxDragging, setClimMaxDragging] = useState(false) - let x, - y, - dx, - dy = 0 - let id = null + let x: number, + y: number, + dx: number, + dy: number = 0 + let id: null | string = null let init = [0, 0] let scale = setClimStep - const draggingFunction = (e) => { + const draggingFunction = (e: MouseEvent) => { + if (!setClim) return if (id === 'min' && !climMinDragging) setClimMinDragging(true) if (id === 'max' && !climMaxDragging) setClimMaxDragging(true) dx = e.pageX - x @@ -163,10 +203,10 @@ const Colorbar = ({ } } - const handleMouseDown = (e) => { + const handleMouseDown: MouseEventHandler = (e) => { y = e.pageY x = e.pageX - id = e.target.id + id = (e.target as HTMLDivElement).id init = clim document.body.setAttribute( @@ -186,7 +226,9 @@ const Colorbar = ({ window.addEventListener('mouseup', updater) } - const increment = (e) => { + const increment = (e: KeyboardEvent) => { + if (!setClim) return + if (climRef[0].current === document.activeElement) { e.preventDefault() setClim((prev) => [Math.min(prev[0] + scale, prev[1]), prev[1]]) @@ -199,7 +241,9 @@ const Colorbar = ({ } } - const decrement = (e) => { + const decrement = (e: KeyboardEvent) => { + if (!setClim) return + if (climRef[0].current === document.activeElement) { e.preventDefault() setClim((prev) => [Math.min(prev[0] - scale, prev[1]), prev[1]]) @@ -213,7 +257,7 @@ const Colorbar = ({ } useEffect(() => { - const listener = (e) => { + const listener = (e: KeyboardEvent) => { if ( ['ArrowUp', 'ArrowRight'].includes(e.code) || ['ArrowUp', 'ArrowRight'].includes(e.key) @@ -253,7 +297,7 @@ const Colorbar = ({ mr: horizontal ? ['2px', '1px', '1px', '2px'] : 0, mb: horizontal ? 0 : ['-2px', '-2px', '-2px', '-3px'], borderBottom: setClim - ? ({ colors }) => `solid 1px ${colors.primary}` + ? (t) => `solid 1px ${get(t, 'colors.primary')}` : 'unset', cursor: setClim ? horizontal @@ -263,7 +307,7 @@ const Colorbar = ({ ...sxClim, }} onMouseDown={setClim ? handleMouseDown : () => {}} - onClick={() => climRef[0].current.focus()} + onClick={() => climRef[0].current?.focus()} > {format(clim[0])} @@ -284,7 +328,7 @@ const Colorbar = ({ : ['2px', '1px', '1px', '2px'], mt: horizontal ? 0 : ['-2px', '-3px', '-3px', '-3px'], borderBottom: setClim - ? ({ colors }) => `solid 1px ${colors.primary}` + ? (t) => `solid 1px ${get(t, 'colors.primary')}` : 'unset', cursor: setClim ? horizontal @@ -294,7 +338,7 @@ const Colorbar = ({ ...sxClim, }} onMouseDown={setClim ? handleMouseDown : () => {}} - onClick={() => climRef[1].current.focus()} + onClick={() => climRef[1].current?.focus()} > {format(clim[1])} diff --git a/src/colors.js b/src/colors.js deleted file mode 100644 index 305f1f0..0000000 --- a/src/colors.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react' -import { Box } from 'theme-ui' - -const InlineColor = ({ sx, color, children, ...props }) => { - return ( - - {children} - - ) -} - -const Colors = {} - -Colors.Primary = (props) => { - return -} - -Colors.Secondary = (props) => { - return -} - -Colors.Background = (props) => { - return -} - -Colors.Red = (props) => { - return -} - -Colors.Orange = (props) => { - return -} - -Colors.Yellow = (props) => { - return -} - -Colors.Green = (props) => { - return -} - -Colors.Teal = (props) => { - return -} - -Colors.Blue = (props) => { - return -} - -Colors.Purple = (props) => { - return -} - -Colors.Pink = (props) => { - return -} - -Colors.Grey = (props) => { - return -} - -export default Colors diff --git a/src/colors.tsx b/src/colors.tsx new file mode 100644 index 0000000..f2d322a --- /dev/null +++ b/src/colors.tsx @@ -0,0 +1,69 @@ +import React, { ReactNode } from 'react' +import { Box, BoxProps, ThemeUIStyleObject } from 'theme-ui' + +interface InlineColorProps extends BoxProps { + sx: ThemeUIStyleObject + color: string + children: ReactNode +} +const InlineColor = ({ sx, color, children, ...props }: InlineColorProps) => { + return ( + + {children} + + ) +} + +type ColorProps = Omit + +export const Primary = (props: ColorProps) => { + return +} + +export const Secondary = (props: ColorProps) => { + return +} + +export const Background = (props: ColorProps) => { + return +} + +export const Red = (props: ColorProps) => { + return +} + +export const Orange = (props: ColorProps) => { + return +} + +export const Yellow = (props: ColorProps) => { + return +} + +export const Green = (props: ColorProps) => { + return +} + +export const Teal = (props: ColorProps) => { + return +} + +export const Blue = (props: ColorProps) => { + return +} + +export const Purple = (props: ColorProps) => { + return +} + +export const Pink = (props: ColorProps) => { + return +} + +export const Grey = (props: ColorProps) => { + return +} diff --git a/src/column.js b/src/column.tsx similarity index 64% rename from src/column.js rename to src/column.tsx index b5be553..aa18d48 100644 --- a/src/column.js +++ b/src/column.tsx @@ -1,11 +1,28 @@ import React from 'react' -import { Box } from 'theme-ui' +import { Box, BoxProps, ResponsiveStyleValue } from 'theme-ui' -const Column = ({ start, width, dl, dr, children, sx, ...props }) => { +export interface ColumnProps extends BoxProps { + start?: number | 'auto' | (number | 'auto')[] + width?: number | 'auto' | (number | 'auto')[] + dl?: 0.5 | 1 + dr?: 0.5 | 1 +} + +type GridValue = number | 'auto' + +const Column = ({ + start, + width, + dl, + dr, + children, + sx, + ...props +}: ColumnProps) => { start = start || 'auto' width = width || 'auto' - const makeArray = (input) => { + const makeArray = (input: GridValue[]): GridValue[] => { if (input && !Array.isArray(input)) { input = [input] } @@ -23,15 +40,16 @@ const Column = ({ start, width, dl, dr, children, sx, ...props }) => { return input } - start = makeArray(start) - width = makeArray(width) + start = makeArray(start as GridValue[]) + width = makeArray(width as GridValue[]) const end = start.map((d, i) => { if (d == 'auto') return 'auto' - return d + width[i] + return (d as number) + (width[i] as number) }) - let ml, mr + let ml: ResponsiveStyleValue | undefined, + mr: ResponsiveStyleValue | undefined if (dl) { if (![0.5, 1].includes(dl)) { diff --git a/src/custom-404.js b/src/custom-404.tsx similarity index 99% rename from src/custom-404.js rename to src/custom-404.tsx index 312c135..b19ab15 100644 --- a/src/custom-404.js +++ b/src/custom-404.tsx @@ -1,5 +1,6 @@ import React from 'react' import { Box } from 'theme-ui' +// @ts-ignore import { PoopSad } from '@carbonplan/emoji' import Layout from './layout' import Row from './row' diff --git a/src/dimmer.js b/src/dimmer.tsx similarity index 82% rename from src/dimmer.js rename to src/dimmer.tsx index 77d00a8..4d1cad9 100644 --- a/src/dimmer.js +++ b/src/dimmer.tsx @@ -1,9 +1,12 @@ import React from 'react' -import { Box, IconButton, useColorMode } from 'theme-ui' +import { IconButton, useColorMode, IconButtonProps } from 'theme-ui' import { useCallback } from 'react' +// @ts-ignore import { Sun } from '@carbonplan/icons' -const Dimmer = ({ sx, ...props }) => { +export type DimmerProps = IconButtonProps + +const Dimmer = ({ sx = {}, ...props }: DimmerProps) => { const [colorMode, setColorMode] = useColorMode() const toggle = useCallback(() => { diff --git a/src/expander.js b/src/expander.tsx similarity index 75% rename from src/expander.js rename to src/expander.tsx index 717a6ec..2584917 100644 --- a/src/expander.js +++ b/src/expander.tsx @@ -1,7 +1,13 @@ import React from 'react' -import { Box, IconButton } from 'theme-ui' +import { IconButton, IconButtonProps } from 'theme-ui' -const Expander = ({ value, id, onClick, sx }) => { +export interface ExpanderProps { + value: IconButtonProps['aria-checked'] + id: IconButtonProps['id'] + onClick: IconButtonProps['onClick'] + sx: IconButtonProps['sx'] +} +const Expander = ({ value, id, onClick, sx }: ExpanderProps) => { return ( { +export interface FadeInProps extends BoxProps { + duration?: number + delay?: number +} + +const FadeIn = ({ + duration = 300, + delay = 0, + children, + ...delegated +}: FadeInProps) => { return ( { +const FigureCaption = ({ + as = 'figcaption', + number, + children, +}: CaptionProps) => { return ( {children} diff --git a/src/figure.js b/src/figure.tsx similarity index 75% rename from src/figure.js rename to src/figure.tsx index 874d6df..0160b9b 100644 --- a/src/figure.js +++ b/src/figure.tsx @@ -1,9 +1,9 @@ import React from 'react' -import { Box } from 'theme-ui' +import { Box, BoxProps } from 'theme-ui' import Group from './group' -const Figure = ({ as = 'figure', children, sx }) => { +const Figure = ({ as = 'figure', children, sx }: BoxProps) => { return ( extends BoxProps { + values: { [Property in keyof T]: boolean } + setValues: (updated: { [Property in keyof T]: boolean }) => void + order?: (keyof T)[] + colors?: { [Property in keyof T]: string } + labels?: { [Property in keyof T]: ReactNode } + label?: ReactNode + showAll?: boolean + multiSelect?: boolean +} const sx = { label: { fontFamily: 'mono', letterSpacing: 'mono', fontSize: [1, 1, 1, 2], color: 'secondary', - userSelect: 'none', - textTransform: 'uppercase', + userSelect: 'none' as const, + textTransform: 'uppercase' as const, }, } -const duplicateOptions = (options, defaultValue, overrides = {}) => { - return Object.keys(options).reduce( - (o, key) => Object.assign(o, { [key]: overrides[key] || defaultValue }), - {} - ) +const duplicateOptions = ( + options: { [Property in keyof T]: boolean }, + defaultValue: boolean, + overrides: Partial<{ [Property in keyof T]: boolean }> = {} +): { [Property in keyof T]: boolean } => { + let result = { ...options } + Object.keys(options).forEach((key) => { + result[key as keyof T] = !!overrides[key as keyof T] || defaultValue + }) + + return result } -const isAll = (option) => { +const isAll = (option: FilterProps['values']) => { return ( - Object.keys(option).filter((d) => option[d]).length == + Object.keys(option).filter((d) => option[d as keyof T]).length == Object.keys(option).length ) } -const updateValues = ({ values, multiSelect, setValues, value }) => { +const updateValues = ({ + values, + multiSelect, + setValues, + value, +}: { + values: FilterProps['values'] + multiSelect: FilterProps['multiSelect'] + setValues: FilterProps['setValues'] + value: keyof T +}) => { const isAllAlreadySelected = isAll(values) const isSelectingAll = value === 'all' @@ -52,7 +78,9 @@ const updateValues = ({ values, multiSelect, setValues, value }) => { // do nothing } else { // select only value - updatedToggle = duplicateOptions(values, false, { [value]: true }) + updatedToggle = duplicateOptions(values, false, { + [value]: true, + } as Partial<{ [Property in keyof T]: boolean }>) } } @@ -61,7 +89,7 @@ const updateValues = ({ values, multiSelect, setValues, value }) => { } } -const Filter = ({ +const Filter = ({ values, setValues, label, @@ -71,12 +99,12 @@ const Filter = ({ showAll = false, multiSelect = false, ...props -}) => { +}: FilterProps) => { const keys = useMemo(() => { if (order) { return order } else { - return Object.keys(values) + return Object.keys(values) as (keyof T)[] } }, [order, ...Object.keys(values).sort()]) @@ -86,12 +114,13 @@ const Filter = ({ {showAll && ( updateValues({ values: values, multiSelect, setValues: setValues, - value: 'all', + value: 'all' as keyof T, }) } value={isAll(values)} @@ -102,6 +131,7 @@ const Filter = ({ )} {keys.map((d, i) => ( updateValues({ values: values, @@ -127,7 +157,7 @@ const Filter = ({ mb: [1], }} > - {labels ? labels[d] : d} + {labels ? labels[d] : String(d)} ))} diff --git a/src/group.js b/src/group.js deleted file mode 100644 index 391002d..0000000 --- a/src/group.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react' -import { Box } from 'theme-ui' - -const sizes = { - xs: [1], - sm: [3], - md: [5], - lg: [7], - xl: [9], -} -const Group = ({ children, direction = 'vertical', spacing = 'md', sx }) => { - let marginValue - if (typeof spacing === 'string' && sizes.hasOwnProperty(spacing)) { - marginValue = sizes[spacing] - } else { - marginValue = spacing - } - - if (!['horizontal', 'vertical'].includes(direction)) { - throw new Error( - 'Invalid direction value. Must be either horizontal or vertical' - ) - } - - const marginProperty = direction === 'vertical' ? 'mb' : 'mr' - const additionalStyles = - direction === 'horizontal' ? { display: 'inline-block' } : {} - return ( - - {React.Children.map(children, (child, i) => { - return ( - - {child} - - ) - })} - - ) -} - -export default Group diff --git a/src/group.tsx b/src/group.tsx new file mode 100644 index 0000000..cc2ccb1 --- /dev/null +++ b/src/group.tsx @@ -0,0 +1,64 @@ +import React from 'react' +import { ResponsiveStyleValue } from 'theme-ui' +import { Box, BoxProps } from 'theme-ui' + +const sizes = { + xs: [1], + sm: [3], + md: [5], + lg: [7], + xl: [9], +} + +type SizeKey = keyof typeof sizes +type Direction = 'horizontal' | 'vertical' +type SpacingValue = SizeKey | ResponsiveStyleValue + +export interface GroupProps extends BoxProps { + direction?: Direction + spacing?: SpacingValue +} + +const Group = ({ + children, + direction = 'vertical', + spacing = 'md', + sx, +}: GroupProps) => { + let marginValue: ResponsiveStyleValue + + if (typeof spacing === 'string' && spacing in sizes) { + marginValue = sizes[spacing as SizeKey] + } else { + marginValue = spacing as ResponsiveStyleValue + } + + if (!['horizontal', 'vertical'].includes(direction)) { + throw new Error( + 'Invalid direction value. Must be either horizontal or vertical' + ) + } + + const marginProperty = direction === 'vertical' ? 'mb' : 'mr' + const additionalStyles = + direction === 'horizontal' ? { display: 'inline-block' } : {} + return ( + + {React.Children.map(children, (child, i) => { + const childrenCount = React.Children.count(children) + return ( + + {child} + + ) + })} + + ) +} + +export default Group diff --git a/src/header.js b/src/header.tsx similarity index 79% rename from src/header.js rename to src/header.tsx index a7ba0f5..3c35625 100644 --- a/src/header.js +++ b/src/header.tsx @@ -1,6 +1,7 @@ -import React, { useState } from 'react' +import React, { useState, ReactNode, MouseEvent } from 'react' import { default as NextLink } from 'next/link' -import { Box, Flex, Container, Link } from 'theme-ui' +import { Box, Flex, Container, Link, ThemeUIStyleObject } from 'theme-ui' +// @ts-ignore import { Arrow } from '@carbonplan/icons' import Logo from './logo' import Row from './row' @@ -8,7 +9,11 @@ import Column from './column' import Menu from './menu' const sx = { - link: (current, label, first = false) => { + link: ( + current: string | undefined, + label: string, + first = false + ): ThemeUIStyleObject => { return { width: 'auto', color: current === label ? 'secondary' : 'text', @@ -31,20 +36,25 @@ const sx = { }, }, '&:hover': { - color: current == label ? 'secondary' : 'text', + color: current === label ? 'secondary' : 'text', }, } }, } -const links = [ +export interface LinkItem { + url: string + display: string +} + +const links: LinkItem[] = [ { url: 'about', display: 'About' }, { url: 'research', display: 'Research' }, { url: 'blog', display: 'Blog' }, { url: 'press', display: 'Press' }, ] -const HoverArrow = () => { +const HoverArrow = (): JSX.Element => { return ( { ) } -const Nav = ({ link, mode, nav, first, setExpanded }) => { +export interface NavProps { + link: LinkItem + mode?: 'homepage' | 'local' | 'remote' + nav?: string + first: boolean + setExpanded: (expanded: boolean) => void +} + +const Nav = ({ + link, + mode, + nav, + first, + setExpanded, +}: NavProps): JSX.Element => { const { url, display } = link const href = mode === 'remote' ? 'https://carbonplan.org/' + url : '/' + url @@ -92,7 +116,19 @@ const Nav = ({ link, mode, nav, first, setExpanded }) => { } } -const NavGroup = ({ links, nav, mode, setExpanded }) => { +export interface NavGroupProps { + links: LinkItem[] + nav?: string + mode?: 'homepage' | 'local' | 'remote' + setExpanded: (expanded: boolean) => void +} + +const NavGroup = ({ + links, + nav, + mode, + setExpanded, +}: NavGroupProps): JSX.Element[] => { return links.map((d, i) => { return (