diff --git a/.github/card.png b/.github/card.png new file mode 100644 index 0000000..d7930ab Binary files /dev/null and b/.github/card.png differ diff --git a/.github/demo.gif b/.github/demo.gif new file mode 100644 index 0000000..38f647c Binary files /dev/null and b/.github/demo.gif differ diff --git a/.gitignore b/.gitignore index 0ca39c0..a355487 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist .DS_Store +.next \ No newline at end of file diff --git a/README.md b/README.md index 657027a..e78986e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,35 @@ # React Wrap Balancer +One simple component to make your titles more elegant: + +![](.github/demo.gif) + +Notice that in “Balanced Title Wrapping” above, every line of wrapped text has almost the same width, so it will likely not to have one single word in a line. + +## Usage + +Install it to your React project: + +```bash +npm i react-wrap-balancer +``` + +And wrap any text with it: + +```jsx +import { Balancer } from 'react-wrap-balancer' + +// ... + +function Title() { + return ( +

+ My Awesome Title +

+ ) +} +``` + ## Requirements This library uses browser and React 18 APIs such as: diff --git a/package.json b/package.json index 047b0db..dfa3899 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-wrap-balancer", - "version": "0.1.0", + "version": "0.2.3", "description": "Better text wrapping.", "main": "dist/index.js", "module": "dist/index.mjs", @@ -9,7 +9,7 @@ "dist" ], "scripts": { - "build": "tsup src/index.tsx --format cjs,esm --dts-resolve --external react" + "build": "tsup src/index.tsx" }, "keywords": [ "react", @@ -20,6 +20,7 @@ "author": "Shu Ding ", "license": "MIT", "devDependencies": { + "@types/node": "^18.11.13", "react": "^18.2.0", "tsup": "^6.4.0", "typescript": "^4.8.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 065e53b..398a5d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,14 +1,36 @@ -lockfileVersion: 5.4 - -specifiers: - react: ^18.2.0 - tsup: ^6.4.0 - typescript: ^4.8.4 +lockfileVersion: 5.3 + +importers: + + .: + specifiers: + '@types/node': ^18.11.13 + react: ^18.2.0 + tsup: ^6.4.0 + typescript: ^4.8.4 + devDependencies: + '@types/node': 18.11.13 + react: 18.2.0 + tsup: 6.4.0_typescript@4.8.4 + typescript: 4.8.4 -devDependencies: - react: 18.2.0 - tsup: 6.4.0_typescript@4.8.4 - typescript: 4.8.4 + website: + specifiers: + '@next/font': ^13.0.3 + '@react-spring/web': ^9.5.5 + copy-to-clipboard: ^3.3.3 + next: 13.0.8-canary.2 + react: ^18.2.0 + react-dom: ^18.2.0 + react-wrap-balancer: workspace:* + dependencies: + '@next/font': 13.0.3 + '@react-spring/web': 9.5.5_react-dom@18.2.0+react@18.2.0 + copy-to-clipboard: 3.3.3 + next: 13.0.8-canary.2_react-dom@18.2.0+react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-wrap-balancer: link:.. packages: @@ -30,6 +52,118 @@ packages: dev: true optional: true + /@next/env/13.0.8-canary.2: + resolution: {integrity: sha512-AL/qgwt7uwsauCWZdX3HsEanNCmv/kCY+0p3Av9q++6uSDRauZ6/nofn0NvxRqi2zjmQXZcVTiZbJJLA7aucnQ==} + dev: false + + /@next/font/13.0.3: + resolution: {integrity: sha512-J3bZooNyhzhRshEJZlc4Kagfx6f8LQkPdCsP0nZm63dAUaoeMaQRidFFhbL68WZ3S5XleVfpGYypZuBSyJqYtw==} + dev: false + + /@next/swc-android-arm-eabi/13.0.8-canary.2: + resolution: {integrity: sha512-L0HoZ8XTD+zFW1E7UwQY0iaugy/t49QVXwWwUZYJ601+5puhuvgvRyAz6qY8ByNd2dGXCsCEqEsT77Ns6Cq5hQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + dev: false + optional: true + + /@next/swc-android-arm64/13.0.8-canary.2: + resolution: {integrity: sha512-98vkDyWvARLWLbdjq34NMm6lqhqoy21JZSZdklxoQ7ZffmQmTcPgIPhvYehq+05hf+5JBkhMGkd496k0bMsBjw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + dev: false + optional: true + + /@next/swc-darwin-arm64/13.0.8-canary.2: + resolution: {integrity: sha512-VvzrT2DOgst5e0YpG2VfMvOqYf3eUAuHSdBmktxA8l27LKHjNzSZ3t017MmibQtiErkf7vQHvP93riZWE/7I/Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + dev: false + optional: true + + /@next/swc-darwin-x64/13.0.8-canary.2: + resolution: {integrity: sha512-yRN+26jOrXFgVeY3kX5SXnY4Dg7t3JQCFBFiKVsRaerF0aDyrfsF751ujldRGJG1wLHhN5FR1ulnWZ1d3drE7w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + dev: false + optional: true + + /@next/swc-freebsd-x64/13.0.8-canary.2: + resolution: {integrity: sha512-HxFxdO+H7v1++p/b4kv8OKcrGO9lidBIrUnCgTCuxrSufOsfCJAi9bo8qE+t4zmYj9i16rw/OC+YC0Dn4L0Uvw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + dev: false + optional: true + + /@next/swc-linux-arm-gnueabihf/13.0.8-canary.2: + resolution: {integrity: sha512-l9aY0mkCxzYJud2iZ20ZG6urSLUgcSpUk1GJ15jdlctzT7XEVU5DUElxPhjZMICO0s337Nt2kenlss71Aiw8Zg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + dev: false + optional: true + + /@next/swc-linux-arm64-gnu/13.0.8-canary.2: + resolution: {integrity: sha512-v+1+woCfgMW6c7gw6U/oSl4zkdbSL5I7hBg7Yt/2jyXa9Xrk+qxsU4GPVA5RStX9S6X1aDK/cHX/IncKUq7XTQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + dev: false + optional: true + + /@next/swc-linux-arm64-musl/13.0.8-canary.2: + resolution: {integrity: sha512-+CmK4LVZ+RYgLHeN7AQJsES+YLMhr29vWpmyPrvnIAybq839L2iwcP0TzjiYNdbGVHxUYkbJ7Q+2F7J3Tdihpg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + dev: false + optional: true + + /@next/swc-linux-x64-gnu/13.0.8-canary.2: + resolution: {integrity: sha512-qX+HkP3X4YHElZvF9hdwVyR5tOjDhbntH/kxh035FwzuDSQZxdLs1pzK3+czHOTBYpdvY4m8TFAK6QCYngi5jw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + dev: false + optional: true + + /@next/swc-linux-x64-musl/13.0.8-canary.2: + resolution: {integrity: sha512-fWNi2GcScjKQILD1Z2tjt0vKrsq755ov7gB5sx9AsQksAHzBQw4aJS+siYg1O9vnBnwzlThyeiAnqCFE4ZGCAA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + dev: false + optional: true + + /@next/swc-win32-arm64-msvc/13.0.8-canary.2: + resolution: {integrity: sha512-A9NIhEn7gnnr3sOCQOcWOF8AkwA2MEUivJnXa8zYCT4iA6yBp9g8zxF02ikUtNG1V/FnT14fCgPZBh0t4fzGMQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + dev: false + optional: true + + /@next/swc-win32-ia32-msvc/13.0.8-canary.2: + resolution: {integrity: sha512-IfrGEhTKI9wBbf3FxzklVDEziHSN3t6eDY1UVlst+JRMcaJkQb5DsNrFRXXXVGgbl7WTvlpAkyuuHmCOVZMf5g==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + dev: false + optional: true + + /@next/swc-win32-x64-msvc/13.0.8-canary.2: + resolution: {integrity: sha512-ymZLJDSYDSquhpUl5lbJ4ZXsoPH2okx/rI4rpHaM+MvP45C8HXllgs10N17kMxwTGPVNI2Lsca8DC34CcbuMsQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + dev: false + optional: true + /@nodelib/fs.scandir/2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -51,6 +185,70 @@ packages: fastq: 1.13.0 dev: true + /@react-spring/animated/9.5.5_react@18.2.0: + resolution: {integrity: sha512-glzViz7syQ3CE6BQOwAyr75cgh0qsihm5lkaf24I0DfU63cMm/3+br299UEYkuaHNmfDfM414uktiPlZCNJbQA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@react-spring/shared': 9.5.5_react@18.2.0 + '@react-spring/types': 9.5.5 + react: 18.2.0 + dev: false + + /@react-spring/core/9.5.5_react@18.2.0: + resolution: {integrity: sha512-shaJYb3iX18Au6gkk8ahaF0qx0LpS0Yd+ajb4asBaAQf6WPGuEdJsbsNSgei1/O13JyEATsJl20lkjeslJPMYA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@react-spring/animated': 9.5.5_react@18.2.0 + '@react-spring/rafz': 9.5.5 + '@react-spring/shared': 9.5.5_react@18.2.0 + '@react-spring/types': 9.5.5 + react: 18.2.0 + dev: false + + /@react-spring/rafz/9.5.5: + resolution: {integrity: sha512-F/CLwB0d10jL6My5vgzRQxCNY2RNyDJZedRBK7FsngdCmzoq3V4OqqNc/9voJb9qRC2wd55oGXUeXv2eIaFmsw==} + dev: false + + /@react-spring/shared/9.5.5_react@18.2.0: + resolution: {integrity: sha512-YwW70Pa/YXPOwTutExHZmMQSHcNC90kJOnNR4G4mCDNV99hE98jWkIPDOsgqbYx3amIglcFPiYKMaQuGdr8dyQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@react-spring/rafz': 9.5.5 + '@react-spring/types': 9.5.5 + react: 18.2.0 + dev: false + + /@react-spring/types/9.5.5: + resolution: {integrity: sha512-7I/qY8H7Enwasxr4jU6WmtNK+RZ4Z/XvSlDvjXFVe7ii1x0MoSlkw6pD7xuac8qrHQRm9BTcbZNyeeKApYsvCg==} + dev: false + + /@react-spring/web/9.5.5_react-dom@18.2.0+react@18.2.0: + resolution: {integrity: sha512-+moT8aDX/ho/XAhU+HRY9m0LVV9y9CK6NjSRaI+30Re150pB3iEip6QfnF4qnhSCQ5drpMF0XRXHgOTY/xbtFw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@react-spring/animated': 9.5.5_react@18.2.0 + '@react-spring/core': 9.5.5_react@18.2.0 + '@react-spring/shared': 9.5.5_react@18.2.0 + '@react-spring/types': 9.5.5 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@swc/helpers/0.4.14: + resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} + dependencies: + tslib: 2.4.1 + dev: false + + /@types/node/18.11.13: + resolution: {integrity: sha512-IASpMGVcWpUsx5xBOrxMj7Bl8lqfuTY7FKAnPmu5cHkfQVWF8GulWS1jbRqA934qZL35xh5xN/+Xe/i26Bod4w==} + dev: true + /any-promise/1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} dev: true @@ -106,6 +304,10 @@ packages: engines: {node: '>=8'} dev: true + /caniuse-lite/1.0.30001431: + resolution: {integrity: sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==} + dev: false + /chokidar/3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -121,6 +323,10 @@ packages: fsevents: 2.3.2 dev: true + /client-only/0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false + /commander/4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -130,6 +336,12 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /copy-to-clipboard/3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + dependencies: + toggle-selection: 1.0.6 + dev: false + /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -515,7 +727,6 @@ packages: /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true /lilconfig/2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} @@ -540,7 +751,6 @@ packages: hasBin: true dependencies: js-tokens: 4.0.0 - dev: true /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -582,6 +792,56 @@ packages: thenify-all: 1.6.0 dev: true + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + + /next/13.0.8-canary.2_react-dom@18.2.0+react@18.2.0: + resolution: {integrity: sha512-3HMCYnM2kqNz7FgLGnpbkWBvm1o9stNoYIjSfDVtlIk1slDFkJYuekF8L+R0MTl4siRdA7VoEjb/jRyXWjFtiA==} + engines: {node: '>=14.6.0'} + hasBin: true + peerDependencies: + fibers: '>= 3.1.0' + node-sass: ^6.0.0 || ^7.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + dependencies: + '@next/env': 13.0.8-canary.2 + '@swc/helpers': 0.4.14 + caniuse-lite: 1.0.30001431 + postcss: 8.4.14 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + styled-jsx: 5.1.1_react@18.2.0 + optionalDependencies: + '@next/swc-android-arm-eabi': 13.0.8-canary.2 + '@next/swc-android-arm64': 13.0.8-canary.2 + '@next/swc-darwin-arm64': 13.0.8-canary.2 + '@next/swc-darwin-x64': 13.0.8-canary.2 + '@next/swc-freebsd-x64': 13.0.8-canary.2 + '@next/swc-linux-arm-gnueabihf': 13.0.8-canary.2 + '@next/swc-linux-arm64-gnu': 13.0.8-canary.2 + '@next/swc-linux-arm64-musl': 13.0.8-canary.2 + '@next/swc-linux-x64-gnu': 13.0.8-canary.2 + '@next/swc-linux-x64-musl': 13.0.8-canary.2 + '@next/swc-win32-arm64-msvc': 13.0.8-canary.2 + '@next/swc-win32-ia32-msvc': 13.0.8-canary.2 + '@next/swc-win32-x64-msvc': 13.0.8-canary.2 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -627,6 +887,10 @@ packages: engines: {node: '>=8'} dev: true + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -653,6 +917,15 @@ packages: yaml: 1.10.2 dev: true + /postcss/8.4.14: + resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} @@ -662,12 +935,21 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /react-dom/18.2.0_react@18.2.0: + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + /react/18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 - dev: true /readdirp/3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} @@ -700,6 +982,12 @@ packages: queue-microtask: 1.2.3 dev: true + /scheduler/0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: false + /shebang-command/2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -721,6 +1009,11 @@ packages: engines: {node: '>=8'} dev: true + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: false + /source-map/0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -733,6 +1026,23 @@ packages: engines: {node: '>=6'} dev: true + /styled-jsx/5.1.1_react@18.2.0: + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + client-only: 0.0.1 + react: 18.2.0 + dev: false + /sucrase/3.28.0: resolution: {integrity: sha512-TK9600YInjuiIhVM3729rH4ZKPOsGeyXUwY+Ugu9eilNbdTFyHr6XcAGYbRVZPDgWj6tgI7bx95aaJjHnbffag==} engines: {node: '>=8'} @@ -766,6 +1076,10 @@ packages: is-number: 7.0.0 dev: true + /toggle-selection/1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + dev: false + /tr46/1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} dependencies: @@ -781,6 +1095,10 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /tslib/2.4.1: + resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + dev: false + /tsup/6.4.0_typescript@4.8.4: resolution: {integrity: sha512-4OlbqIK/SF+cJp0mMqPM2pKULvgj/1S2Gm3I1aFoFGIryUOyIqPZBoqKkqVQT6uFtWJ5AHftIv0riXKfHox1zQ==} engines: {node: '>=14'} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..a703c08 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'website' diff --git a/src/index.tsx b/src/index.tsx index 636e4d0..817ee97 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,37 +1,41 @@ +'use client' + import React from 'react' -const DO_BALANCE = Symbol.for('__wrap_balancer__') +const SYMBOL_KEY = '__wrap_balancer' const IS_SERVER = typeof window === 'undefined' const useIsomorphicLayoutEffect = IS_SERVER ? React.useEffect : React.useLayoutEffect -// Register the balancer globally on client. -if (!IS_SERVER) { - window[DO_BALANCE] = (id: string) => { - const el = window.document.querySelector( - `[data-balanced="${id}"]` - ) as HTMLElement - if (!el) return +const relayout = ( + id: string | number, + ratio: number, + wrapper?: HTMLElement +) => { + wrapper = + wrapper || (document.querySelector(`[data-br="${id}"]`) as HTMLElement) + const container = wrapper.parentElement as HTMLElement + + const update = (width) => (wrapper.style.maxWidth = width + 'px') - const wrapper = el.childNodes[0] as HTMLElement - const update = (width) => (wrapper.style.maxWidth = width + 'px') + // Reset wrapper width + wrapper.style.maxWidth = '' - // Reset wrapper width - wrapper.style.maxWidth = '' + // Get the intial container size + const w = container.clientWidth + const h = container.clientHeight - // Get the intial container size - const w = el.offsetWidth - const h = el.offsetHeight + // Synchronously do binary search and calculate the layout + let l = w / 2 + let r = w + let m - // Synchrnously do binary search and calculate the layout - let l = 0 - let r = w - let m + if (w) { while (l + 1 < r) { m = ~~((l + r) / 2) update(m) - if (el.offsetHeight === h) { + if (container.clientHeight == h) { r = m } else { l = m @@ -39,49 +43,111 @@ if (!IS_SERVER) { } // Update the wrapper width - update(r) + update(r * ratio + w * (1 - ratio)) } } +const MINIFIED_RELAYOUT_STR = relayout.toString() + type Props = { - as: string + /** + * The HTML tag to use for the wrapper element. + * @default 'span' + */ + as?: string + /** + * The balance ratio of the wrapper width (0 <= ratio <= 1). + * 0 means the wrapper width is the same as the container width (no balance, browser default). + * 1 means the wrapper width is the minimum (full balance, most compact). + * @default 1 + */ + ratio?: number + children?: React.ReactNode } -export const Balancer: React.FC = ({ as = 'p', children, ...props }) => { +// As Next.js adds `display: none` to `body` for development, we need to trigger +// a re-balance right after the style is removed, synchronously. +if (!IS_SERVER && process.env.NODE_ENV !== 'production') { + const next_dev_style = document.querySelector('[data-next-hide-fouc]') + if (next_dev_style) { + const callback = (mutationList) => { + for (const mutation of mutationList) { + for (const node of mutation.removedNodes) { + if (node === next_dev_style) { + observer.disconnect() + const el = document.querySelectorAll('[data-br]') + // @ts-ignore + for (const e of el) { + self[SYMBOL_KEY](0, +e.dataset.brr, e) + } + } + } + } + } + const observer = new MutationObserver(callback) + observer.observe(document.head, { childList: true }) + } +} + +const Balancer: React.FC = ({ + as = 'span', + ratio = 1, + children, + ...props +}) => { const As = as - const style = Object.assign({}, props.style) const id = React.useId() - const containerRef = React.useRef() + const wrapperRef = React.useRef() // Re-balance on content change and on mount/hydration useIsomorphicLayoutEffect(() => { - window[Symbol.for('react-balanced')](id) - }, [children]) + if (!wrapperRef.current) { + return + } + + // Re-assign the function here as the component can be dynamically rendered, and script tag won't work in that case. + ;(self[SYMBOL_KEY] = relayout)(0, ratio, wrapperRef.current) + }, [children, ratio]) // Re-balance on resize - React.useEffect(() => { - if (containerRef.current) { - const resizeObserver = new ResizeObserver(() => { - window[Symbol.for('react-balanced')](id) - }) - resizeObserver.observe(containerRef.current) - return () => { - resizeObserver.unobserve(containerRef.current) - } - } + useIsomorphicLayoutEffect(() => { + if (!wrapperRef.current) return + + const container = wrapperRef.current.parentElement as HTMLElement + if (!container) return + + const resizeObserver = new ResizeObserver(() => { + if (!wrapperRef.current) return + self[SYMBOL_KEY](0, ratio, wrapperRef.current) + }) + resizeObserver.observe(container) + return () => resizeObserver.unobserve(container) }, []) return ( <> - - {children} + + {children} ) } + +export default Balancer diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..63f6d74 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + minify: true, + target: 'es2018', + external: ['react'], + sourcemap: true, + dts: true, + format: ['esm', 'cjs'], + esbuildOptions(options) { + options.banner = { + js: '"use client"', + } + }, +}) diff --git a/website/app/head.js b/website/app/head.js new file mode 100644 index 0000000..148872c --- /dev/null +++ b/website/app/head.js @@ -0,0 +1,208 @@ +export default function Head() { + return ( + <> + React Wrap Balancer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/website/app/layout.js b/website/app/layout.js new file mode 100644 index 0000000..8b9912d --- /dev/null +++ b/website/app/layout.js @@ -0,0 +1,24 @@ +import { Inter, Alice } from '@next/font/google' +import './style.css' + +const inter = Inter({ + weight: 'variable', + subsets: ['latin'], + display: 'block', +}) + +const alice = Alice({ + weight: '400', + subsets: ['latin'], + variable: '--font-alice', + display: 'block', +}) + +export default function RootLayout({ children }) { + return ( + + + {children} + + ) +} diff --git a/website/app/page.js b/website/app/page.js new file mode 100644 index 0000000..a6e1819 --- /dev/null +++ b/website/app/page.js @@ -0,0 +1,542 @@ +'use client' + +import copy from 'copy-to-clipboard' +import Balancer from 'react-wrap-balancer' +import { useSpring, animated } from '@react-spring/web' +import { useState } from 'react' + +const content = ( + <> +
+
+
+ +) + +function Comparison({ a, b, align = 'left' }) { + const [styles, api] = useSpring(() => ({ + width: 0.55, + })) + return ( +
+
+ { + api.start({ width: +e.target.value / 100 }) + }} + /> +
+ {typeof a === 'function' ? ( +
+
+ Default + {a( + styles.width.to( + (v) => `calc(${v} * var(--w1) + ${150 * (1 - v) - 31 * v}px)` + ) + )} +
+
+ With Balancer + {b( + styles.width.to( + (v) => `calc(${v} * var(--w1) + ${150 * (1 - v) - 31 * v}px)` + ) + )} +
+
+ ) : ( + `calc(${v * 100}% + ${1 - v} * var(--w0))` + ), + textAlign: align, + }} + className='demo' + > +
+ Default + {a} +
+
+ With Balancer + {b} +
+
+ )} +
+ ) +} + +function Ratio() { + const [ratio, setRatio] = useState(0.65) + const [currentRatio, setCurrentRatio] = useState(0.65) + + useSpring({ + from: { r: 0.65 }, + to: { r: ratio }, + onChange(prop) { + setCurrentRatio(prop.value.r) + }, + }) + + return ( +
+
+ { + setRatio(+e.target.value / 100) + }} + /> +
+
+
+
+

+ + The quick brown fox jumps over the lazy dog + +

+

+ + The quick brown fox jumps over the lazy dog + +

+ {``} +
+
+
+
+ ) +} + +export default function () { + const [copying, setCopying] = useState(0) + + return ( +
+ +

+ + Simple React Component That Makes Titles More Readable + +

+ +

React: A JavaScript library for building user interfaces

+ {content} +
+ } + b={ +
+

+ + React: A JavaScript library for building user interfaces + +

+ {content} +
+ } + /> +

+ + React Wrap Balancer avoids single hanging word on the last line + +

+

+ Getting Started +

+

+ + { + copy('npm install react-wrap-balancer') + setCopying((c) => c + 1) + setTimeout(() => { + setCopying((c) => c - 1) + }, 2000) + }} + > + npm install react-wrap-balancer + + {copying > 0 ? ( + + + + ) : ( + + + + )} + + +

+

+ + + {`import Balancer from 'react-wrap-balancer'\n\n// ...\n\n

\n My Title\n

`} + +

+
+ + +
+

+ + This library requires React ≥ 18.0.0, and IE 11 is not supported. +

+

+ + View project on + + + + + GitHub + + +

+

+ Custom Balance Ratio +

+ +

+ + Adjust the balance ratio to a custom value between{' '} + 0 (loose) and{' '} + 1 (compact, the default) + +

+

+ How Does It Work? +

+

+ React Wrap Balancer reduces the width of the content wrapper as much as + it could, before causing an extra line break. When the minimum width is + reached, each line should approximately have the same width, hence it’ll + look more balanced and compact. +

+

+ Check out the{' '} + + GitHub Repository + {' '} + to learn more. +

+

+ Use Cases +

+ ( + <> +
+
+ +
+ This deployment is currently in progress. Read more. +
+
+ + + +
+
+ + + +
+
+ + )} + b={(width) => ( + <> +
+
+ +
+ + This deployment is currently in progress. Read more + . + +
+
+ + + +
+
+ + + +
+
+ + )} + /> +

+ Useful in tooltips and other UI components +

+ +

+ 第六個沉思:論物質性東西的存在;論人的靈魂和肉體之間的實在區別 +

+ {content} + + } + b={ + <> +

+ + 第六個沉思:論物質性東西的存在;論人的靈魂和肉體之間的實在區別 + +

+ {content} + + } + /> +

+ Left aligned, non-latin content +

+ +
+ + You have wakened not out of sleep, but into a prior dream, and + that dream lies within another, and so on, to infinity, which is + the number of grains of sand. The path that you are to take is + endless, and you will die before you have truly awakened. + +
- Jorge Luis Borges +
+ + } + b={ + <> +
+ + You have wakened not out of sleep, but into a prior dream, and + that dream lies within another, and so on, to infinity, which is + the number of grains of sand. The path that you are to take is + endless, and you will die before you have truly awakened. + +
- Jorge Luis Borges +
+ + } + /> +

+ + Makes multi-line content more compact with fewer visual changes when + resizing + +

+

+ About React Wrap Balancer +

+

+ + This project was inspired by Adobe’s{' '} + + balance-text + {' '} + project, NYT’s{' '} + + text-balancer + {' '} + project, and Daniel Aleksandersen’s{' '} + + Improving the New York Times’ line wrap balancer + + . If you want to learn more, you can also take a look at the{' '} + + + text-wrap: balance + + {' '} + proposal. + +

+

+ + Special thanks to{' '} + + Emil Kowalski + {' '} + for testing and feedback. Created by{' '} + + Shu Ding + {' '} + in 2022, released under the MIT license. + +

+

+ + Deployed on + + + + +

+ + ) +} diff --git a/website/app/style.css b/website/app/style.css new file mode 100644 index 0000000..a3364df --- /dev/null +++ b/website/app/style.css @@ -0,0 +1,475 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + height: 100%; +} + +p, +h1, +h2 { + word-break: break-word; +} + +body { + margin: 0; + -webkit-font-smoothing: subpixel-antialiased; + color: #222; + background-color: #f5f5f5; + font-feature-settings: 'ss04'; + line-height: 1.5; + --w0: 320px; + --w1: min(27.5vw, 480px); +} + +.logo-container { + display: flex; + justify-content: center; + margin-top: 40px; +} + +.logo { + display: inline-block; + border: 5px solid currentColor; + padding: 0 6px 0 4px; + font-weight: 700; + font-size: 30px; + letter-spacing: -0.021em; + line-height: 34px; + margin: 0; + user-select: none; + border-radius: 7px; + align-self: center; + text-decoration: none; + mask-image: linear-gradient( + 45deg, + black 25%, + rgba(0, 0, 0, 0.2) 50%, + black 75% + ); + mask-size: 800%; + mask-position: 0%; +} + +.headline { + text-align: center; + font-family: var(--font-alice); + margin: 80px 0 50px; + font-size: 26px; + letter-spacing: -0.04rem; + line-height: 1.45; +} + +.github-link { + display: inline-flex; + align-items: center; + margin: 0.8em 0; + font-size: 14px; + gap: 0.3em; + user-select: none; +} +.github-link span { + display: inline-flex; + align-items: center; + gap: 0.2em; +} + +.installation { + cursor: copy; + transition: all 0.2s ease; +} + +.installation:hover { + background-color: #ffffffaa; +} +.installation:active .copy { + opacity: 0.2; +} + +.copy { + position: absolute; + right: 8.6px; + width: 2em; + height: 2em; + border-radius: 1em; + background-color: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; + top: 0; + bottom: 0; + margin: auto; + transition: all 0.1s ease; +} + +h3 { + text-align: center; + font-family: var(--font-alice); + margin: 30px 0 80px; + color: #888; + font-weight: 400; +} + +h3 > span::before { + content: ''; + width: 0; + height: 0; + border-left: 0.35em solid transparent; + border-right: 0.35em solid transparent; + border-bottom: 0.6em solid currentColor; + display: inline-block; + margin-right: 0.4em; +} + +h3 + .headline { + margin-top: 10px; +} + +a { + color: inherit; + text-decoration: underline; + text-decoration-thickness: from-font; + text-underline-position: from-font; + transition: opacity 0.2s ease; +} + +a:hover { + opacity: 0.6; +} +a.logo:hover { + transition: mask-position 2s ease, -webkit-mask-position 2s ease; + mask-position: 120%; + opacity: 1; +} + +main { + display: flex; + flex-direction: column; + align-items: center; + padding: 40px 20px; + margin: 0 auto; + max-width: 1800px; +} + +main > * { + width: 100%; + max-width: 600px; +} + +label { + font-weight: 500; + text-transform: uppercase; + font-size: 12px; + display: block; + letter-spacing: 0.04rem; + margin-bottom: 8px; + color: #888; + user-select: none; +} + +code, +.code { + font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, + monospace; +} +code { + position: relative; + background: #fff; + display: block; + border-radius: 6px; + padding: 10px 12px; + font-size: 14px; + line-height: 1.8; + white-space: pre-wrap; +} +.code { + font-size: 0.92em; +} + +p, +.p { + line-height: 1.8; + margin: 0 0 1.5em; +} + +.controller { + position: relative; + align-self: center; + display: flex; + align-items: center; + width: auto; + height: 32px; + padding: 5px 10px; + font-size: 15px; + background-color: #fff; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + user-select: none; +} + +.controller::before { + content: ''; + display: block; + width: 20px; + height: 20px; + position: absolute; + background-color: transparent; + border-bottom: 10px solid white; + border-right: 10px solid white; + border-bottom-right-radius: 20px; + left: -10px; + bottom: -10px; + clip-path: inset(0 10px 10px 0); +} + +.controller::after { + content: ''; + display: block; + width: 20px; + height: 20px; + position: absolute; + background-color: transparent; + border-bottom: 10px solid white; + border-left: 10px solid white; + border-bottom-left-radius: 20px; + right: -10px; + bottom: -10px; + clip-path: inset(0 0 10px 10px); +} + +.controller input { + appearance: none; + background: #dedede; + height: 3px; + border-radius: 3px; +} + +.demo-container { + max-width: unset; + display: flex; + flex-direction: column; + align-items: center; +} + +.demo { + display: flex; + gap: 20px; + padding: 20px; + background-color: #fff; + border-radius: 10px; + box-shadow: 0 20px 25px -5px rgb(0 0 0 / 8%), 0 3px 10px -6px rgb(0 0 0 / 10%); +} + +.demo > div { + flex: 1; +} + +.demo .item { + border: 1px dashed transparent; + border-radius: 2px; + transition: border-color 0.5s ease; +} + +.demo legend { + font-size: 13px; + margin-bottom: 5px; + padding: 0; + text-align: left; + color: #888; + user-select: none; +} + +h2 { + margin: 10px 0 0; + font-size: 22px; +} + +.skeleton { + display: inline-block; + width: var(--w); + height: 24px; + margin-top: 24px; + border-radius: 4px; + background-color: #f5f5f5; +} +.skeleton + .skeleton { + margin-top: 12px; +} +.skeleton:last-child { + margin-bottom: 10px; +} + +.tooltip-container { + position: relative; +} +.TooltipContent { + position: absolute; + left: 50%; + transform: translate3d(-50%, -10px, 0); + margin: auto; + bottom: 100%; + border-radius: 9px; + padding: 10px; + line-height: 1; + background-color: white; + box-shadow: 0 1px 5px #0000000f, 0 5px 20px #00000017; + font-size: 14px; + user-select: none; +} +.TooltipArrow { + fill: white; + position: absolute; + left: 0; + right: 0; + top: 100%; + margin: auto; +} + +.tooltip-trigger { + margin-top: 120px; + margin-bottom: 10px; + padding: 4px; + text-align: center; + font-size: 14px; +} +.tooltip { + line-height: 1.5; + text-align: center; +} + +.controller:active + .demo .item { + border-color: #229dff; +} + +blockquote::before { + content: '“'; + display: block; + font-size: 80px; + text-align: center; + color: #ddd; + margin-bottom: -40px; +} + +blockquote { + line-height: 1.65; + display: flex; + flex-direction: column; + align-items: center; + font-family: var(--font-alice); + margin: 0 0 20px; + font-size: 18px; +} + +ul { + margin: 0; + padding-left: 12px; + list-style-position: inside; +} + +h2.ratio-title { + position: relative; + margin-bottom: 1em; + background: #fff2e0; + border-right: 1px dashed #c59759; + border-left: 1px dashed #c59759; +} +h2.ratio-title span { + background: #cdebff; +} +h2.ratio-title:after, +h2.ratio-ruler span:after { + content: '0'; + position: absolute; + top: 100%; + left: 0; + font-size: 11px; + transform: translateX(-50%); + margin-top: 2px; + margin-left: -1px; + font-weight: 400; +} +h2.ratio-ruler { + margin-top: 0; + position: absolute; + width: 100%; + user-select: none; + pointer-events: none; + z-index: 1; +} +h2.ratio-ruler > span { + position: relative; + border-right: 1px dashed #2d76a7; + border-left: 1px dashed #2d76a7; +} +h2.ratio-ruler span span { + color: transparent; +} +h2.ratio-ruler span:after { + content: '1'; +} + +/* Mobile */ +@media screen and (max-width: 640px) { + .demo { + flex-direction: column; + } + body { + --w0: 200px; + --w1: 80vw; + } + .demo-container { + flex-direction: column-reverse; + } + .controller { + position: sticky; + z-index: 1; + bottom: 25px; + margin-top: 15px; + border: 1px solid #e5e5e5; + border-radius: 20px; + box-shadow: 0 5px 10px rgb(0 0 0 / 4%); + backdrop-filter: blur(10px); + background: rgb(255 255 255 / 83%); + } + .controller::before, + .controller::after { + display: none; + } + blockquote { + font-size: 15px; + } + h2 { + font-size: 20px; + } + h3 { + font-size: 16px; + } + .headline { + font-size: 22px; + } + .TooltipContent { + font-size: 10px; + } + h2.ratio-ruler, + h2.ratio-title { + font-size: 22px; + } +} + +@media screen and (max-width: 450px) { + h2.ratio-ruler, + h2.ratio-title { + font-size: 18px; + } +} + +@media screen and (max-width: 390px) { + h2.ratio-ruler, + h2.ratio-title { + font-size: 16px; + } +} diff --git a/website/next.config.js b/website/next.config.js new file mode 100644 index 0000000..cfa3ac3 --- /dev/null +++ b/website/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + experimental: { + appDir: true, + }, +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..1939c2c --- /dev/null +++ b/website/package.json @@ -0,0 +1,23 @@ +{ + "name": "website", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@next/font": "^13.0.3", + "@react-spring/web": "^9.5.5", + "copy-to-clipboard": "^3.3.3", + "next": "13.0.8-canary.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-wrap-balancer": "workspace:*" + } +} diff --git a/website/public/android-icon-144x144.png b/website/public/android-icon-144x144.png new file mode 100644 index 0000000..ed9e8c9 Binary files /dev/null and b/website/public/android-icon-144x144.png differ diff --git a/website/public/android-icon-192x192.png b/website/public/android-icon-192x192.png new file mode 100644 index 0000000..8b0934b Binary files /dev/null and b/website/public/android-icon-192x192.png differ diff --git a/website/public/android-icon-36x36.png b/website/public/android-icon-36x36.png new file mode 100644 index 0000000..2499ee2 Binary files /dev/null and b/website/public/android-icon-36x36.png differ diff --git a/website/public/android-icon-48x48.png b/website/public/android-icon-48x48.png new file mode 100644 index 0000000..5040b0f Binary files /dev/null and b/website/public/android-icon-48x48.png differ diff --git a/website/public/android-icon-72x72.png b/website/public/android-icon-72x72.png new file mode 100644 index 0000000..202475d Binary files /dev/null and b/website/public/android-icon-72x72.png differ diff --git a/website/public/android-icon-96x96.png b/website/public/android-icon-96x96.png new file mode 100644 index 0000000..aaeb4a2 Binary files /dev/null and b/website/public/android-icon-96x96.png differ diff --git a/website/public/apple-icon-114x114.png b/website/public/apple-icon-114x114.png new file mode 100644 index 0000000..a15c461 Binary files /dev/null and b/website/public/apple-icon-114x114.png differ diff --git a/website/public/apple-icon-120x120.png b/website/public/apple-icon-120x120.png new file mode 100644 index 0000000..b6e36df Binary files /dev/null and b/website/public/apple-icon-120x120.png differ diff --git a/website/public/apple-icon-144x144.png b/website/public/apple-icon-144x144.png new file mode 100644 index 0000000..1e0ff9a Binary files /dev/null and b/website/public/apple-icon-144x144.png differ diff --git a/website/public/apple-icon-152x152.png b/website/public/apple-icon-152x152.png new file mode 100644 index 0000000..01ebe6e Binary files /dev/null and b/website/public/apple-icon-152x152.png differ diff --git a/website/public/apple-icon-180x180.png b/website/public/apple-icon-180x180.png new file mode 100644 index 0000000..c997993 Binary files /dev/null and b/website/public/apple-icon-180x180.png differ diff --git a/website/public/apple-icon-57x57.png b/website/public/apple-icon-57x57.png new file mode 100644 index 0000000..cf21758 Binary files /dev/null and b/website/public/apple-icon-57x57.png differ diff --git a/website/public/apple-icon-60x60.png b/website/public/apple-icon-60x60.png new file mode 100644 index 0000000..e409637 Binary files /dev/null and b/website/public/apple-icon-60x60.png differ diff --git a/website/public/apple-icon-72x72.png b/website/public/apple-icon-72x72.png new file mode 100644 index 0000000..e0e4c43 Binary files /dev/null and b/website/public/apple-icon-72x72.png differ diff --git a/website/public/apple-icon-76x76.png b/website/public/apple-icon-76x76.png new file mode 100644 index 0000000..b946c6b Binary files /dev/null and b/website/public/apple-icon-76x76.png differ diff --git a/website/public/apple-icon-precomposed.png b/website/public/apple-icon-precomposed.png new file mode 100644 index 0000000..257dcfd Binary files /dev/null and b/website/public/apple-icon-precomposed.png differ diff --git a/website/public/apple-icon.png b/website/public/apple-icon.png new file mode 100644 index 0000000..257dcfd Binary files /dev/null and b/website/public/apple-icon.png differ diff --git a/website/public/dark-android-icon-144x144.png b/website/public/dark-android-icon-144x144.png new file mode 100644 index 0000000..9550407 Binary files /dev/null and b/website/public/dark-android-icon-144x144.png differ diff --git a/website/public/dark-android-icon-192x192.png b/website/public/dark-android-icon-192x192.png new file mode 100644 index 0000000..4bbdfa8 Binary files /dev/null and b/website/public/dark-android-icon-192x192.png differ diff --git a/website/public/dark-android-icon-36x36.png b/website/public/dark-android-icon-36x36.png new file mode 100644 index 0000000..eefe4c5 Binary files /dev/null and b/website/public/dark-android-icon-36x36.png differ diff --git a/website/public/dark-android-icon-48x48.png b/website/public/dark-android-icon-48x48.png new file mode 100644 index 0000000..1d2b8e6 Binary files /dev/null and b/website/public/dark-android-icon-48x48.png differ diff --git a/website/public/dark-android-icon-72x72.png b/website/public/dark-android-icon-72x72.png new file mode 100644 index 0000000..1bb8ec0 Binary files /dev/null and b/website/public/dark-android-icon-72x72.png differ diff --git a/website/public/dark-android-icon-96x96.png b/website/public/dark-android-icon-96x96.png new file mode 100644 index 0000000..a344617 Binary files /dev/null and b/website/public/dark-android-icon-96x96.png differ diff --git a/website/public/dark-apple-icon-114x114.png b/website/public/dark-apple-icon-114x114.png new file mode 100644 index 0000000..8b9b951 Binary files /dev/null and b/website/public/dark-apple-icon-114x114.png differ diff --git a/website/public/dark-apple-icon-120x120.png b/website/public/dark-apple-icon-120x120.png new file mode 100644 index 0000000..a855f65 Binary files /dev/null and b/website/public/dark-apple-icon-120x120.png differ diff --git a/website/public/dark-apple-icon-144x144.png b/website/public/dark-apple-icon-144x144.png new file mode 100644 index 0000000..9550407 Binary files /dev/null and b/website/public/dark-apple-icon-144x144.png differ diff --git a/website/public/dark-apple-icon-152x152.png b/website/public/dark-apple-icon-152x152.png new file mode 100644 index 0000000..5f5dfcc Binary files /dev/null and b/website/public/dark-apple-icon-152x152.png differ diff --git a/website/public/dark-apple-icon-180x180.png b/website/public/dark-apple-icon-180x180.png new file mode 100644 index 0000000..413598c Binary files /dev/null and b/website/public/dark-apple-icon-180x180.png differ diff --git a/website/public/dark-apple-icon-57x57.png b/website/public/dark-apple-icon-57x57.png new file mode 100644 index 0000000..96739df Binary files /dev/null and b/website/public/dark-apple-icon-57x57.png differ diff --git a/website/public/dark-apple-icon-60x60.png b/website/public/dark-apple-icon-60x60.png new file mode 100644 index 0000000..ab939d7 Binary files /dev/null and b/website/public/dark-apple-icon-60x60.png differ diff --git a/website/public/dark-apple-icon-72x72.png b/website/public/dark-apple-icon-72x72.png new file mode 100644 index 0000000..1bb8ec0 Binary files /dev/null and b/website/public/dark-apple-icon-72x72.png differ diff --git a/website/public/dark-apple-icon-76x76.png b/website/public/dark-apple-icon-76x76.png new file mode 100644 index 0000000..93fc10e Binary files /dev/null and b/website/public/dark-apple-icon-76x76.png differ diff --git a/website/public/dark-apple-icon-precomposed.png b/website/public/dark-apple-icon-precomposed.png new file mode 100644 index 0000000..11813a0 Binary files /dev/null and b/website/public/dark-apple-icon-precomposed.png differ diff --git a/website/public/dark-apple-icon.png b/website/public/dark-apple-icon.png new file mode 100644 index 0000000..11813a0 Binary files /dev/null and b/website/public/dark-apple-icon.png differ diff --git a/website/public/dark-favicon-16x16.png b/website/public/dark-favicon-16x16.png new file mode 100644 index 0000000..e7d0cf7 Binary files /dev/null and b/website/public/dark-favicon-16x16.png differ diff --git a/website/public/dark-favicon-32x32.png b/website/public/dark-favicon-32x32.png new file mode 100644 index 0000000..a7b4a64 Binary files /dev/null and b/website/public/dark-favicon-32x32.png differ diff --git a/website/public/dark-favicon-96x96.png b/website/public/dark-favicon-96x96.png new file mode 100644 index 0000000..a344617 Binary files /dev/null and b/website/public/dark-favicon-96x96.png differ diff --git a/website/public/dark-favicon.ico b/website/public/dark-favicon.ico new file mode 100644 index 0000000..ed3f355 Binary files /dev/null and b/website/public/dark-favicon.ico differ diff --git a/website/public/dark-ms-icon-144x144.png b/website/public/dark-ms-icon-144x144.png new file mode 100644 index 0000000..9550407 Binary files /dev/null and b/website/public/dark-ms-icon-144x144.png differ diff --git a/website/public/dark-ms-icon-150x150.png b/website/public/dark-ms-icon-150x150.png new file mode 100644 index 0000000..0d5eba6 Binary files /dev/null and b/website/public/dark-ms-icon-150x150.png differ diff --git a/website/public/dark-ms-icon-310x310.png b/website/public/dark-ms-icon-310x310.png new file mode 100644 index 0000000..6d87eec Binary files /dev/null and b/website/public/dark-ms-icon-310x310.png differ diff --git a/website/public/dark-ms-icon-70x70.png b/website/public/dark-ms-icon-70x70.png new file mode 100644 index 0000000..2339803 Binary files /dev/null and b/website/public/dark-ms-icon-70x70.png differ diff --git a/website/public/favicon-16x16.png b/website/public/favicon-16x16.png new file mode 100644 index 0000000..538670f Binary files /dev/null and b/website/public/favicon-16x16.png differ diff --git a/website/public/favicon-32x32.png b/website/public/favicon-32x32.png new file mode 100644 index 0000000..65ae08c Binary files /dev/null and b/website/public/favicon-32x32.png differ diff --git a/website/public/favicon-96x96.png b/website/public/favicon-96x96.png new file mode 100644 index 0000000..f6b553b Binary files /dev/null and b/website/public/favicon-96x96.png differ diff --git a/website/public/favicon.ico b/website/public/favicon.ico new file mode 100644 index 0000000..2850cc1 Binary files /dev/null and b/website/public/favicon.ico differ diff --git a/website/public/ms-icon-144x144.png b/website/public/ms-icon-144x144.png new file mode 100644 index 0000000..1e0ff9a Binary files /dev/null and b/website/public/ms-icon-144x144.png differ diff --git a/website/public/ms-icon-150x150.png b/website/public/ms-icon-150x150.png new file mode 100644 index 0000000..942a814 Binary files /dev/null and b/website/public/ms-icon-150x150.png differ diff --git a/website/public/ms-icon-310x310.png b/website/public/ms-icon-310x310.png new file mode 100644 index 0000000..30fb76c Binary files /dev/null and b/website/public/ms-icon-310x310.png differ diff --git a/website/public/ms-icon-70x70.png b/website/public/ms-icon-70x70.png new file mode 100644 index 0000000..e8f809b Binary files /dev/null and b/website/public/ms-icon-70x70.png differ diff --git a/website/public/og.png b/website/public/og.png new file mode 100644 index 0000000..e476a41 Binary files /dev/null and b/website/public/og.png differ