├── .github
└── workflows
│ ├── main.yml
│ └── size.yml
├── .gitignore
├── LICENSE
├── README.md
├── assets
└── header.svg
├── package.json
├── pnpm-lock.yaml
├── site
├── .gitignore
├── README.md
├── assets
│ ├── arrow.svg
│ ├── butter-1.svg
│ ├── butter-2.png
│ ├── butter-2.svg
│ ├── checkmark.svg
│ ├── github.svg
│ ├── logo-small.svg
│ └── logo.svg
├── components
│ ├── code.tsx
│ ├── docs-layout.tsx
│ ├── emoji-button.tsx
│ └── sections
│ │ ├── footer.tsx
│ │ ├── splitbee-counter.tsx
│ │ ├── toast-example.tsx
│ │ └── toaster-example.tsx
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── pages
│ ├── _app.tsx
│ ├── docs
│ │ ├── index.mdx
│ │ ├── styling.mdx
│ │ ├── toast-bar.mdx
│ │ ├── toast.mdx
│ │ ├── toaster.mdx
│ │ ├── use-toaster-store.mdx
│ │ ├── use-toaster.mdx
│ │ └── version-2.mdx
│ └── index.tsx
├── pnpm-lock.yaml
├── postcss.config.js
├── public
│ ├── favicon.png
│ └── social-image.png
├── styles
│ ├── main.css
│ ├── prism-theme.css
│ └── tailwind-utils.css
├── tailwind.config.js
├── tsconfig.json
└── types
│ ├── mdx.d.ts
│ └── svg.d.ts
├── src
├── components
│ ├── checkmark.tsx
│ ├── error.tsx
│ ├── loader.tsx
│ ├── toast-bar.tsx
│ ├── toast-icon.tsx
│ └── toaster.tsx
├── core
│ ├── store.ts
│ ├── toast.ts
│ ├── types.ts
│ ├── use-toaster.ts
│ └── utils.ts
├── headless
│ └── index.ts
└── index.ts
├── test
└── toast.test.tsx
├── tsconfig.json
└── tsup.config.ts
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | name: Build
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v2
9 | - uses: pnpm/action-setup@v2.2.2
10 | with:
11 | version: 7
12 | - name: Use Node.js ${{ matrix.node-version }}
13 | uses: actions/setup-node@v2
14 | with:
15 | node-version: ${{ matrix.node-version }}
16 | cache: 'pnpm'
17 | - name: Install dependencies
18 | run: pnpm install
19 | - name: Build package
20 | run: pnpm build
21 |
--------------------------------------------------------------------------------
/.github/workflows/size.yml:
--------------------------------------------------------------------------------
1 | name: size
2 | on: [pull_request]
3 | jobs:
4 | size:
5 | runs-on: ubuntu-latest
6 | env:
7 | CI_JOB_NUMBER: 1
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: pnpm/action-setup@v2.2.2
11 | with:
12 | version: 7
13 | - uses: andresz1/size-limit-action@v1
14 | with:
15 | github_token: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 | /headless
7 | .vscode
8 | .vercel
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Timo Lins
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 | Smoking hot Notifications for React.
11 | Lightweight, customizable and beautiful by default.
12 |
13 |
20 |
21 |
22 |
25 |
26 |
27 |
28 | ## Features
29 |
30 | - 🔥 **Hot by default**
31 | - 🔩 **Easily Customizable**
32 | - ⏳ **Promise API** - _Automatic loader from a promise_
33 | - 🕊 **Lightweight** - _less than 5kb including styles_
34 | - ✅ **Accessible**
35 | - 🤯 **Headless Hooks** - _Create your own with [`useToaster()`](https://react-hot-toast.com/docs/use-toaster)_
36 |
37 | ## Installation
38 |
39 | #### With yarn
40 |
41 | ```sh
42 | yarn add react-hot-toast
43 | ```
44 |
45 | #### With NPM
46 |
47 | ```sh
48 | npm install react-hot-toast
49 | ```
50 |
51 | ## Getting Started
52 |
53 | Add the Toaster to your app first. It will take care of rendering all notifications emitted. Now you can trigger `toast()` from anywhere!
54 |
55 | ```jsx
56 | import toast, { Toaster } from 'react-hot-toast';
57 |
58 | const notify = () => toast('Here is your toast.');
59 |
60 | const App = () => {
61 | return (
62 |
63 | Make me a toast
64 |
65 |
66 | );
67 | };
68 | ```
69 |
70 | ## Documentation
71 |
72 | Find the full API reference on [official documentation](https://react-hot-toast.com/docs).
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-hot-toast",
3 | "description": "Smoking hot React Notifications. Lightweight, customizable and beautiful by default.",
4 | "version": "2.3.0",
5 | "author": "Timo Lins",
6 | "license": "MIT",
7 | "repository": "timolins/react-hot-toast",
8 | "keywords": [
9 | "react",
10 | "notifications",
11 | "toast",
12 | "snackbar"
13 | ],
14 | "main": "dist/index.js",
15 | "types": "dist/index.d.ts",
16 | "exports": {
17 | "./package.json": "./package.json",
18 | ".": {
19 | "types": "./dist/index.d.ts",
20 | "import": "./dist/index.mjs",
21 | "require": "./dist/index.js"
22 | },
23 | "./headless": {
24 | "types": "./headless/index.d.ts",
25 | "import": "./headless/index.mjs",
26 | "require": "./headless/index.js"
27 | }
28 | },
29 | "files": [
30 | "headless",
31 | "dist",
32 | "src"
33 | ],
34 | "engines": {
35 | "node": ">=10"
36 | },
37 | "scripts": {
38 | "start": "tsup --watch",
39 | "build": "tsup",
40 | "test": "echo \"No test specified\"",
41 | "setup": "pnpm i && cd site && pnpm i && cd .. && pnpm run link",
42 | "link": "pnpm link ./site/node_modules/react && pnpm link ./site/node_modules/react-dom",
43 | "size": "size-limit"
44 | },
45 | "husky": {
46 | "hooks": {
47 | "pre-commit": "prettier src --ignore-unknown --write"
48 | }
49 | },
50 | "prettier": {
51 | "printWidth": 80,
52 | "semi": true,
53 | "singleQuote": true,
54 | "arrowParens": "always",
55 | "trailingComma": "es5"
56 | },
57 | "size-limit": [
58 | {
59 | "path": "dist/index.js",
60 | "limit": "5 KB"
61 | },
62 | {
63 | "path": "dist/index.mjs",
64 | "limit": "5 KB"
65 | },
66 | {
67 | "path": "headless/index.js",
68 | "limit": "2 KB"
69 | },
70 | {
71 | "path": "headless/index.mjs",
72 | "limit": "2 KB"
73 | }
74 | ],
75 | "devDependencies": {
76 | "@size-limit/preset-small-lib": "^7.0.8",
77 | "@types/react": "^17.0.0",
78 | "@types/react-dom": "^17.0.0",
79 | "csstype": "^3.0.7",
80 | "prettier": "^2.7.1",
81 | "size-limit": "^7.0.8",
82 | "tsup": "^6.1.3",
83 | "typescript": "^4.7.4"
84 | },
85 | "dependencies": {
86 | "goober": "^2.1.10"
87 | },
88 | "peerDependencies": {
89 | "react": ">=16",
90 | "react-dom": ">=16"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: 5.4
2 |
3 | specifiers:
4 | '@size-limit/preset-small-lib': ^7.0.8
5 | '@types/react': ^17.0.0
6 | '@types/react-dom': ^17.0.0
7 | csstype: ^3.0.7
8 | goober: ^2.1.10
9 | prettier: ^2.7.1
10 | size-limit: ^7.0.8
11 | tsup: ^6.1.3
12 | typescript: ^4.7.4
13 |
14 | dependencies:
15 | goober: 2.1.10_csstype@3.1.0
16 |
17 | devDependencies:
18 | '@size-limit/preset-small-lib': 7.0.8_size-limit@7.0.8
19 | '@types/react': 17.0.47
20 | '@types/react-dom': 17.0.17
21 | csstype: 3.1.0
22 | prettier: 2.7.1
23 | size-limit: 7.0.8
24 | tsup: 6.1.3_typescript@4.7.4
25 | typescript: 4.7.4
26 |
27 | packages:
28 |
29 | /@nodelib/fs.scandir/2.1.5:
30 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
31 | engines: {node: '>= 8'}
32 | dependencies:
33 | '@nodelib/fs.stat': 2.0.5
34 | run-parallel: 1.2.0
35 | dev: true
36 |
37 | /@nodelib/fs.stat/2.0.5:
38 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
39 | engines: {node: '>= 8'}
40 | dev: true
41 |
42 | /@nodelib/fs.walk/1.2.8:
43 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
44 | engines: {node: '>= 8'}
45 | dependencies:
46 | '@nodelib/fs.scandir': 2.1.5
47 | fastq: 1.13.0
48 | dev: true
49 |
50 | /@size-limit/esbuild/7.0.8_size-limit@7.0.8:
51 | resolution: {integrity: sha512-AzCrxJJThDvHrBNoolebYVgXu46c6HuS3fOxoXr3V0YWNM0qz81z5F3j7RruzboZnls8ZgME4WrH6GM5rB9gtA==}
52 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
53 | peerDependencies:
54 | size-limit: 7.0.8
55 | dependencies:
56 | esbuild: 0.14.48
57 | nanoid: 3.3.4
58 | size-limit: 7.0.8
59 | dev: true
60 |
61 | /@size-limit/file/7.0.8_size-limit@7.0.8:
62 | resolution: {integrity: sha512-1KeFQuMXIXAH/iELqIX7x+YNYDFvzIvmxcp9PrdwEoSNL0dXdaDIo9WE/yz8xvOmUcKaLfqbWkL75DM0k91WHQ==}
63 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
64 | peerDependencies:
65 | size-limit: 7.0.8
66 | dependencies:
67 | semver: 7.3.5
68 | size-limit: 7.0.8
69 | dev: true
70 |
71 | /@size-limit/preset-small-lib/7.0.8_size-limit@7.0.8:
72 | resolution: {integrity: sha512-CT8nIYA/c2CSD+X4rAUgwqYccQMahJ6rBnaZxvi3YKFdkXIbuGNXHNjHsYaFksgwG9P4UjG/unyO5L73f3zQBw==}
73 | peerDependencies:
74 | size-limit: 7.0.8
75 | dependencies:
76 | '@size-limit/esbuild': 7.0.8_size-limit@7.0.8
77 | '@size-limit/file': 7.0.8_size-limit@7.0.8
78 | size-limit: 7.0.8
79 | dev: true
80 |
81 | /@types/prop-types/15.7.5:
82 | resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
83 | dev: true
84 |
85 | /@types/react-dom/17.0.17:
86 | resolution: {integrity: sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==}
87 | dependencies:
88 | '@types/react': 17.0.47
89 | dev: true
90 |
91 | /@types/react/17.0.47:
92 | resolution: {integrity: sha512-mk0BL8zBinf2ozNr3qPnlu1oyVTYq+4V7WA76RgxUAtf0Em/Wbid38KN6n4abEkvO4xMTBWmnP1FtQzgkEiJoA==}
93 | dependencies:
94 | '@types/prop-types': 15.7.5
95 | '@types/scheduler': 0.16.2
96 | csstype: 3.1.0
97 | dev: true
98 |
99 | /@types/scheduler/0.16.2:
100 | resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
101 | dev: true
102 |
103 | /any-promise/1.3.0:
104 | resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
105 | dev: true
106 |
107 | /anymatch/3.1.2:
108 | resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
109 | engines: {node: '>= 8'}
110 | dependencies:
111 | normalize-path: 3.0.0
112 | picomatch: 2.3.1
113 | dev: true
114 |
115 | /array-union/2.1.0:
116 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
117 | engines: {node: '>=8'}
118 | dev: true
119 |
120 | /balanced-match/1.0.2:
121 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
122 | dev: true
123 |
124 | /binary-extensions/2.2.0:
125 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
126 | engines: {node: '>=8'}
127 | dev: true
128 |
129 | /brace-expansion/1.1.11:
130 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
131 | dependencies:
132 | balanced-match: 1.0.2
133 | concat-map: 0.0.1
134 | dev: true
135 |
136 | /braces/3.0.2:
137 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
138 | engines: {node: '>=8'}
139 | dependencies:
140 | fill-range: 7.0.1
141 | dev: true
142 |
143 | /bundle-require/3.0.4_esbuild@0.14.48:
144 | resolution: {integrity: sha512-VXG6epB1yrLAvWVQpl92qF347/UXmncQj7J3U8kZEbdVZ1ZkQyr4hYeL/9RvcE8vVVdp53dY78Fd/3pqfRqI1A==}
145 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
146 | peerDependencies:
147 | esbuild: '>=0.13'
148 | dependencies:
149 | esbuild: 0.14.48
150 | load-tsconfig: 0.2.3
151 | dev: true
152 |
153 | /bytes-iec/3.1.1:
154 | resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==}
155 | engines: {node: '>= 0.8'}
156 | dev: true
157 |
158 | /cac/6.7.12:
159 | resolution: {integrity: sha512-rM7E2ygtMkJqD9c7WnFU6fruFcN3xe4FM5yUmgxhZzIKJk4uHl9U/fhwdajGFQbQuv43FAUo1Fe8gX/oIKDeSA==}
160 | engines: {node: '>=8'}
161 | dev: true
162 |
163 | /chokidar/3.5.3:
164 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
165 | engines: {node: '>= 8.10.0'}
166 | dependencies:
167 | anymatch: 3.1.2
168 | braces: 3.0.2
169 | glob-parent: 5.1.2
170 | is-binary-path: 2.1.0
171 | is-glob: 4.0.3
172 | normalize-path: 3.0.0
173 | readdirp: 3.6.0
174 | optionalDependencies:
175 | fsevents: 2.3.2
176 | dev: true
177 |
178 | /ci-job-number/1.2.2:
179 | resolution: {integrity: sha512-CLOGsVDrVamzv8sXJGaILUVI6dsuAkouJP/n6t+OxLPeeA4DDby7zn9SB6EUpa1H7oIKoE+rMmkW80zYsFfUjA==}
180 | dev: true
181 |
182 | /commander/4.1.1:
183 | resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
184 | engines: {node: '>= 6'}
185 | dev: true
186 |
187 | /concat-map/0.0.1:
188 | resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
189 | dev: true
190 |
191 | /cross-spawn/7.0.3:
192 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
193 | engines: {node: '>= 8'}
194 | dependencies:
195 | path-key: 3.1.1
196 | shebang-command: 2.0.0
197 | which: 2.0.2
198 | dev: true
199 |
200 | /csstype/3.1.0:
201 | resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==}
202 |
203 | /debug/4.3.4:
204 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
205 | engines: {node: '>=6.0'}
206 | peerDependencies:
207 | supports-color: '*'
208 | peerDependenciesMeta:
209 | supports-color:
210 | optional: true
211 | dependencies:
212 | ms: 2.1.2
213 | dev: true
214 |
215 | /dir-glob/3.0.1:
216 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
217 | engines: {node: '>=8'}
218 | dependencies:
219 | path-type: 4.0.0
220 | dev: true
221 |
222 | /esbuild-android-64/0.14.48:
223 | resolution: {integrity: sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==}
224 | engines: {node: '>=12'}
225 | cpu: [x64]
226 | os: [android]
227 | requiresBuild: true
228 | dev: true
229 | optional: true
230 |
231 | /esbuild-android-arm64/0.14.48:
232 | resolution: {integrity: sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==}
233 | engines: {node: '>=12'}
234 | cpu: [arm64]
235 | os: [android]
236 | requiresBuild: true
237 | dev: true
238 | optional: true
239 |
240 | /esbuild-darwin-64/0.14.48:
241 | resolution: {integrity: sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==}
242 | engines: {node: '>=12'}
243 | cpu: [x64]
244 | os: [darwin]
245 | requiresBuild: true
246 | dev: true
247 | optional: true
248 |
249 | /esbuild-darwin-arm64/0.14.48:
250 | resolution: {integrity: sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==}
251 | engines: {node: '>=12'}
252 | cpu: [arm64]
253 | os: [darwin]
254 | requiresBuild: true
255 | dev: true
256 | optional: true
257 |
258 | /esbuild-freebsd-64/0.14.48:
259 | resolution: {integrity: sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==}
260 | engines: {node: '>=12'}
261 | cpu: [x64]
262 | os: [freebsd]
263 | requiresBuild: true
264 | dev: true
265 | optional: true
266 |
267 | /esbuild-freebsd-arm64/0.14.48:
268 | resolution: {integrity: sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==}
269 | engines: {node: '>=12'}
270 | cpu: [arm64]
271 | os: [freebsd]
272 | requiresBuild: true
273 | dev: true
274 | optional: true
275 |
276 | /esbuild-linux-32/0.14.48:
277 | resolution: {integrity: sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==}
278 | engines: {node: '>=12'}
279 | cpu: [ia32]
280 | os: [linux]
281 | requiresBuild: true
282 | dev: true
283 | optional: true
284 |
285 | /esbuild-linux-64/0.14.48:
286 | resolution: {integrity: sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==}
287 | engines: {node: '>=12'}
288 | cpu: [x64]
289 | os: [linux]
290 | requiresBuild: true
291 | dev: true
292 | optional: true
293 |
294 | /esbuild-linux-arm/0.14.48:
295 | resolution: {integrity: sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==}
296 | engines: {node: '>=12'}
297 | cpu: [arm]
298 | os: [linux]
299 | requiresBuild: true
300 | dev: true
301 | optional: true
302 |
303 | /esbuild-linux-arm64/0.14.48:
304 | resolution: {integrity: sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==}
305 | engines: {node: '>=12'}
306 | cpu: [arm64]
307 | os: [linux]
308 | requiresBuild: true
309 | dev: true
310 | optional: true
311 |
312 | /esbuild-linux-mips64le/0.14.48:
313 | resolution: {integrity: sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==}
314 | engines: {node: '>=12'}
315 | cpu: [mips64el]
316 | os: [linux]
317 | requiresBuild: true
318 | dev: true
319 | optional: true
320 |
321 | /esbuild-linux-ppc64le/0.14.48:
322 | resolution: {integrity: sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==}
323 | engines: {node: '>=12'}
324 | cpu: [ppc64]
325 | os: [linux]
326 | requiresBuild: true
327 | dev: true
328 | optional: true
329 |
330 | /esbuild-linux-riscv64/0.14.48:
331 | resolution: {integrity: sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==}
332 | engines: {node: '>=12'}
333 | cpu: [riscv64]
334 | os: [linux]
335 | requiresBuild: true
336 | dev: true
337 | optional: true
338 |
339 | /esbuild-linux-s390x/0.14.48:
340 | resolution: {integrity: sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==}
341 | engines: {node: '>=12'}
342 | cpu: [s390x]
343 | os: [linux]
344 | requiresBuild: true
345 | dev: true
346 | optional: true
347 |
348 | /esbuild-netbsd-64/0.14.48:
349 | resolution: {integrity: sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==}
350 | engines: {node: '>=12'}
351 | cpu: [x64]
352 | os: [netbsd]
353 | requiresBuild: true
354 | dev: true
355 | optional: true
356 |
357 | /esbuild-openbsd-64/0.14.48:
358 | resolution: {integrity: sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==}
359 | engines: {node: '>=12'}
360 | cpu: [x64]
361 | os: [openbsd]
362 | requiresBuild: true
363 | dev: true
364 | optional: true
365 |
366 | /esbuild-sunos-64/0.14.48:
367 | resolution: {integrity: sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==}
368 | engines: {node: '>=12'}
369 | cpu: [x64]
370 | os: [sunos]
371 | requiresBuild: true
372 | dev: true
373 | optional: true
374 |
375 | /esbuild-windows-32/0.14.48:
376 | resolution: {integrity: sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==}
377 | engines: {node: '>=12'}
378 | cpu: [ia32]
379 | os: [win32]
380 | requiresBuild: true
381 | dev: true
382 | optional: true
383 |
384 | /esbuild-windows-64/0.14.48:
385 | resolution: {integrity: sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==}
386 | engines: {node: '>=12'}
387 | cpu: [x64]
388 | os: [win32]
389 | requiresBuild: true
390 | dev: true
391 | optional: true
392 |
393 | /esbuild-windows-arm64/0.14.48:
394 | resolution: {integrity: sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==}
395 | engines: {node: '>=12'}
396 | cpu: [arm64]
397 | os: [win32]
398 | requiresBuild: true
399 | dev: true
400 | optional: true
401 |
402 | /esbuild/0.14.48:
403 | resolution: {integrity: sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==}
404 | engines: {node: '>=12'}
405 | hasBin: true
406 | requiresBuild: true
407 | optionalDependencies:
408 | esbuild-android-64: 0.14.48
409 | esbuild-android-arm64: 0.14.48
410 | esbuild-darwin-64: 0.14.48
411 | esbuild-darwin-arm64: 0.14.48
412 | esbuild-freebsd-64: 0.14.48
413 | esbuild-freebsd-arm64: 0.14.48
414 | esbuild-linux-32: 0.14.48
415 | esbuild-linux-64: 0.14.48
416 | esbuild-linux-arm: 0.14.48
417 | esbuild-linux-arm64: 0.14.48
418 | esbuild-linux-mips64le: 0.14.48
419 | esbuild-linux-ppc64le: 0.14.48
420 | esbuild-linux-riscv64: 0.14.48
421 | esbuild-linux-s390x: 0.14.48
422 | esbuild-netbsd-64: 0.14.48
423 | esbuild-openbsd-64: 0.14.48
424 | esbuild-sunos-64: 0.14.48
425 | esbuild-windows-32: 0.14.48
426 | esbuild-windows-64: 0.14.48
427 | esbuild-windows-arm64: 0.14.48
428 | dev: true
429 |
430 | /execa/5.1.1:
431 | resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
432 | engines: {node: '>=10'}
433 | dependencies:
434 | cross-spawn: 7.0.3
435 | get-stream: 6.0.1
436 | human-signals: 2.1.0
437 | is-stream: 2.0.1
438 | merge-stream: 2.0.0
439 | npm-run-path: 4.0.1
440 | onetime: 5.1.2
441 | signal-exit: 3.0.7
442 | strip-final-newline: 2.0.0
443 | dev: true
444 |
445 | /fast-glob/3.2.11:
446 | resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
447 | engines: {node: '>=8.6.0'}
448 | dependencies:
449 | '@nodelib/fs.stat': 2.0.5
450 | '@nodelib/fs.walk': 1.2.8
451 | glob-parent: 5.1.2
452 | merge2: 1.4.1
453 | micromatch: 4.0.5
454 | dev: true
455 |
456 | /fastq/1.13.0:
457 | resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
458 | dependencies:
459 | reusify: 1.0.4
460 | dev: true
461 |
462 | /fill-range/7.0.1:
463 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
464 | engines: {node: '>=8'}
465 | dependencies:
466 | to-regex-range: 5.0.1
467 | dev: true
468 |
469 | /fs.realpath/1.0.0:
470 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
471 | dev: true
472 |
473 | /fsevents/2.3.2:
474 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
475 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
476 | os: [darwin]
477 | requiresBuild: true
478 | dev: true
479 | optional: true
480 |
481 | /get-stream/6.0.1:
482 | resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
483 | engines: {node: '>=10'}
484 | dev: true
485 |
486 | /glob-parent/5.1.2:
487 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
488 | engines: {node: '>= 6'}
489 | dependencies:
490 | is-glob: 4.0.3
491 | dev: true
492 |
493 | /glob/7.1.6:
494 | resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
495 | dependencies:
496 | fs.realpath: 1.0.0
497 | inflight: 1.0.6
498 | inherits: 2.0.4
499 | minimatch: 3.1.2
500 | once: 1.4.0
501 | path-is-absolute: 1.0.1
502 | dev: true
503 |
504 | /globby/11.1.0:
505 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
506 | engines: {node: '>=10'}
507 | dependencies:
508 | array-union: 2.1.0
509 | dir-glob: 3.0.1
510 | fast-glob: 3.2.11
511 | ignore: 5.2.0
512 | merge2: 1.4.1
513 | slash: 3.0.0
514 | dev: true
515 |
516 | /goober/2.1.10_csstype@3.1.0:
517 | resolution: {integrity: sha512-7PpuQMH10jaTWm33sQgBQvz45pHR8N4l3Cu3WMGEWmHShAcTuuP7I+5/DwKo39fwti5A80WAjvqgz6SSlgWmGA==}
518 | peerDependencies:
519 | csstype: ^3.0.10
520 | dependencies:
521 | csstype: 3.1.0
522 | dev: false
523 |
524 | /human-signals/2.1.0:
525 | resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
526 | engines: {node: '>=10.17.0'}
527 | dev: true
528 |
529 | /ignore/5.2.0:
530 | resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
531 | engines: {node: '>= 4'}
532 | dev: true
533 |
534 | /inflight/1.0.6:
535 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
536 | dependencies:
537 | once: 1.4.0
538 | wrappy: 1.0.2
539 | dev: true
540 |
541 | /inherits/2.0.4:
542 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
543 | dev: true
544 |
545 | /is-binary-path/2.1.0:
546 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
547 | engines: {node: '>=8'}
548 | dependencies:
549 | binary-extensions: 2.2.0
550 | dev: true
551 |
552 | /is-extglob/2.1.1:
553 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
554 | engines: {node: '>=0.10.0'}
555 | dev: true
556 |
557 | /is-glob/4.0.3:
558 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
559 | engines: {node: '>=0.10.0'}
560 | dependencies:
561 | is-extglob: 2.1.1
562 | dev: true
563 |
564 | /is-number/7.0.0:
565 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
566 | engines: {node: '>=0.12.0'}
567 | dev: true
568 |
569 | /is-stream/2.0.1:
570 | resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
571 | engines: {node: '>=8'}
572 | dev: true
573 |
574 | /isexe/2.0.0:
575 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
576 | dev: true
577 |
578 | /joycon/3.1.1:
579 | resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
580 | engines: {node: '>=10'}
581 | dev: true
582 |
583 | /lilconfig/2.0.5:
584 | resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==}
585 | engines: {node: '>=10'}
586 | dev: true
587 |
588 | /lines-and-columns/1.2.4:
589 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
590 | dev: true
591 |
592 | /load-tsconfig/0.2.3:
593 | resolution: {integrity: sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ==}
594 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
595 | dev: true
596 |
597 | /lodash.sortby/4.7.0:
598 | resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
599 | dev: true
600 |
601 | /lru-cache/6.0.0:
602 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
603 | engines: {node: '>=10'}
604 | dependencies:
605 | yallist: 4.0.0
606 | dev: true
607 |
608 | /merge-stream/2.0.0:
609 | resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
610 | dev: true
611 |
612 | /merge2/1.4.1:
613 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
614 | engines: {node: '>= 8'}
615 | dev: true
616 |
617 | /micromatch/4.0.5:
618 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
619 | engines: {node: '>=8.6'}
620 | dependencies:
621 | braces: 3.0.2
622 | picomatch: 2.3.1
623 | dev: true
624 |
625 | /mimic-fn/2.1.0:
626 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
627 | engines: {node: '>=6'}
628 | dev: true
629 |
630 | /minimatch/3.1.2:
631 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
632 | dependencies:
633 | brace-expansion: 1.1.11
634 | dev: true
635 |
636 | /mkdirp/1.0.4:
637 | resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
638 | engines: {node: '>=10'}
639 | hasBin: true
640 | dev: true
641 |
642 | /ms/2.1.2:
643 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
644 | dev: true
645 |
646 | /mz/2.7.0:
647 | resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
648 | dependencies:
649 | any-promise: 1.3.0
650 | object-assign: 4.1.1
651 | thenify-all: 1.6.0
652 | dev: true
653 |
654 | /nanoid/3.3.4:
655 | resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
656 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
657 | hasBin: true
658 | dev: true
659 |
660 | /nanospinner/1.1.0:
661 | resolution: {integrity: sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA==}
662 | dependencies:
663 | picocolors: 1.0.0
664 | dev: true
665 |
666 | /normalize-path/3.0.0:
667 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
668 | engines: {node: '>=0.10.0'}
669 | dev: true
670 |
671 | /npm-run-path/4.0.1:
672 | resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
673 | engines: {node: '>=8'}
674 | dependencies:
675 | path-key: 3.1.1
676 | dev: true
677 |
678 | /object-assign/4.1.1:
679 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
680 | engines: {node: '>=0.10.0'}
681 | dev: true
682 |
683 | /once/1.4.0:
684 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
685 | dependencies:
686 | wrappy: 1.0.2
687 | dev: true
688 |
689 | /onetime/5.1.2:
690 | resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
691 | engines: {node: '>=6'}
692 | dependencies:
693 | mimic-fn: 2.1.0
694 | dev: true
695 |
696 | /path-is-absolute/1.0.1:
697 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
698 | engines: {node: '>=0.10.0'}
699 | dev: true
700 |
701 | /path-key/3.1.1:
702 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
703 | engines: {node: '>=8'}
704 | dev: true
705 |
706 | /path-type/4.0.0:
707 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
708 | engines: {node: '>=8'}
709 | dev: true
710 |
711 | /picocolors/1.0.0:
712 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
713 | dev: true
714 |
715 | /picomatch/2.3.1:
716 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
717 | engines: {node: '>=8.6'}
718 | dev: true
719 |
720 | /pirates/4.0.5:
721 | resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==}
722 | engines: {node: '>= 6'}
723 | dev: true
724 |
725 | /postcss-load-config/3.1.4:
726 | resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
727 | engines: {node: '>= 10'}
728 | peerDependencies:
729 | postcss: '>=8.0.9'
730 | ts-node: '>=9.0.0'
731 | peerDependenciesMeta:
732 | postcss:
733 | optional: true
734 | ts-node:
735 | optional: true
736 | dependencies:
737 | lilconfig: 2.0.5
738 | yaml: 1.10.2
739 | dev: true
740 |
741 | /prettier/2.7.1:
742 | resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
743 | engines: {node: '>=10.13.0'}
744 | hasBin: true
745 | dev: true
746 |
747 | /punycode/2.1.1:
748 | resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
749 | engines: {node: '>=6'}
750 | dev: true
751 |
752 | /queue-microtask/1.2.3:
753 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
754 | dev: true
755 |
756 | /readdirp/3.6.0:
757 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
758 | engines: {node: '>=8.10.0'}
759 | dependencies:
760 | picomatch: 2.3.1
761 | dev: true
762 |
763 | /resolve-from/5.0.0:
764 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
765 | engines: {node: '>=8'}
766 | dev: true
767 |
768 | /reusify/1.0.4:
769 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
770 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
771 | dev: true
772 |
773 | /rollup/2.75.7:
774 | resolution: {integrity: sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==}
775 | engines: {node: '>=10.0.0'}
776 | hasBin: true
777 | optionalDependencies:
778 | fsevents: 2.3.2
779 | dev: true
780 |
781 | /run-parallel/1.2.0:
782 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
783 | dependencies:
784 | queue-microtask: 1.2.3
785 | dev: true
786 |
787 | /semver/7.3.5:
788 | resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
789 | engines: {node: '>=10'}
790 | hasBin: true
791 | dependencies:
792 | lru-cache: 6.0.0
793 | dev: true
794 |
795 | /shebang-command/2.0.0:
796 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
797 | engines: {node: '>=8'}
798 | dependencies:
799 | shebang-regex: 3.0.0
800 | dev: true
801 |
802 | /shebang-regex/3.0.0:
803 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
804 | engines: {node: '>=8'}
805 | dev: true
806 |
807 | /signal-exit/3.0.7:
808 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
809 | dev: true
810 |
811 | /size-limit/7.0.8:
812 | resolution: {integrity: sha512-3h76c9E0e/nNhYLSR7IBI/bSoXICeo7EYkYjlyVqNIsu7KvN/PQmMbIXeyd2QKIF8iZKhaiZQoXLkGWbyPDtvQ==}
813 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
814 | hasBin: true
815 | dependencies:
816 | bytes-iec: 3.1.1
817 | chokidar: 3.5.3
818 | ci-job-number: 1.2.2
819 | globby: 11.1.0
820 | lilconfig: 2.0.5
821 | mkdirp: 1.0.4
822 | nanospinner: 1.1.0
823 | picocolors: 1.0.0
824 | dev: true
825 |
826 | /slash/3.0.0:
827 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
828 | engines: {node: '>=8'}
829 | dev: true
830 |
831 | /source-map/0.8.0-beta.0:
832 | resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
833 | engines: {node: '>= 8'}
834 | dependencies:
835 | whatwg-url: 7.1.0
836 | dev: true
837 |
838 | /strip-final-newline/2.0.0:
839 | resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
840 | engines: {node: '>=6'}
841 | dev: true
842 |
843 | /sucrase/3.23.0:
844 | resolution: {integrity: sha512-xgC1xboStzGhCnRywlBf/DLmkC+SkdAKqrNCDsxGrzM0phR5oUxoFKiQNrsc2D8wDdAm03iLbSZqjHDddo3IzQ==}
845 | engines: {node: '>=8'}
846 | hasBin: true
847 | dependencies:
848 | commander: 4.1.1
849 | glob: 7.1.6
850 | lines-and-columns: 1.2.4
851 | mz: 2.7.0
852 | pirates: 4.0.5
853 | ts-interface-checker: 0.1.13
854 | dev: true
855 |
856 | /thenify-all/1.6.0:
857 | resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
858 | engines: {node: '>=0.8'}
859 | dependencies:
860 | thenify: 3.3.1
861 | dev: true
862 |
863 | /thenify/3.3.1:
864 | resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
865 | dependencies:
866 | any-promise: 1.3.0
867 | dev: true
868 |
869 | /to-regex-range/5.0.1:
870 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
871 | engines: {node: '>=8.0'}
872 | dependencies:
873 | is-number: 7.0.0
874 | dev: true
875 |
876 | /tr46/1.0.1:
877 | resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
878 | dependencies:
879 | punycode: 2.1.1
880 | dev: true
881 |
882 | /tree-kill/1.2.2:
883 | resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
884 | hasBin: true
885 | dev: true
886 |
887 | /ts-interface-checker/0.1.13:
888 | resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
889 | dev: true
890 |
891 | /tsup/6.1.3_typescript@4.7.4:
892 | resolution: {integrity: sha512-eRpBnbfpDFng+EJNTQ90N7QAf4HAGGC7O3buHIjroKWK7D1ibk9/YnR/3cS8HsMU5T+6Oi+cnF+yU5WmCnB//Q==}
893 | engines: {node: '>=14'}
894 | hasBin: true
895 | peerDependencies:
896 | '@swc/core': ^1
897 | postcss: ^8.4.12
898 | typescript: ^4.1.0
899 | peerDependenciesMeta:
900 | '@swc/core':
901 | optional: true
902 | postcss:
903 | optional: true
904 | typescript:
905 | optional: true
906 | dependencies:
907 | bundle-require: 3.0.4_esbuild@0.14.48
908 | cac: 6.7.12
909 | chokidar: 3.5.3
910 | debug: 4.3.4
911 | esbuild: 0.14.48
912 | execa: 5.1.1
913 | globby: 11.1.0
914 | joycon: 3.1.1
915 | postcss-load-config: 3.1.4
916 | resolve-from: 5.0.0
917 | rollup: 2.75.7
918 | source-map: 0.8.0-beta.0
919 | sucrase: 3.23.0
920 | tree-kill: 1.2.2
921 | typescript: 4.7.4
922 | transitivePeerDependencies:
923 | - supports-color
924 | - ts-node
925 | dev: true
926 |
927 | /typescript/4.7.4:
928 | resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
929 | engines: {node: '>=4.2.0'}
930 | hasBin: true
931 | dev: true
932 |
933 | /webidl-conversions/4.0.2:
934 | resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
935 | dev: true
936 |
937 | /whatwg-url/7.1.0:
938 | resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
939 | dependencies:
940 | lodash.sortby: 4.7.0
941 | tr46: 1.0.1
942 | webidl-conversions: 4.0.2
943 | dev: true
944 |
945 | /which/2.0.2:
946 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
947 | engines: {node: '>= 8'}
948 | hasBin: true
949 | dependencies:
950 | isexe: 2.0.0
951 | dev: true
952 |
953 | /wrappy/1.0.2:
954 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
955 | dev: true
956 |
957 | /yallist/4.0.0:
958 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
959 | dev: true
960 |
961 | /yaml/1.10.2:
962 | resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
963 | engines: {node: '>= 6'}
964 | dev: true
965 |
--------------------------------------------------------------------------------
/site/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/site/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | ## Learn More
18 |
19 | To learn more about Next.js, take a look at the following resources:
20 |
21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
23 |
24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
25 |
26 | ## Deploy on Vercel
27 |
28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
29 |
30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
31 |
--------------------------------------------------------------------------------
/site/assets/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/site/assets/butter-1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/site/assets/butter-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogSoulDev/react-hot-toast/ee483cc02dcac4359a5081a99aa006fac54cfafc/site/assets/butter-2.png
--------------------------------------------------------------------------------
/site/assets/butter-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/site/assets/checkmark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/site/assets/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/site/assets/logo-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/site/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/site/components/code.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import Highlight, {
3 | defaultProps,
4 | Language,
5 | PrismTheme,
6 | } from 'prism-react-renderer';
7 |
8 | const theme: PrismTheme = {
9 | plain: {
10 | backgroundColor: '#351e11',
11 | color: '#d6ceff',
12 | },
13 | styles: [
14 | {
15 | types: ['comment', 'prolog', 'doctype', 'cdata', 'punctuation'],
16 | style: {
17 | color: '#6c6783',
18 | },
19 | },
20 | {
21 | types: ['namespace'],
22 | style: {
23 | opacity: 0.7,
24 | },
25 | },
26 | {
27 | types: ['tag', 'operator', 'number', 'module'],
28 | style: {
29 | color: '#e09142',
30 | },
31 | },
32 | {
33 | types: ['property', 'function'],
34 | style: {
35 | color: '#9a86fd',
36 | },
37 | },
38 | {
39 | types: ['tag-id', 'selector', 'atrule-id'],
40 | style: {
41 | color: '#eeebff',
42 | },
43 | },
44 | {
45 | types: ['attr-name'],
46 | style: {
47 | color: '#c4b9fe',
48 | },
49 | },
50 | {
51 | types: [
52 | 'boolean',
53 | 'string',
54 | 'entity',
55 | 'url',
56 | 'attr-value',
57 | 'keyword',
58 | 'control',
59 | 'directive',
60 | 'unit',
61 | 'statement',
62 | 'regex',
63 | 'at-rule',
64 | 'placeholder',
65 | 'variable',
66 | ],
67 | style: {
68 | color: '#ffcc99',
69 | },
70 | },
71 | {
72 | types: ['deleted'],
73 | style: {
74 | textDecorationLine: 'line-through',
75 | },
76 | },
77 | {
78 | types: ['inserted'],
79 | style: {
80 | textDecorationLine: 'underline',
81 | },
82 | },
83 | {
84 | types: ['italic'],
85 | style: {
86 | fontStyle: 'italic',
87 | },
88 | },
89 | {
90 | types: ['important', 'bold'],
91 | style: {
92 | fontWeight: 'bold',
93 | },
94 | },
95 | {
96 | types: ['important'],
97 | style: {
98 | color: '#c4b9fe',
99 | },
100 | },
101 | ],
102 | };
103 |
104 | export const Code: React.FC<{
105 | snippet: string;
106 | language?: Language;
107 | className?: string;
108 | }> = (props) => {
109 | const language = props.language || 'jsx';
110 |
111 | return (
112 |
118 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
119 |
127 | {tokens.map((line, i) => {
128 | if (tokens.length - 1 === i && line[0].empty) {
129 | return null;
130 | }
131 |
132 | return (
133 |
134 | {line.map((token, key) => (
135 |
136 | ))}
137 |
138 | );
139 | })}
140 |
141 | )}
142 |
143 | );
144 | };
145 |
--------------------------------------------------------------------------------
/site/components/docs-layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Toaster } from 'react-hot-toast';
3 | import { NextSeo } from 'next-seo';
4 | import Link from 'next/link';
5 | import { Footer } from './sections/footer';
6 | import Logo from '../assets/logo-small.svg';
7 |
8 | const TableItem: React.FC<{
9 | href: string;
10 | children?: React.ReactNode;
11 | }> = ({ children, href }) => (
12 |
13 |
14 | {children}
15 |
16 |
17 | );
18 |
19 | const TableHeader: React.FC<{
20 | children?: React.ReactNode;
21 | }> = ({ children }) => (
22 |
23 | {children}
24 |
25 | );
26 |
27 | export default function DocsLayout({ meta, children }) {
28 | return (
29 |
30 |
43 |
44 |
45 |
59 |
60 |
61 |
62 |
63 |
Overview
64 |
65 |
Get Started
66 |
67 |
API
68 |
69 |
toast()
70 |
{`Toaster`}
71 |
{`ToastBar`}
72 |
useToaster()
73 |
74 | useToasterStore()
75 |
76 |
Guides
77 |
Styling
78 |
Releases
79 |
New in 2.0
80 |
81 |
82 |
83 |
84 | {children}
85 |
86 |
87 |
88 |
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/site/components/emoji-button.tsx:
--------------------------------------------------------------------------------
1 | export const EmojiButton: React.FC<{
2 | onClick: () => void;
3 | emoji: string | JSX.Element;
4 | children?: React.ReactNode;
5 | }> = ({ onClick, children, emoji }) => (
6 |
10 |
16 | {emoji}
17 |
18 | {children}
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/site/components/sections/footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 |
4 | export function Footer({ noBadge }: { noBadge?: boolean }) {
5 | return (
6 |
7 |
21 |
22 |
© {new Date().getFullYear()} react-hot-toast
23 | {' · '}
24 |
25 | Built by
26 |
27 | Timo Lins
28 |
29 |
30 |
31 | {!noBadge && (
32 |
44 | )}
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/site/components/sections/splitbee-counter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 |
4 | export const useSplitbeeCount = (
5 | event: T,
6 | token: string
7 | ): number => {
8 | const [data, setData] = React.useState(0);
9 | const socket = React.useRef(null);
10 | React.useEffect(() => {
11 | if (typeof window !== undefined) {
12 | socket.current = new WebSocket('wss://realtime.react-hot-toast.com/');
13 | socket.current.onopen = (e) => {
14 | socket.current.send(
15 | JSON.stringify({
16 | type: 'subscribe',
17 | data: {
18 | token: token,
19 | events: [event],
20 | },
21 | })
22 | );
23 | };
24 | socket.current.onmessage = (e) => {
25 | const d = JSON.parse(e.data);
26 | setData(d.count);
27 | };
28 |
29 | return () => {};
30 | }
31 | }, []);
32 |
33 | return data;
34 | };
35 |
36 | export const SplitbeeCounter = () => {
37 | const count = useSplitbeeCount('Trigger Toast', 'NTV7AYBLEXW3');
38 |
39 | const letters = count.toString().split('');
40 |
41 | return (
42 |
43 |
44 | Toasts made on this website so far
45 |
46 |
49 | {letters.map((l, i) => (
50 |
57 | {l}
58 |
59 | ))}
60 |
61 |
72 |
73 | );
74 | };
75 |
--------------------------------------------------------------------------------
/site/components/sections/toast-example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import toast from 'react-hot-toast';
3 |
4 | import { EmojiButton } from '../emoji-button';
5 | import { Code } from '../code';
6 |
7 | const examples: Array<{
8 | title: string;
9 | action: () => void;
10 | emoji: string;
11 | snippet: string;
12 | }> = [
13 | {
14 | title: 'Success',
15 | emoji: '✅',
16 | snippet: "toast.success('Successfully toasted!')",
17 | action: () => {
18 | toast.success('Successfully toasted!');
19 | },
20 | },
21 | {
22 | title: 'Error',
23 | emoji: '❌',
24 | snippet: `toast.error("This didn't work.")`,
25 |
26 | action: () => {
27 | toast.error("This didn't work.");
28 | },
29 | },
30 | {
31 | title: 'Promise',
32 | emoji: '⏳',
33 | snippet: `toast.promise(
34 | saveSettings(settings),
35 | {
36 | loading: 'Saving...',
37 | success: Settings saved! ,
38 | error: Could not save. ,
39 | }
40 | );`,
41 | action: () => {
42 | const promise = new Promise((res, rej) => {
43 | setTimeout(Math.random() > 0.5 ? res : rej, 1000);
44 | });
45 |
46 | toast.promise(
47 | promise,
48 | {
49 | loading: 'Saving...',
50 | success: Settings saved! ,
51 | error: Could not save. ,
52 | },
53 | {
54 | style: {
55 | width: '200px',
56 | paddingRight: '10px',
57 | },
58 | }
59 | );
60 | },
61 | },
62 | {
63 | title: 'Multi Line',
64 | emoji: '↕️',
65 | snippet: `toast(
66 | "This toast is super big. I don't think anyone could eat it in one bite.\\n\\nIt's larger than you expected. You eat it but it does not seem to get smaller.",
67 | {
68 | duration: 6000,
69 | }
70 | );`,
71 | action: () => {
72 | toast(
73 | "This toast is super big. I don't think anyone could eat it in one bite.\n\n It's larger than you expected. You eat it but it does not seem to get smaller.",
74 | {
75 | duration: 6000,
76 | }
77 | );
78 | },
79 | },
80 | {
81 | title: 'Emoji',
82 | emoji: '👏',
83 | snippet: `toast('Good Job!', {
84 | icon: '👏',
85 | });`,
86 | action: () => {
87 | toast('Good Job!', {
88 | icon: '👏',
89 | });
90 | },
91 | },
92 | {
93 | title: 'Dark Mode',
94 | emoji: '🌚',
95 | snippet: `toast('Hello Darkness!',
96 | {
97 | icon: '👏',
98 | style: {
99 | borderRadius: '10px',
100 | background: '#333',
101 | color: '#fff',
102 | },
103 | }
104 | );`,
105 | action: () => {
106 | toast('Hello Darkness!', {
107 | icon: '👏',
108 |
109 | style: {
110 | borderRadius: '200px',
111 | background: '#333',
112 | color: '#fff',
113 | },
114 | });
115 | },
116 | },
117 | {
118 | title: 'JSX Content',
119 | emoji: '🔩',
120 | snippet: `toast((t) => (
121 |
122 | Custom and bold
123 | toast.dismiss(t.id)}>
124 | Dismiss
125 |
126 |
127 | ));`,
128 |
129 | action: () => {
130 | toast((t) => (
131 |
132 | Custom and bold
133 | toast.dismiss(t.id)}
136 | >
137 | Dismiss
138 |
139 |
140 | ));
141 | },
142 | },
143 | {
144 | title: 'Themed',
145 | emoji: '🎨',
146 | snippet: `toast.success('Look at my styles.', {
147 | style: {
148 | border: '1px solid #713200',
149 | padding: '16px',
150 | color: '#713200',
151 | },
152 | iconTheme: {
153 | primary: '#713200',
154 | secondary: '#FFFAEE',
155 | },
156 | });`,
157 |
158 | action: () => {
159 | toast.success('Look at my styles.', {
160 | style: {
161 | border: '1px solid #713200',
162 | padding: '16px',
163 | color: '#713200',
164 | },
165 | iconTheme: {
166 | primary: '#713200',
167 | secondary: '#FFFAEE',
168 | },
169 | });
170 | },
171 | },
172 | {
173 | title: 'Custom Position',
174 | emoji: '⬆️',
175 | snippet: `toast.success('Always at the bottom.', {
176 | position: "bottom-center"
177 | })`,
178 | action: () => {
179 | toast.success('Always at the bottom.', {
180 | position: 'bottom-center',
181 | duration: 10000,
182 | });
183 | },
184 | },
185 | {
186 | title: 'TailwindCSS',
187 | emoji: '️💨',
188 | snippet: `toast.custom((t) => (
189 |
194 |
195 |
196 |
197 |
202 |
203 |
204 |
205 | Emilia Gates
206 |
207 |
208 | Sure! 8:30pm works great!
209 |
210 |
211 |
212 |
213 |
214 | toast.dismiss(t.id)}
216 | className="w-full border border-transparent rounded-none rounded-r-lg p-4 flex items-center justify-center text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500"
217 | >
218 | Close
219 |
220 |
221 |
222 | ))`,
223 | action: () => {
224 | // toast.custom( );
225 |
226 | toast.custom(
227 | (t) => (
228 |
233 |
234 |
235 |
236 |
241 |
242 |
243 |
244 | Emilia Gates
245 |
246 |
247 | Sure! 8:30pm works great!
248 |
249 |
250 |
251 |
252 |
253 | toast.dismiss(t.id)}
255 | className="w-full border border-transparent rounded-none rounded-r-lg p-4 flex items-center justify-center text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500"
256 | >
257 | Close
258 |
259 |
260 |
261 | ),
262 | {
263 | duration: 10000,
264 | }
265 | );
266 | },
267 | },
268 | ];
269 |
270 | export const ToastExample = () => {
271 | const [snippet, setSnippet] = useState(examples[0].snippet);
272 | return (
273 |
274 |
275 |
276 | {examples.map((e) => (
277 | {
281 | if (e.snippet) {
282 | setSnippet(e.snippet);
283 | }
284 | (window as any).splitbee?.track('Trigger Toast', {
285 | example: e.title,
286 | });
287 | e.action();
288 | }}
289 | >
290 | {e.title}
291 |
292 | ))}
293 |
294 |
295 |
296 |
297 |
298 |
299 | );
300 | };
301 |
--------------------------------------------------------------------------------
/site/components/sections/toaster-example.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import toast, { ToastPosition } from 'react-hot-toast';
3 | import Arrow from '../../assets/arrow.svg';
4 | import { Code } from '../code';
5 |
6 | import { EmojiButton } from '../emoji-button';
7 |
8 | export const positions: Array = [
9 | 'top-left',
10 | 'top-center',
11 | 'top-right',
12 | 'bottom-left',
13 | 'bottom-center',
14 | 'bottom-right',
15 | ];
16 |
17 | export const ToasterExample: React.FC<{
18 | position: ToastPosition;
19 | onPosition: (pos: ToastPosition) => void;
20 | reverse: boolean;
21 | onReverse: (rev: boolean) => void;
22 | }> = ({ position, onPosition, reverse, onReverse }) => {
23 | const reverseIt = () => {
24 | setTimeout(() => {
25 | toast('Notification 1', {
26 | icon: '1️⃣',
27 | id: 'reverse-1',
28 | });
29 | }, 10);
30 |
31 | setTimeout(
32 | () =>
33 | toast('Notification 2', {
34 | icon: '2️⃣',
35 | id: 'reverse-2',
36 | }),
37 | 250
38 | );
39 | setTimeout(
40 | () =>
41 | toast('Notification 3', {
42 | icon: '3️⃣',
43 | id: 'reverse-3',
44 | }),
45 | 500
46 | );
47 | setTimeout(
48 | () =>
49 | toast('Notification 4', {
50 | icon: '4️⃣',
51 | id: 'reverse-4',
52 | }),
53 | 750
54 | );
55 | (window as any).splitbee?.track('Change Order', {
56 | reverseOrder: !reverse,
57 | });
58 | onReverse(!reverse);
59 | };
60 |
61 | const renderPosition = (p: ToastPosition) => (
62 | {
72 | toast.success(
73 |
74 | Position set to {p}
75 | ,
76 | {
77 | id: 'position',
78 | }
79 | );
80 |
81 | (window as any).splitbee?.track('Change Position', {
82 | position: p,
83 | });
84 |
85 | (window as any).splitbee?.track('Trigger Toast', {
86 | example: 'position',
87 | });
88 |
89 | onPosition(p);
90 | }}
91 | >
92 | {p}
93 |
94 | );
95 |
96 | return (
97 |
98 |
`}
103 | />
104 |
105 | {positions.map((p) => renderPosition(p))}
106 |
107 |
108 |
118 | }
119 | onClick={reverseIt}
120 | >
121 | Toggle Direction
122 |
123 |
124 |
125 | );
126 | };
127 |
--------------------------------------------------------------------------------
/site/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/site/next.config.mjs:
--------------------------------------------------------------------------------
1 | import rehypeSlug from 'rehype-slug';
2 | import remarkGfm from 'remark-gfm';
3 | import nextMdx from '@next/mdx';
4 | import withPlugins from 'next-compose-plugins';
5 |
6 | const withMDX = nextMdx({
7 | extension: /.mdx?$/,
8 | options: {
9 | rehypePlugins: [rehypeSlug],
10 | remarkPlugins: [remarkGfm],
11 | providerImportSource: '@mdx-js/react',
12 | },
13 | });
14 |
15 | const withSvgr = (nextConfig = {}, nextComposePlugins = {}) => {
16 | return Object.assign({}, nextConfig, {
17 | webpack(config, options) {
18 | config.module.rules.push({
19 | test: /.svg$/,
20 | use: ['@svgr/webpack'],
21 | });
22 |
23 | if (typeof nextConfig.webpack === 'function') {
24 | return nextConfig.webpack(config, options);
25 | }
26 |
27 | return config;
28 | },
29 | });
30 | };
31 |
32 | export default withPlugins(
33 | [
34 | withMDX({
35 | pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
36 | }),
37 | withSvgr,
38 | ],
39 | {
40 | async rewrites() {
41 | return [
42 | {
43 | source: '/bee.js',
44 | destination: 'https://cdn.splitbee.io/sb.js',
45 | },
46 | {
47 | source: '/_hive/:slug',
48 | destination: 'https://hive.splitbee.io/:slug',
49 | },
50 | ];
51 | },
52 | }
53 | );
54 |
--------------------------------------------------------------------------------
/site/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "site",
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "@mdx-js/loader": "^2.1.2",
10 | "@mdx-js/react": "^2.1.2",
11 | "@next/mdx": "^12.2.1",
12 | "@svgr/webpack": "^6.2.1",
13 | "@types/prismjs": "^1.26.0",
14 | "clsx": "^1.1.1",
15 | "next": "^12.2.1",
16 | "next-seo": "^5.4.0",
17 | "postcss": "^8.4.14",
18 | "prism-react-renderer": "^1.3.5",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-hot-toast": "link:../",
22 | "rehype-slug": "^5.0.1"
23 | },
24 | "devDependencies": {
25 | "@tailwindcss/typography": "^0.5.3",
26 | "@types/node": "^18.0.0",
27 | "@types/react": "^18.0.14",
28 | "@types/react-dom": "^18.0.5",
29 | "autoprefixer": "^10.4.7",
30 | "next-compose-plugins": "^2.2.1",
31 | "remark-gfm": "^3.0.1",
32 | "tailwindcss": "^3.1.5",
33 | "typescript": "^4.7.4"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/site/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/tailwind-utils.css';
2 | import '../styles/main.css';
3 | import * as React from 'react';
4 | import Link from 'next/link';
5 | import Head from 'next/head';
6 |
7 | import { MDXProvider } from '@mdx-js/react';
8 | import { Code } from '../components/code';
9 |
10 | const components = {
11 | a: (props) => (
12 |
13 |
14 |
15 | ),
16 | code: (props) =>
17 | props.className ? (
18 |
19 | ) : (
20 |
24 | ),
25 | };
26 |
27 | function MyApp({ Component, pageProps }) {
28 | return (
29 | <>
30 |
31 | {process.browser && (
32 |
33 | )}
34 |
35 |
36 |
37 |
38 |
39 | >
40 | );
41 | }
42 |
43 | export default MyApp;
44 |
--------------------------------------------------------------------------------
/site/pages/docs/index.mdx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/docs-layout';
2 | import toast from 'react-hot-toast';
3 |
4 | export const meta = {
5 | title: 'Documentation',
6 | };
7 |
8 | export default ({ children }) => {children} ;
9 |
10 | # Getting Started
11 |
12 | Add beautiful notifications to your React app with [react-hot-toast](https://github.com/timolins/react-hot-toast).
13 |
14 | ### Install with Yarn
15 |
16 | ```sh
17 | yarn add react-hot-toast
18 | ```
19 |
20 | ### Install with NPM
21 |
22 | ```sh
23 | npm install react-hot-toast
24 | ```
25 |
26 | ## Basic usage
27 |
28 | ```jsx
29 | import toast, { Toaster } from 'react-hot-toast';
30 |
31 | const notify = () => toast('Here is your toast.');
32 |
33 | const App = () => {
34 | return (
35 |
36 | Make me a toast
37 |
38 |
39 | );
40 | };
41 | ```
42 |
--------------------------------------------------------------------------------
/site/pages/docs/styling.mdx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/docs-layout';
2 | import toast from 'react-hot-toast';
3 |
4 | export const meta = {
5 | title: 'Styling',
6 | };
7 |
8 | export default ({ children }) => {children} ;
9 |
10 | # Styling
11 |
12 | You can style your notifications globally with the `toastOptions` inside the Toaster component, or for each notification manually.
13 |
14 | ### Set default for all toasts
15 |
16 | ```jsx
17 |
27 | ```
28 |
29 | ### Set default for specific types
30 |
31 | ```jsx
32 |
46 | ```
47 |
48 | ### Style per toast
49 |
50 | ```jsx
51 | toast('I have a border.', {
52 | style: {
53 | border: '1px solid black',
54 | },
55 | });
56 | ```
57 |
58 | ## Change the offset
59 |
60 | If you want to change the offset of your notifications, you can adapt the absolute position in `containerStyle`.
61 |
62 | ```jsx
63 |
71 | ```
72 |
73 | ## Change position of the toaster
74 |
75 | By default, the toaster is position fixed in the window. If you want to place it somewhere else, you can ovewrite the position with `containerStyle`.
76 |
77 | ```jsx
78 |
83 | ```
84 |
85 | ## Change offset between toasts
86 |
87 | If you want to change the offset between notifications change the gutter.
88 |
89 | ```jsx
90 |
91 | ```
92 |
93 | ## Change icon color
94 |
95 | All icon colors can be changed by supplying a `iconTheme` with a `primary` & `secondary` color.
96 |
97 | ```jsx
98 |
108 | ```
109 |
110 | ## Change enter and exit animations
111 |
112 | In this example, we provide a render function with the default ` `. We overwrite the animation style based on the current state.
113 |
114 | ```jsx
115 | import { Toaster, ToastBar } from 'react-hot-toast';
116 |
117 |
118 | {(t) => (
119 |
126 | )}
127 | ;
128 | ```
129 |
--------------------------------------------------------------------------------
/site/pages/docs/toast-bar.mdx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/docs-layout';
2 | import toast from 'react-hot-toast';
3 |
4 | export const meta = {
5 | title: ' API',
6 | };
7 |
8 | export default ({ children }) => {children} ;
9 |
10 | # ` ` API
11 |
12 | This is the **default toast component** rendered by the [Toaster](/docs/toaster). You can use this component in a [Toaster](/docs/toaster) with a custom render function to overwrite its defaults.
13 |
14 | ## Available options
15 |
16 | ```jsx
17 |
22 | ```
23 |
24 | ## Add custom content
25 |
26 | You can add a **render function to the ToastBar to modify its content**. An object containing The `icon` as well as the `message` are passed into the function.
27 |
28 | ### Add a dismiss button
29 |
30 | In this example we add a basic dismiss button to all toasts, except if the loading one.
31 |
32 | ```jsx
33 | import { toast, Toaster, ToastBar } from 'react-hot-toast';
34 |
35 |
36 | {(t) => (
37 |
38 | {({ icon, message }) => (
39 | <>
40 | {icon}
41 | {message}
42 | {t.type !== 'loading' && (
43 | toast.dismiss(t.id)}>X
44 | )}
45 | >
46 | )}
47 |
48 | )}
49 | ;
50 | ```
51 |
--------------------------------------------------------------------------------
/site/pages/docs/toast.mdx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/docs-layout';
2 | import toast from 'react-hot-toast';
3 |
4 | export const meta = {
5 | title: 'toast() API',
6 | };
7 |
8 | # `toast()` API
9 |
10 | Call it to create a toast from anywhere, even outside React. Make sure you add the [` `](/docs/toaster) component to your app first.
11 |
12 | ## Available toast options
13 |
14 | You can provide `ToastOptions` as the second argument. They will overwrite all options received from [` `](/docs/toaster).
15 |
16 | ```js
17 | toast('Hello World', {
18 | duration: 4000,
19 | position: 'top-center',
20 |
21 | // Styling
22 | style: {},
23 | className: '',
24 |
25 | // Custom Icon
26 | icon: '👏',
27 |
28 | // Change colors of success/error/loading icon
29 | iconTheme: {
30 | primary: '#000',
31 | secondary: '#fff',
32 | },
33 |
34 | // Aria
35 | ariaProps: {
36 | role: 'status',
37 | 'aria-live': 'polite',
38 | },
39 | });
40 | ```
41 |
42 | ## Creating a toast
43 |
44 | ### Blank
45 |
46 | ```js
47 | toast('Hello World');
48 | ```
49 |
50 | The most basic variant. It does not have an icon by default, but you can provide one via the options. If you don't want any default styles, use `toast.custom()` instead.
51 |
52 | ### Success
53 |
54 | ```js
55 | toast.success('Successfully created!');
56 | ```
57 |
58 | Creates a notification with an animated checkmark. It can be themed with the `iconTheme` option.
59 |
60 | ### Error
61 |
62 | ```js
63 | toast.error('This is an error!');
64 | ```
65 |
66 | Creates a notification with an animated error icon. It can be themed with the `iconTheme` option.
67 |
68 | ### Custom (JSX)
69 |
70 | ```js
71 | toast.custom(Hello World
);
72 | ```
73 |
74 | Creates a custom notification with JSX without default styles.
75 |
76 | ### Loading
77 |
78 | ```js
79 | toast.loading('Waiting...');
80 | ```
81 |
82 | This will create a loading notification. Most likely, you want to update it afterwards. For a friendly alternative, check out `toast.promise()`, which takes care of that automatically.
83 |
84 | ### Promise
85 |
86 | This shorthand is useful for mapping a promise to a toast. It will update automatically when the promise resolves or fails.
87 |
88 | #### Simple Usage
89 |
90 | ```js
91 | const myPromise = fetchData();
92 |
93 | toast.promise(myPromise, {
94 | loading: 'Loading',
95 | success: 'Got the data',
96 | error: 'Error when fetching',
97 | });
98 | ```
99 |
100 | It's recommend to add min-width to your `toast.promise()` calls to **prevent jumps** from different message lengths.
101 |
102 | #### Advanced
103 |
104 | You can provide a function to the success/error messages to incorporate the result/error of the promise. The third argument are `toastOptions` similiar to [` `](/docs/toaster)
105 |
106 | ```js
107 | toast.promise(
108 | myPromise,
109 | {
110 | loading: 'Loading',
111 | success: (data) => `Successfully saved ${data.name}`,
112 | error: (err) => `This just happened: ${err.toString()}`,
113 | },
114 | {
115 | style: {
116 | minWidth: '250px',
117 | },
118 | success: {
119 | duration: 5000,
120 | icon: '🔥',
121 | },
122 | }
123 | );
124 | ```
125 |
126 | ## Default durations
127 |
128 | Every type has its own duration. You can overwrite them `duration` with the toast options. This can be done per toast options or globally by the [` `](/docs/toaster).
129 |
130 | | type | duration |
131 | | --------- | -------- |
132 | | `blank` | 4000 |
133 | | `error` | 4000 |
134 | | `success` | 2000 |
135 | | `custom` | 4000 |
136 | | `loading` | Infinity |
137 |
138 | ### Dismiss toast programmatically
139 |
140 | You can manually dismiss a notification with `toast.dismiss`. Be aware that it triggers the exit animation and does not remove the Toast instantly. Toasts will auto-remove after 1 second by default.
141 |
142 | #### Dismiss a single toast
143 |
144 | ```js
145 | const toastId = toast.loading('Loading...');
146 |
147 | // ...
148 |
149 | toast.dismiss(toastId);
150 | ```
151 |
152 | You can dismiss all toasts at once, by leaving out the `toastId`.
153 |
154 | #### Dismiss all toasts at once
155 |
156 | ```js
157 | toast.dismiss();
158 | ```
159 |
160 | To remove toasts instantly without any animations, use `toast.remove`.
161 |
162 | #### Remove toasts instantly
163 |
164 | ```js
165 | toast.remove(toastId);
166 |
167 | // or
168 |
169 | toast.remove();
170 | ```
171 |
172 | ### Update an existing toast
173 |
174 | Each toast call returns a unique id. Use in the toast options to update the existing toast.
175 |
176 | ```js
177 | const toastId = toast.loading('Loading...');
178 |
179 | // ...
180 |
181 | toast.success('This worked', {
182 | id: toastId,
183 | });
184 | ```
185 |
186 | ### Prevent duplicate toasts
187 |
188 | To prevent duplicates of the same kind, you can provide a unique permanent id.
189 |
190 | ```js
191 | toast.success('Copied to clipboard!', {
192 | id: 'clipboard',
193 | });
194 | ```
195 |
196 | ### Render JSX custom content
197 |
198 | You can provide a React component instead of text. If you don't want any default styles use `toast.custom()` instead.
199 |
200 | ```jsx
201 | toast(
202 |
203 | Custom and bold
204 | ,
205 | {
206 | icon: ,
207 | }
208 | );
209 | ```
210 |
211 | You can also supply a function that receives the `Toast` as an argument, giving you access to all properties. This allows you to access the toast id, which can be used to add a dismiss button.
212 |
213 | ```jsx
214 | toast(
215 | (t) => (
216 |
217 | Custom and bold
218 | toast.dismiss(t.id)}>Dismiss
219 |
220 | ),
221 | {
222 | icon: ,
223 | }
224 | );
225 | ```
226 |
227 | export default ({ children }) => {children} ;
228 |
--------------------------------------------------------------------------------
/site/pages/docs/toaster.mdx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/docs-layout';
2 | import toast from 'react-hot-toast';
3 |
4 | export const meta = {
5 | title: ' API',
6 | };
7 |
8 | export default ({ children }) => {children} ;
9 |
10 | # ` ` API
11 |
12 | This component will render all toasts. Alternatively you can create own renderer with the headless [`useToaster()`](/docs/use-toaster) hook.
13 |
14 | ## Available options
15 |
16 | ```jsx
17 |
42 | ```
43 |
44 | ### `position` Prop
45 |
46 | You can change the position of all toasts by modifying supplying `positon` prop.
47 |
48 | | Positions | | |
49 | | ----------- | ------------- | ------------ |
50 | | top-left | top-center | top-right |
51 | | bottom-left | bottom-center | bottom-right |
52 |
53 | ### `reverseOrder` Prop
54 |
55 | Toasts spawn at top by default. Set to `true` if you want new toasts at the end.
56 |
57 | ### `containerClassName` Prop
58 |
59 | Add a custom CSS class name to toaster div. Defaults to `undefined`.
60 |
61 | ### `containerStyle` Prop
62 |
63 | Customize the style of toaster div. This can be used to change the offset of all toasts
64 |
65 | ### `gutter` Prop
66 |
67 | Changes the gap between each toast. Defaults to `8`.
68 |
69 | ### `toastOptions` Prop
70 |
71 | These will act as default options for all toasts. See [`toast()`](/docs/toast) for all available options.
72 |
73 | #### Type specific options
74 |
75 | You can change the defaults for a specific type by adding, `success: {}`, `error: {}`, `loading: {}` or `custom: {}`.
76 |
77 | ## Using a custom render function
78 |
79 | You can provide your **own render function** to the Toaster by passing it as children. It will be called for each [Toast](https://github.com/timolins/react-hot-toast/blob/main/src/core/types.ts#L34) allowing you to render any component based on the toast state.
80 |
81 | ### Minimal example
82 |
83 | ```jsx
84 | import { Toaster, resolveValue } from 'react-hot-toast';
85 |
86 | // In your app
87 |
88 | {(t) => (
89 |
92 | {resolveValue(t.message, t)}
93 |
94 | )}
95 | ;
96 | ```
97 |
98 | `resolveValue()` is needed to resolve all message types: Text, JSX or a function that resolves to JSX.
99 |
100 | ### Adapting the default [` `](/docs/toast-bar)
101 |
102 | You can use this API to modify the default ToastBar as well. In this example we overwrite the animation style based on the current state.
103 |
104 | ```jsx
105 | import { Toaster, ToastBar } from 'react-hot-toast';
106 |
107 |
108 | {(t) => (
109 |
116 | )}
117 | ;
118 | ```
119 |
120 | Check out the [` `](/docs/toast-bar) docs for more options.
121 |
--------------------------------------------------------------------------------
/site/pages/docs/use-toaster-store.mdx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/docs-layout';
2 | import toast from 'react-hot-toast';
3 |
4 | export const meta = {
5 | title: 'useToasterStore() API',
6 | };
7 |
8 | export default ({ children }) => {children} ;
9 |
10 | # `useToasterStore()` API
11 |
12 | This hook gives you access to the internal toaster state. This is the right choice if you need access to the data without wanting to roll your own toaster.
13 |
14 | In comparison to [`useToaster()`](/docs/use-toaster) it does not handle pausing or provide handlers for creating your own notification system.
15 |
16 | ```jsx
17 | import { useToasterStore } from 'react-hot-toast';
18 |
19 | const { toasts, pausedAt } = useToasterStore();
20 | ```
21 |
--------------------------------------------------------------------------------
/site/pages/docs/use-toaster.mdx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/docs-layout';
2 | import toast from 'react-hot-toast';
3 |
4 | export const meta = {
5 | title: 'useToaster() API',
6 | };
7 |
8 | export default ({ children }) => {children} ;
9 |
10 | # `useToaster()` API
11 |
12 | The `useToaster()` hook provides you a **headless system that will manage the notification state** for you. This makes building your own notification system much easier.
13 |
14 | It solves the following problems for you:
15 |
16 | - Built-in dispatch system with [`toast()`](/docs/toast)
17 | - Handlers to pause toasts on hover
18 | - Automatically remove expired toasts
19 | - Support for unmount animations. Removal is delayed by 1s, but sets `visible` on the toast to `false`.
20 |
21 | ### Importing from headless
22 |
23 | You can import only the core of the library with `react-hot-toast/headless`. It won't include any styles, dependencies or custom components.
24 |
25 | ```jsx
26 | import { useToaster } from 'react-hot-toast/headless';
27 | ```
28 |
29 | Be aware: [react-hot-toast 2.0](/docs/version-2) adds support for **custom render functions**, an easier method to render custom notification components.
30 |
31 | It's recommended to only have one ` ` or `useToaster()` in your app at a time. If you need the current state without the handlers, you should use [`useToasterStore()`](/docs/use-toaster-store) instead.
32 |
33 | ## Usage with React Native
34 |
35 | Headless mode is perfectly suited to add notifications to your React Native app. You can check out [this example]().
36 |
37 | ## Examples
38 |
39 | ### Basic Example
40 |
41 | ```jsx
42 | import toast, { useToaster } from 'react-hot-toast/headless';
43 |
44 | const Notifications = () => {
45 | const { toasts, handlers } = useToaster();
46 | const { startPause, endPause } = handlers;
47 |
48 | return (
49 |
50 | {toasts
51 | .filter((toast) => toast.visible)
52 | .map((toast) => (
53 |
54 | {toast.message}
55 |
56 | ))}
57 |
58 | );
59 | };
60 |
61 | // Create toasts anywhere
62 | toast('Hello World');
63 | ```
64 |
65 | ### Animated Example
66 |
67 | Instead of mapping over `visibleToasts` we'll use `toasts`, which includes all hidden toasts. We animate them based on `toast.visible`. Toasts will be removed from 1 second after being dismissed, which give us enough time to animate.
68 |
69 | You can play with the demo on [CodeSandbox](https://codesandbox.io/s/react-hot-toast-usetoaster-headless-example-zw7op?file=/src/App.js).
70 |
71 | ```jsx
72 | import { useToaster } from 'react-hot-toast/headless';
73 |
74 | const Notifications = () => {
75 | const { toasts, handlers } = useToaster();
76 | const { startPause, endPause, calculateOffset, updateHeight } = handlers;
77 |
78 | return (
79 |
88 | {toasts.map((toast) => {
89 | const offset = calculateOffset(toast, {
90 | reverseOrder: false,
91 | gutter: 8,
92 | });
93 |
94 | const ref = (el) => {
95 | if (el && !toast.height) {
96 | const height = el.getBoundingClientRect().height;
97 | updateHeight(toast.id, height);
98 | }
99 | };
100 | return (
101 |
114 | {toast.message}
115 |
116 | );
117 | })}
118 |
119 | );
120 | };
121 | ```
122 |
--------------------------------------------------------------------------------
/site/pages/docs/version-2.mdx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/docs-layout';
2 | import toast from 'react-hot-toast';
3 |
4 | export const meta = {
5 | title: 'react-hot-toast 2.0 changes',
6 | };
7 |
8 | export default ({ children }) => {children} ;
9 |
10 | # What's new in react-hot-toast 2.0
11 |
12 | This release is all about **flexibility**. It allows you to create the notification system of your dreams, even simpler. Before we dig deeper into the new APIs, check out what's included in this release:
13 |
14 |
32 |
33 | As well as a many [other improvements and fixes](#changelog).
34 |
35 | ## Introducing `toast.custom()`
36 |
37 | This new function allows you to **render any React component** on the fly. Pass in JSX, and it will add it to the notification stack. There are no default styles applied, giving you complete control.
38 |
39 | This API makes it super easy to add [Tailwind UI Notifications](https://tailwindui.com/components/application-ui/overlays/notifications) to your React app.
40 |
41 | ```jsx
42 | // Minimal Example
43 | toast.custom(Minimal Example
);
44 |
45 | // Tailwind Example
46 | toast.custom((t) => (
47 |
52 | Hello TailwindCSS! 👋
53 |
54 | ));
55 | ```
56 |
57 |
58 |
60 | toast.custom((t) => (
61 | Hello from TailwindCSS! 👋
66 | ))
67 | }
68 | className="bg-toast-800 text-toast-100 whitespace-nowrap py-1 px-3 shadow-md rounded-lg absolute mt-[-4.5rem] -ml-2 transform -translate-x-full left-full">Run Example
69 |
70 |
71 | In the example above, we pass in a **function that returns JSX**. This allows us to access the current toast state and toggle between the enter and exit animation.
72 |
73 | Instead of CSS keyframe animations, you can use TailwindCSS classes by wrapping it in the [Transition](https://headlessui.dev/react/transition) component from [@headlessui/react](https://headlessui.dev/).
74 |
75 | ## Better accessibility
76 |
77 | The prefers reduced motion is now respected by default. If react-hot-toast detects this setting, it will use fade transitions instead of sliding.
78 |
79 | ## Smoother exit animation
80 |
81 | The exit animation is now less hectic when you have multiple toasts stacked.
82 |
83 | ## Per toast positioning
84 |
85 | From now on, it's possible to have toasts at multiple positions at once. Just add the `position` you want as option when dispatching a toast.
86 |
87 | ```jsx
88 | toast.success('Always at the bottom', {
89 | position: 'bottom-center',
90 | });
91 | ```
92 |
93 |
94 | {
97 | toast.success('Always at the bottom', {
98 | position: 'bottom-center',
99 | });
100 | }}>Run Example
101 |
102 |
103 | ## Relative positioning
104 |
105 | You can now overwrite the default position of the toaster and place it anywhere you want.
106 |
107 | ```jsx
108 |
109 | ```
110 |
111 | ## Simpler offset styling
112 |
113 | There is now a `gutter` option to control the gap between toasts.
114 |
115 | ```jsx
116 |
117 | ```
118 |
119 | The offset is now controlled by the Toaster and can be changed by overwriting the `top`, `right`, `bottom` and `left` styles.
120 |
121 | ```jsx
122 |
123 | ```
124 |
125 | ## Custom Renderer API
126 |
127 | You can now use the [` `](/docs/toaster#using-a-custom-render-function) to render your own components. Pass in a function that receives a [Toast](https://github.com/timolins/react-hot-toast/blob/main/src/core/types.ts#L34) as the first argument, allowing you to render whatever you please.
128 |
129 | This is a great alternative if you are using [`useToaster()`](/docs/use-toaster) to render create custom notfications.
130 |
131 | This API allows us to dynamically react to to current state our toasts. This can be used to **change the default animations**, add **a custom dismiss button** or render a custom notification, like [TailwindUI Notifications](https://tailwindui.com/components/application-ui/overlays/notifications).
132 |
133 | ```jsx
134 | import { toast, Toaster, ToastBar } from 'react-hot-toast';
135 |
136 | const CustomToaster = () => (
137 |
138 | {(t) => (
139 |
140 | {({ icon, message }) => (
141 | <>
142 | {icon}
143 | {message}
144 | {t.type !== 'loading' && (
145 | toast.dismiss(t.id)}>X
146 | )}
147 | >
148 | )}
149 |
150 | )}
151 |
152 | );
153 | ```
154 |
155 | This example adapts the [ToastBar](/docs/toast-bar) with its new render function API. You can read more about the APIs in the [Toaster](/docs/toaster) & [ToastBar](/docs/toast-bar) docs.
156 |
157 | ## Available now
158 |
159 | Get react-hot-toast 2.0 while it's hot. Upgrading from 1.0.0 should be seamless for most users.
160 |
161 | ```sh
162 | yarn add react-hot-toast
163 | ```
164 |
165 | ## The future and beyond
166 |
167 | React Hot Toast got a lot more flexible with this version, laying the **foundation for future releases**. Thanks to everyone who helped out; much appreciated!
168 |
169 | In the next releases, I plan to add the [most requested feature](https://github.com/timolins/react-hot-toast/issues/7): a dismiss button. As well as support for [custom toast types](https://github.com/timolins/react-hot-toast/issues/23).
170 |
171 | ---
172 |
173 | ## Changelog
174 |
175 | ### New
176 |
177 | - Easier Customization
178 | - Create your own toast renderer (without useToaster)
179 | - Support for custom render function in Toaster
180 | - Support for custom render function in ToastBar
181 | - `toast.custom()` - Render custom one-off toasts. No default styling will be applied.
182 | - Per toast positioning
183 | - New exit animation
184 | - Change the gutter between toasts with ` `
185 | - Support for relative positioning
186 | - Respect reduce motion OS setting
187 | - Create persistent toasts with `duration: Infinity`
188 |
189 | ### Breaking Changes
190 |
191 | - Use the `top`, `right`, `bottom`, `left` to in `containerStyle` to change the offset, instead of margin
192 | - Loading toasts no longer disappear after 30 seconds
193 | - `role` & `ariaLive` got moved into `ariaProps`
194 | - `useToaster()` no longer exposes `visibleToasts`
195 | - No longer expose `dispatch`
196 |
--------------------------------------------------------------------------------
/site/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { NextSeo } from 'next-seo';
2 | import toast, {
3 | Toaster,
4 | useToasterStore,
5 | ToastPosition,
6 | } from 'react-hot-toast';
7 | import React, { useState } from 'react';
8 | import clsx from 'clsx';
9 | import Link from 'next/link';
10 |
11 | import Logo from '../assets/logo.svg';
12 | import Butter1 from '../assets/butter-1.svg';
13 | import Butter2 from '../assets/butter-2.svg';
14 | import GitHub from '../assets/github.svg';
15 | import Checkmark from '../assets/checkmark.svg';
16 | import { ToastExample } from '../components/sections/toast-example';
17 | import { Footer } from '../components/sections/footer';
18 | import { ToasterExample } from '../components/sections/toaster-example';
19 | import { SplitbeeCounter } from '../components/sections/splitbee-counter';
20 |
21 | import packageInfo from '../../package.json';
22 | const version = packageInfo.version;
23 |
24 | const Feature: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
25 |
26 |
27 | {children}
28 |
29 | );
30 |
31 | const Step: React.FC<{
32 | count: number;
33 | title: string;
34 | subTitle: string;
35 | code: JSX.Element;
36 | }> = (props) => (
37 |
38 |
39 | {props.count}
40 |
41 |
{props.title}
42 |
{props.subTitle}
43 |
44 | {props.code}
45 |
46 |
47 | );
48 |
49 | const Steps = () => (
50 |
51 |
57 | yarn add {' '}
58 | react-hot-toast
59 |
60 | }
61 | >
62 |
68 | {''}
69 | {' '}
70 | {'
'}
71 | >
72 | }
73 | >
74 |
80 | {'toast'}
81 | {'("Hello World")'}
82 | >
83 | }
84 | >
85 |
86 | );
87 |
88 | const Features = () => (
89 |
90 | Hot by default
91 | Easy to use
92 | Accessible
93 | Emoji Support
94 | Customizable
95 | Promise API
96 | Lightweight
97 | Pause on hover
98 | Headless Hooks
99 |
100 | );
101 |
102 | export default function Home() {
103 | const [position, setPosition] = useState('top-center');
104 | const [reverse, setReverse] = useState(false);
105 | const { toasts: allToasts } = useToasterStore();
106 |
107 | const shouldFade =
108 | allToasts.filter((t) => t.visible).length && position.includes('top');
109 | return (
110 |
111 |
124 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 | );
250 | }
251 |
--------------------------------------------------------------------------------
/site/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['tailwindcss'],
3 | };
4 |
--------------------------------------------------------------------------------
/site/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogSoulDev/react-hot-toast/ee483cc02dcac4359a5081a99aa006fac54cfafc/site/public/favicon.png
--------------------------------------------------------------------------------
/site/public/social-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogSoulDev/react-hot-toast/ee483cc02dcac4359a5081a99aa006fac54cfafc/site/public/social-image.png
--------------------------------------------------------------------------------
/site/styles/main.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 |
4 | html,
5 | body,
6 | body > div {
7 | @apply flex flex-col justify-between flex-1 min-h-full text-toast-900;
8 | }
9 |
10 | button,
11 | a {
12 | @apply outline-none focus:outline-none ring-offset-green-900 ring-toast-900 ring-opacity-30 transition-shadow duration-100 focus:ring-4;
13 | }
14 |
--------------------------------------------------------------------------------
/site/styles/prism-theme.css:
--------------------------------------------------------------------------------
1 | /* Generated with http://k88hudson.github.io/syntax-highlighting-theme-generator/www */
2 | /* http://k88hudson.github.io/react-markdocs */
3 | /**
4 | * @author k88hudson
5 | *
6 | * Based on prism.js default theme for JavaScript, CSS and HTML
7 | * Based on dabblet (http://dabblet.com)
8 | * @author Lea Verou
9 | */
10 | /*********************************************************
11 | * General
12 | */
13 | pre[class*="language-"],
14 | code[class*="language-"] {
15 | color: #a1724e;
16 | font-size: 13px;
17 | text-shadow: none;
18 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
19 | direction: ltr;
20 | text-align: left;
21 | white-space: pre;
22 | word-spacing: normal;
23 | word-break: normal;
24 | line-height: 1.5;
25 | -moz-tab-size: 4;
26 | -o-tab-size: 4;
27 | tab-size: 4;
28 | -webkit-hyphens: none;
29 | -moz-hyphens: none;
30 | -ms-hyphens: none;
31 | hyphens: none;
32 | }
33 | pre[class*="language-"]::selection,
34 | code[class*="language-"]::selection,
35 | pre[class*="language-"]::mozselection,
36 | code[class*="language-"]::mozselection {
37 | text-shadow: none;
38 | background: #ffff00;
39 | }
40 | @media print {
41 | pre[class*="language-"],
42 | code[class*="language-"] {
43 | text-shadow: none;
44 | }
45 | }
46 | pre[class*="language-"] {
47 | padding: 1em;
48 | margin: .5em 0;
49 | overflow: auto;
50 | background: #faf0de;
51 | }
52 | :not(pre) > code[class*="language-"] {
53 | padding: .1em .3em;
54 | border-radius: .3em;
55 | color: #db4c69;
56 | background: #f9f2f4;
57 | }
58 | /*********************************************************
59 | * Tokens
60 | */
61 | .namespace {
62 | opacity: .7;
63 | }
64 | .token.comment,
65 | .token.prolog,
66 | .token.doctype,
67 | .token.cdata {
68 | color: #dddddd;
69 | }
70 | .token.punctuation {
71 | color: #999999;
72 | }
73 | .token.property,
74 | .token.tag,
75 | .token.boolean,
76 | .token.number,
77 | .token.constant,
78 | .token.symbol,
79 | .token.deleted {
80 | color: #327015;
81 | }
82 | .token.selector,
83 | .token.attr-name,
84 | .token.string,
85 | .token.char,
86 | .token.builtin,
87 | .token.inserted {
88 | color: #54b427;
89 | }
90 | .token.operator,
91 | .token.entity,
92 | .token.url,
93 | .language-css .token.string,
94 | .style .token.string {
95 | color: #8c4913;
96 | background: transparent;
97 | }
98 | .token.atrule,
99 | .token.attr-value,
100 | .token.keyword {
101 | color: #482307;
102 | }
103 | .token.function {
104 | color: #381b05;
105 | }
106 | .token.regex,
107 | .token.important,
108 | .token.variable {
109 | color: #ffedc0;
110 | }
111 | .token.important,
112 | .token.bold {
113 | font-weight: bold;
114 | }
115 | .token.italic {
116 | font-style: italic;
117 | }
118 | .token.entity {
119 | cursor: help;
120 | }
121 | /*********************************************************
122 | * Line highlighting
123 | */
124 | pre[data-line] {
125 | position: relative;
126 | }
127 | pre[class*="language-"] > code[class*="language-"] {
128 | position: relative;
129 | z-index: 1;
130 | }
131 | .line-highlight {
132 | position: absolute;
133 | left: 0;
134 | right: 0;
135 | padding: inherit 0;
136 | margin-top: 1em;
137 | background: #ffe092;
138 | box-shadow: inset 5px 0 0 #482307;
139 | z-index: 0;
140 | pointer-events: none;
141 | line-height: inherit;
142 | white-space: pre;
143 | }
144 |
--------------------------------------------------------------------------------
/site/styles/tailwind-utils.css:
--------------------------------------------------------------------------------
1 | @tailwind utilities;
2 |
--------------------------------------------------------------------------------
/site/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | content: [
4 | './pages/*.tsx',
5 | './pages/**/*.tsx',
6 | './pages/*.mdx',
7 | './pages/**/*.mdx',
8 | './components/*.tsx',
9 | './components/**/*.tsx',
10 | ],
11 | theme: {
12 | extend: {
13 | boxShadow: {
14 | 'small-button': '0px 1px 2px rgba(126, 56, 0, 0.5)',
15 | button:
16 | '-6px 8px 10px rgba(81, 41, 10, 0.1), 0px 2px 2px rgba(81, 41, 10, 0.2)',
17 | 'button-active':
18 | '-1px 2px 5px rgba(81, 41, 10, 0.15), 0px 1px 1px rgba(81, 41, 10, 0.15)',
19 | },
20 | animation: {
21 | enter: 'enter 200ms ease-out',
22 | 'slide-in': 'slide-in 1.2s cubic-bezier(.41,.73,.51,1.02)',
23 | leave: 'leave 150ms ease-in forwards',
24 | },
25 | keyframes: {
26 | enter: {
27 | '0%': { transform: 'scale(0.9)', opacity: 0 },
28 | '100%': { transform: 'scale(1)', opacity: 1 },
29 | },
30 | leave: {
31 | '0%': { transform: 'scale(1)', opacity: 1 },
32 | '100%': { transform: 'scale(0.9)', opacity: 0 },
33 | },
34 | 'slide-in': {
35 | '0%': { transform: 'translateY(-100%)' },
36 | '100%': { transform: 'translateY(0)' },
37 | },
38 | },
39 |
40 | colors: {
41 | toast: {
42 | '50': '#FFF6DF',
43 | '100': '#fdf7f1',
44 | '200': '#F8EEDB',
45 | '300': '#ebbf99',
46 | '400': '#dea373',
47 | '500': '#ce864f',
48 | '600': '#A1724E',
49 | '700': '#8c501c',
50 | '800': '#5c340f',
51 | '900': '#482307',
52 | },
53 | },
54 | typography: (theme) => ({
55 | DEFAULT: {
56 | css: {
57 | '--tw-prose-bullets': theme('colors.toast[400]'),
58 | '--tw-prose-links': theme('colors.toast[600]'),
59 | color: theme('colors.toast.900'),
60 | h1: {
61 | color: theme('colors.toast.900'),
62 | },
63 | h2: {
64 | color: theme('colors.toast.900'),
65 | },
66 | h3: {
67 | color: theme('colors.toast.800'),
68 | },
69 | h4: {
70 | color: theme('colors.toast.900'),
71 | },
72 | a: {
73 | color: theme('colors.toast.600'),
74 | },
75 | strong: {
76 | color: theme('colors.toast.900'),
77 | },
78 | pre: {
79 | color: null,
80 | backgroundColor: null,
81 | overflowX: 'auto',
82 | fontSize: theme('fontSize.base'),
83 | padding: 0,
84 | },
85 | 'pre pre': {
86 | padding: theme('spacing.4'),
87 | margin: 0,
88 | },
89 | 'pre code': {
90 | backgroundColor: 'transparent',
91 | borderWidth: '0',
92 | borderRadius: '0',
93 | fontWeight: '400',
94 | color: 'inherit',
95 | fontFamily: 'inherit',
96 | lineHeight: 'inherit',
97 | },
98 | code: {
99 | color: theme('colors.toast.900'),
100 | fontWeight: '600',
101 | },
102 | 'code::before': {
103 | content: '""',
104 | },
105 | 'code::after': {
106 | content: '""',
107 | },
108 | thead: {
109 | color: theme('colors.toast.900'),
110 | fontWeight: '600',
111 | borderBottomWidth: '1px',
112 | borderBottomColor: theme('colors.toast.200'),
113 | },
114 | 'tbody tr': {
115 | borderBottomWidth: '1px',
116 | borderBottomColor: theme('colors.toast.200'),
117 | },
118 | 'ul > li::before': {
119 | content: '""',
120 | position: 'absolute',
121 | backgroundColor: theme('colors.toast.800'),
122 | borderRadius: '50%',
123 | },
124 | // ...
125 | },
126 | },
127 | }),
128 | },
129 | container: {
130 | padding: '1rem',
131 | center: true,
132 | },
133 | },
134 | variants: {
135 | extend: {
136 | translate: ['active'],
137 | gradientColorStops: ['active'],
138 | boxShadow: ['active'],
139 | },
140 | },
141 | plugins: [require('@tailwindcss/typography')],
142 | };
143 |
--------------------------------------------------------------------------------
/site/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true
21 | },
22 | "include": [
23 | "next-env.d.ts",
24 | "types/*.d.ts",
25 | "**/*.ts",
26 | "**/*.tsx",
27 | "**/**/*.ts",
28 | "**/**/*.tsx",
29 | "../**/*.ts",
30 | "../**/*.tsx"
31 | ],
32 | "exclude": [
33 | "node_modules"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/site/types/mdx.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.mdx' {
2 | let MDXComponent: (props: any) => JSX.Element;
3 | export default MDXComponent;
4 | }
5 |
--------------------------------------------------------------------------------
/site/types/svg.d.ts:
--------------------------------------------------------------------------------
1 | interface SvgrComponent
2 | extends React.StatelessComponent> {}
3 |
4 | declare module '*.svg' {
5 | const value: SvgrComponent;
6 | export default value;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/checkmark.tsx:
--------------------------------------------------------------------------------
1 | import { styled, keyframes } from 'goober';
2 |
3 | const circleAnimation = keyframes`
4 | from {
5 | transform: scale(0) rotate(45deg);
6 | opacity: 0;
7 | }
8 | to {
9 | transform: scale(1) rotate(45deg);
10 | opacity: 1;
11 | }`;
12 |
13 | const checkmarkAnimation = keyframes`
14 | 0% {
15 | height: 0;
16 | width: 0;
17 | opacity: 0;
18 | }
19 | 40% {
20 | height: 0;
21 | width: 6px;
22 | opacity: 1;
23 | }
24 | 100% {
25 | opacity: 1;
26 | height: 10px;
27 | }`;
28 |
29 | export interface CheckmarkTheme {
30 | primary?: string;
31 | secondary?: string;
32 | }
33 |
34 | export const CheckmarkIcon = styled('div')`
35 | width: 20px;
36 | opacity: 0;
37 | height: 20px;
38 | border-radius: 10px;
39 | background: ${(p) => p.primary || '#61d345'};
40 | position: relative;
41 | transform: rotate(45deg);
42 |
43 | animation: ${circleAnimation} 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)
44 | forwards;
45 | animation-delay: 100ms;
46 | &:after {
47 | content: '';
48 | box-sizing: border-box;
49 | animation: ${checkmarkAnimation} 0.2s ease-out forwards;
50 | opacity: 0;
51 | animation-delay: 200ms;
52 | position: absolute;
53 | border-right: 2px solid;
54 | border-bottom: 2px solid;
55 | border-color: ${(p) => p.secondary || '#fff'};
56 | bottom: 6px;
57 | left: 6px;
58 | height: 10px;
59 | width: 6px;
60 | }
61 | `;
62 |
--------------------------------------------------------------------------------
/src/components/error.tsx:
--------------------------------------------------------------------------------
1 | import { styled, keyframes } from 'goober';
2 |
3 | const circleAnimation = keyframes`
4 | from {
5 | transform: scale(0) rotate(45deg);
6 | opacity: 0;
7 | }
8 | to {
9 | transform: scale(1) rotate(45deg);
10 | opacity: 1;
11 | }`;
12 |
13 | const firstLineAnimation = keyframes`
14 | from {
15 | transform: scale(0);
16 | opacity: 0;
17 | }
18 | to {
19 | transform: scale(1);
20 | opacity: 1;
21 | }`;
22 |
23 | const secondLineAnimation = keyframes`
24 | from {
25 | transform: scale(0) rotate(90deg);
26 | opacity: 0;
27 | }
28 | to {
29 | transform: scale(1) rotate(90deg);
30 | opacity: 1;
31 | }`;
32 |
33 | export interface ErrorTheme {
34 | primary?: string;
35 | secondary?: string;
36 | }
37 |
38 | export const ErrorIcon = styled('div')`
39 | width: 20px;
40 | opacity: 0;
41 | height: 20px;
42 | border-radius: 10px;
43 | background: ${(p) => p.primary || '#ff4b4b'};
44 | position: relative;
45 | transform: rotate(45deg);
46 |
47 | animation: ${circleAnimation} 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)
48 | forwards;
49 | animation-delay: 100ms;
50 |
51 | &:after,
52 | &:before {
53 | content: '';
54 | animation: ${firstLineAnimation} 0.15s ease-out forwards;
55 | animation-delay: 150ms;
56 | position: absolute;
57 | border-radius: 3px;
58 | opacity: 0;
59 | background: ${(p) => p.secondary || '#fff'};
60 | bottom: 9px;
61 | left: 4px;
62 | height: 2px;
63 | width: 12px;
64 | }
65 |
66 | &:before {
67 | animation: ${secondLineAnimation} 0.15s ease-out forwards;
68 | animation-delay: 180ms;
69 | transform: rotate(90deg);
70 | }
71 | `;
72 |
--------------------------------------------------------------------------------
/src/components/loader.tsx:
--------------------------------------------------------------------------------
1 | import { styled, keyframes } from 'goober';
2 |
3 | const rotate = keyframes`
4 | from {
5 | transform: rotate(0deg);
6 | }
7 | to {
8 | transform: rotate(360deg);
9 | }
10 | `;
11 |
12 | export interface LoaderTheme {
13 | primary?: string;
14 | secondary?: string;
15 | }
16 |
17 | export const LoaderIcon = styled('div')`
18 | width: 12px;
19 | height: 12px;
20 | box-sizing: border-box;
21 | border: 2px solid;
22 | border-radius: 100%;
23 | border-color: ${(p) => p.secondary || '#e0e0e0'};
24 | border-right-color: ${(p) => p.primary || '#616161'};
25 | animation: ${rotate} 1s linear infinite;
26 | `;
27 |
--------------------------------------------------------------------------------
/src/components/toast-bar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { styled, keyframes } from 'goober';
3 |
4 | import { Toast, ToastPosition, resolveValue, Renderable } from '../core/types';
5 | import { ToastIcon } from './toast-icon';
6 | import { prefersReducedMotion } from '../core/utils';
7 |
8 | const enterAnimation = (factor: number) => `
9 | 0% {transform: translate3d(0,${factor * -200}%,0) scale(.6); opacity:.5;}
10 | 100% {transform: translate3d(0,0,0) scale(1); opacity:1;}
11 | `;
12 |
13 | const exitAnimation = (factor: number) => `
14 | 0% {transform: translate3d(0,0,-1px) scale(1); opacity:1;}
15 | 100% {transform: translate3d(0,${factor * -150}%,-1px) scale(.6); opacity:0;}
16 | `;
17 |
18 | const fadeInAnimation = `0%{opacity:0;} 100%{opacity:1;}`;
19 | const fadeOutAnimation = `0%{opacity:1;} 100%{opacity:0;}`;
20 |
21 | const ToastBarBase = styled('div', React.forwardRef)`
22 | display: flex;
23 | align-items: center;
24 | background: #fff;
25 | color: #363636;
26 | line-height: 1.3;
27 | will-change: transform;
28 | box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1), 0 3px 3px rgba(0, 0, 0, 0.05);
29 | max-width: 350px;
30 | pointer-events: auto;
31 | padding: 8px 10px;
32 | border-radius: 8px;
33 | `;
34 |
35 | const Message = styled('div')`
36 | display: flex;
37 | justify-content: center;
38 | margin: 4px 10px;
39 | color: inherit;
40 | flex: 1 1 auto;
41 | white-space: pre-line;
42 | `;
43 |
44 | interface ToastBarProps {
45 | toast: Toast;
46 | position?: ToastPosition;
47 | style?: React.CSSProperties;
48 | children?: (components: {
49 | icon: Renderable;
50 | message: Renderable;
51 | }) => Renderable;
52 | }
53 |
54 | const getAnimationStyle = (
55 | position: ToastPosition,
56 | visible: boolean
57 | ): React.CSSProperties => {
58 | const top = position.includes('top');
59 | const factor = top ? 1 : -1;
60 |
61 | const [enter, exit] = prefersReducedMotion()
62 | ? [fadeInAnimation, fadeOutAnimation]
63 | : [enterAnimation(factor), exitAnimation(factor)];
64 |
65 | return {
66 | animation: visible
67 | ? `${keyframes(enter)} 0.35s cubic-bezier(.21,1.02,.73,1) forwards`
68 | : `${keyframes(exit)} 0.4s forwards cubic-bezier(.06,.71,.55,1)`,
69 | };
70 | };
71 |
72 | export const ToastBar: React.FC = React.memo(
73 | ({ toast, position, style, children }) => {
74 | const animationStyle: React.CSSProperties = toast?.height
75 | ? getAnimationStyle(
76 | toast.position || position || 'top-center',
77 | toast.visible
78 | )
79 | : { opacity: 0 };
80 |
81 | const icon = ;
82 | const message = (
83 |
84 | {resolveValue(toast.message, toast)}
85 |
86 | );
87 |
88 | return (
89 |
97 | {typeof children === 'function' ? (
98 | children({
99 | icon,
100 | message,
101 | })
102 | ) : (
103 | <>
104 | {icon}
105 | {message}
106 | >
107 | )}
108 |
109 | );
110 | }
111 | );
112 |
--------------------------------------------------------------------------------
/src/components/toast-icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { styled, keyframes } from 'goober';
3 |
4 | import { Toast } from '../core/types';
5 | import { ErrorIcon, ErrorTheme } from './error';
6 | import { LoaderIcon, LoaderTheme } from './loader';
7 | import { CheckmarkIcon, CheckmarkTheme } from './checkmark';
8 |
9 | const StatusWrapper = styled('div')`
10 | position: absolute;
11 | `;
12 |
13 | const IndicatorWrapper = styled('div')`
14 | position: relative;
15 | display: flex;
16 | justify-content: center;
17 | align-items: center;
18 | min-width: 20px;
19 | min-height: 20px;
20 | `;
21 |
22 | const enter = keyframes`
23 | from {
24 | transform: scale(0.6);
25 | opacity: 0.4;
26 | }
27 | to {
28 | transform: scale(1);
29 | opacity: 1;
30 | }`;
31 |
32 | export const AnimatedIconWrapper = styled('div')`
33 | position: relative;
34 | transform: scale(0.6);
35 | opacity: 0.4;
36 | min-width: 20px;
37 | animation: ${enter} 0.3s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275)
38 | forwards;
39 | `;
40 |
41 | export type IconThemes = Partial<{
42 | success: CheckmarkTheme;
43 | error: ErrorTheme;
44 | loading: LoaderTheme;
45 | }>;
46 |
47 | export const ToastIcon: React.FC<{
48 | toast: Toast;
49 | }> = ({ toast }) => {
50 | const { icon, type, iconTheme } = toast;
51 | if (icon !== undefined) {
52 | if (typeof icon === 'string') {
53 | return {icon} ;
54 | } else {
55 | return icon;
56 | }
57 | }
58 |
59 | if (type === 'blank') {
60 | return null;
61 | }
62 |
63 | return (
64 |
65 |
66 | {type !== 'loading' && (
67 |
68 | {type === 'error' ? (
69 |
70 | ) : (
71 |
72 | )}
73 |
74 | )}
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/components/toaster.tsx:
--------------------------------------------------------------------------------
1 | import { css, setup } from 'goober';
2 | import * as React from 'react';
3 | import { resolveValue, ToasterProps, ToastPosition } from '../core/types';
4 | import { useToaster } from '../core/use-toaster';
5 | import { createRectRef, prefersReducedMotion } from '../core/utils';
6 | import { ToastBar } from './toast-bar';
7 |
8 | setup(React.createElement);
9 |
10 | const getPositionStyle = (
11 | position: ToastPosition,
12 | offset: number
13 | ): React.CSSProperties => {
14 | const top = position.includes('top');
15 | const verticalStyle: React.CSSProperties = top ? { top: 0 } : { bottom: 0 };
16 | const horizontalStyle: React.CSSProperties = position.includes('center')
17 | ? {
18 | justifyContent: 'center',
19 | }
20 | : position.includes('right')
21 | ? {
22 | justifyContent: 'flex-end',
23 | }
24 | : {};
25 | return {
26 | left: 0,
27 | right: 0,
28 | display: 'flex',
29 | position: 'absolute',
30 | transition: prefersReducedMotion()
31 | ? undefined
32 | : `all 230ms cubic-bezier(.21,1.02,.73,1)`,
33 | transform: `translateY(${offset * (top ? 1 : -1)}px)`,
34 | ...verticalStyle,
35 | ...horizontalStyle,
36 | };
37 | };
38 |
39 | const activeClass = css`
40 | z-index: 9999;
41 | > * {
42 | pointer-events: auto;
43 | }
44 | `;
45 |
46 | const DEFAULT_OFFSET = 16;
47 |
48 | export const Toaster: React.FC = ({
49 | reverseOrder,
50 | position = 'top-center',
51 | toastOptions,
52 | gutter,
53 | children,
54 | containerStyle,
55 | containerClassName,
56 | }) => {
57 | const { toasts, handlers } = useToaster(toastOptions);
58 |
59 | return (
60 |
75 | {toasts.map((t) => {
76 | const toastPosition = t.position || position;
77 | const offset = handlers.calculateOffset(t, {
78 | reverseOrder,
79 | gutter,
80 | defaultPosition: position,
81 | });
82 | const positionStyle = getPositionStyle(toastPosition, offset);
83 |
84 | const ref = t.height
85 | ? undefined
86 | : createRectRef((rect) => {
87 | handlers.updateHeight(t.id, rect.height);
88 | });
89 |
90 | return (
91 |
97 | {t.type === 'custom' ? (
98 | resolveValue(t.message, t)
99 | ) : children ? (
100 | children(t)
101 | ) : (
102 |
103 | )}
104 |
105 | );
106 | })}
107 |
108 | );
109 | };
110 |
--------------------------------------------------------------------------------
/src/core/store.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { DefaultToastOptions, Toast, ToastType } from './types';
3 |
4 | const TOAST_LIMIT = 20;
5 |
6 | export enum ActionType {
7 | ADD_TOAST,
8 | UPDATE_TOAST,
9 | UPSERT_TOAST,
10 | DISMISS_TOAST,
11 | REMOVE_TOAST,
12 | START_PAUSE,
13 | END_PAUSE,
14 | }
15 |
16 | type Action =
17 | | {
18 | type: ActionType.ADD_TOAST;
19 | toast: Toast;
20 | }
21 | | {
22 | type: ActionType.UPSERT_TOAST;
23 | toast: Toast;
24 | }
25 | | {
26 | type: ActionType.UPDATE_TOAST;
27 | toast: Partial;
28 | }
29 | | {
30 | type: ActionType.DISMISS_TOAST;
31 | toastId?: string;
32 | }
33 | | {
34 | type: ActionType.REMOVE_TOAST;
35 | toastId?: string;
36 | }
37 | | {
38 | type: ActionType.START_PAUSE;
39 | time: number;
40 | }
41 | | {
42 | type: ActionType.END_PAUSE;
43 | time: number;
44 | };
45 |
46 | interface State {
47 | toasts: Toast[];
48 | pausedAt: number | undefined;
49 | }
50 |
51 | const toastTimeouts = new Map>();
52 |
53 | const addToRemoveQueue = (toastId: string) => {
54 | if (toastTimeouts.has(toastId)) {
55 | return;
56 | }
57 |
58 | const timeout = setTimeout(() => {
59 | toastTimeouts.delete(toastId);
60 | dispatch({
61 | type: ActionType.REMOVE_TOAST,
62 | toastId: toastId,
63 | });
64 | }, 1000);
65 |
66 | toastTimeouts.set(toastId, timeout);
67 | };
68 |
69 | const clearFromRemoveQueue = (toastId: string) => {
70 | const timeout = toastTimeouts.get(toastId);
71 | if (timeout) {
72 | clearTimeout(timeout);
73 | }
74 | };
75 |
76 | export const reducer = (state: State, action: Action): State => {
77 | switch (action.type) {
78 | case ActionType.ADD_TOAST:
79 | return {
80 | ...state,
81 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
82 | };
83 |
84 | case ActionType.UPDATE_TOAST:
85 | // ! Side effects !
86 | if (action.toast.id) {
87 | clearFromRemoveQueue(action.toast.id);
88 | }
89 |
90 | return {
91 | ...state,
92 | toasts: state.toasts.map((t) =>
93 | t.id === action.toast.id ? { ...t, ...action.toast } : t
94 | ),
95 | };
96 |
97 | case ActionType.UPSERT_TOAST:
98 | const { toast } = action;
99 | return state.toasts.find((t) => t.id === toast.id)
100 | ? reducer(state, { type: ActionType.UPDATE_TOAST, toast })
101 | : reducer(state, { type: ActionType.ADD_TOAST, toast });
102 |
103 | case ActionType.DISMISS_TOAST:
104 | const { toastId } = action;
105 |
106 | // ! Side effects ! - This could be execrated into a dismissToast() action, but I'll keep it here for simplicity
107 | if (toastId) {
108 | addToRemoveQueue(toastId);
109 | } else {
110 | state.toasts.forEach((toast) => {
111 | addToRemoveQueue(toast.id);
112 | });
113 | }
114 |
115 | return {
116 | ...state,
117 | toasts: state.toasts.map((t) =>
118 | t.id === toastId || toastId === undefined
119 | ? {
120 | ...t,
121 | visible: false,
122 | }
123 | : t
124 | ),
125 | };
126 | case ActionType.REMOVE_TOAST:
127 | if (action.toastId === undefined) {
128 | return {
129 | ...state,
130 | toasts: [],
131 | };
132 | }
133 | return {
134 | ...state,
135 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
136 | };
137 |
138 | case ActionType.START_PAUSE:
139 | return {
140 | ...state,
141 | pausedAt: action.time,
142 | };
143 |
144 | case ActionType.END_PAUSE:
145 | const diff = action.time - (state.pausedAt || 0);
146 |
147 | return {
148 | ...state,
149 | pausedAt: undefined,
150 | toasts: state.toasts.map((t) => ({
151 | ...t,
152 | pauseDuration: t.pauseDuration + diff,
153 | })),
154 | };
155 | }
156 | };
157 |
158 | const listeners: Array<(state: State) => void> = [];
159 |
160 | let memoryState: State = { toasts: [], pausedAt: undefined };
161 |
162 | export const dispatch = (action: Action) => {
163 | memoryState = reducer(memoryState, action);
164 | listeners.forEach((listener) => {
165 | listener(memoryState);
166 | });
167 | };
168 |
169 | const defaultTimeouts: {
170 | [key in ToastType]: number;
171 | } = {
172 | blank: 4000,
173 | error: 4000,
174 | success: 2000,
175 | loading: Infinity,
176 | custom: 4000,
177 | };
178 |
179 | export const useStore = (toastOptions: DefaultToastOptions = {}): State => {
180 | const [state, setState] = useState(memoryState);
181 | useEffect(() => {
182 | listeners.push(setState);
183 | return () => {
184 | const index = listeners.indexOf(setState);
185 | if (index > -1) {
186 | listeners.splice(index, 1);
187 | }
188 | };
189 | }, [state]);
190 |
191 | const mergedToasts = state.toasts.map((t) => ({
192 | ...toastOptions,
193 | ...toastOptions[t.type],
194 | ...t,
195 | duration:
196 | t.duration ||
197 | toastOptions[t.type]?.duration ||
198 | toastOptions?.duration ||
199 | defaultTimeouts[t.type],
200 | style: {
201 | ...toastOptions.style,
202 | ...toastOptions[t.type]?.style,
203 | ...t.style,
204 | },
205 | }));
206 |
207 | return {
208 | ...state,
209 | toasts: mergedToasts,
210 | };
211 | };
212 |
--------------------------------------------------------------------------------
/src/core/toast.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Renderable,
3 | Toast,
4 | ToastOptions,
5 | ToastType,
6 | DefaultToastOptions,
7 | ValueOrFunction,
8 | resolveValue,
9 | } from './types';
10 | import { genId } from './utils';
11 | import { dispatch, ActionType } from './store';
12 |
13 | type Message = ValueOrFunction;
14 |
15 | type ToastHandler = (message: Message, options?: ToastOptions) => string;
16 |
17 | const createToast = (
18 | message: Message,
19 | type: ToastType = 'blank',
20 | opts?: ToastOptions
21 | ): Toast => ({
22 | createdAt: Date.now(),
23 | visible: true,
24 | type,
25 | ariaProps: {
26 | role: 'status',
27 | 'aria-live': 'polite',
28 | },
29 | message,
30 | pauseDuration: 0,
31 | ...opts,
32 | id: opts?.id || genId(),
33 | });
34 |
35 | const createHandler =
36 | (type?: ToastType): ToastHandler =>
37 | (message, options) => {
38 | const toast = createToast(message, type, options);
39 | dispatch({ type: ActionType.UPSERT_TOAST, toast });
40 | return toast.id;
41 | };
42 |
43 | const toast = (message: Message, opts?: ToastOptions) =>
44 | createHandler('blank')(message, opts);
45 |
46 | toast.error = createHandler('error');
47 | toast.success = createHandler('success');
48 | toast.loading = createHandler('loading');
49 | toast.custom = createHandler('custom');
50 |
51 | toast.dismiss = (toastId?: string) => {
52 | dispatch({
53 | type: ActionType.DISMISS_TOAST,
54 | toastId,
55 | });
56 | };
57 |
58 | toast.remove = (toastId?: string) =>
59 | dispatch({ type: ActionType.REMOVE_TOAST, toastId });
60 |
61 | toast.promise = (
62 | promise: Promise,
63 | msgs: {
64 | loading: Renderable;
65 | success: ValueOrFunction;
66 | error: ValueOrFunction;
67 | },
68 | opts?: DefaultToastOptions
69 | ) => {
70 | const id = toast.loading(msgs.loading, { ...opts, ...opts?.loading });
71 |
72 | promise
73 | .then((p) => {
74 | toast.success(resolveValue(msgs.success, p), {
75 | id,
76 | ...opts,
77 | ...opts?.success,
78 | });
79 | return p;
80 | })
81 | .catch((e) => {
82 | toast.error(resolveValue(msgs.error, e), {
83 | id,
84 | ...opts,
85 | ...opts?.error,
86 | });
87 | });
88 |
89 | return promise;
90 | };
91 |
92 | export { toast };
93 |
--------------------------------------------------------------------------------
/src/core/types.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from 'react';
2 |
3 | export type ToastType = 'success' | 'error' | 'loading' | 'blank' | 'custom';
4 | export type ToastPosition =
5 | | 'top-left'
6 | | 'top-center'
7 | | 'top-right'
8 | | 'bottom-left'
9 | | 'bottom-center'
10 | | 'bottom-right';
11 |
12 | export type Renderable = JSX.Element | string | null;
13 |
14 | export interface IconTheme {
15 | primary: string;
16 | secondary: string;
17 | }
18 |
19 | export type ValueFunction = (arg: TArg) => TValue;
20 | export type ValueOrFunction =
21 | | TValue
22 | | ValueFunction;
23 |
24 | const isFunction = (
25 | valOrFunction: ValueOrFunction
26 | ): valOrFunction is ValueFunction =>
27 | typeof valOrFunction === 'function';
28 |
29 | export const resolveValue = (
30 | valOrFunction: ValueOrFunction,
31 | arg: TArg
32 | ): TValue => (isFunction(valOrFunction) ? valOrFunction(arg) : valOrFunction);
33 |
34 | export interface Toast {
35 | type: ToastType;
36 | id: string;
37 | message: ValueOrFunction;
38 | icon?: Renderable;
39 | duration?: number;
40 | pauseDuration: number;
41 | position?: ToastPosition;
42 |
43 | ariaProps: {
44 | role: 'status' | 'alert';
45 | 'aria-live': 'assertive' | 'off' | 'polite';
46 | };
47 |
48 | style?: CSSProperties;
49 | className?: string;
50 | iconTheme?: IconTheme;
51 |
52 | createdAt: number;
53 | visible: boolean;
54 | height?: number;
55 | }
56 |
57 | export type ToastOptions = Partial<
58 | Pick<
59 | Toast,
60 | | 'id'
61 | | 'icon'
62 | | 'duration'
63 | | 'ariaProps'
64 | | 'className'
65 | | 'style'
66 | | 'position'
67 | | 'iconTheme'
68 | >
69 | >;
70 |
71 | export type DefaultToastOptions = ToastOptions & {
72 | [key in ToastType]?: ToastOptions;
73 | };
74 |
75 | export interface ToasterProps {
76 | position?: ToastPosition;
77 | toastOptions?: DefaultToastOptions;
78 | reverseOrder?: boolean;
79 | gutter?: number;
80 | containerStyle?: React.CSSProperties;
81 | containerClassName?: string;
82 | children?: (toast: Toast) => JSX.Element;
83 | }
84 |
--------------------------------------------------------------------------------
/src/core/use-toaster.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react';
2 | import { dispatch, ActionType, useStore } from './store';
3 | import { toast } from './toast';
4 | import { DefaultToastOptions, Toast, ToastPosition } from './types';
5 |
6 | export const useToaster = (toastOptions?: DefaultToastOptions) => {
7 | const { toasts, pausedAt } = useStore(toastOptions);
8 |
9 | useEffect(() => {
10 | if (pausedAt) {
11 | return;
12 | }
13 |
14 | const now = Date.now();
15 | const timeouts = toasts.map((t) => {
16 | if (t.duration === Infinity) {
17 | return;
18 | }
19 |
20 | const durationLeft =
21 | (t.duration || 0) + t.pauseDuration - (now - t.createdAt);
22 |
23 | if (durationLeft < 0) {
24 | if (t.visible) {
25 | toast.dismiss(t.id);
26 | }
27 | return;
28 | }
29 | return setTimeout(() => toast.dismiss(t.id), durationLeft);
30 | });
31 |
32 | return () => {
33 | timeouts.forEach((timeout) => timeout && clearTimeout(timeout));
34 | };
35 | }, [toasts, pausedAt]);
36 |
37 | const handlers = useMemo(
38 | () => ({
39 | startPause: () => {
40 | dispatch({
41 | type: ActionType.START_PAUSE,
42 | time: Date.now(),
43 | });
44 | },
45 | endPause: () => {
46 | if (pausedAt) {
47 | dispatch({ type: ActionType.END_PAUSE, time: Date.now() });
48 | }
49 | },
50 | updateHeight: (toastId: string, height: number) =>
51 | dispatch({
52 | type: ActionType.UPDATE_TOAST,
53 | toast: { id: toastId, height },
54 | }),
55 | calculateOffset: (
56 | toast: Toast,
57 | opts?: {
58 | reverseOrder?: boolean;
59 | gutter?: number;
60 | defaultPosition?: ToastPosition;
61 | }
62 | ) => {
63 | const {
64 | reverseOrder = false,
65 | gutter = 8,
66 | defaultPosition,
67 | } = opts || {};
68 |
69 | const relevantToasts = toasts.filter(
70 | (t) =>
71 | (t.position || defaultPosition) ===
72 | (toast.position || defaultPosition) && t.height
73 | );
74 | const toastIndex = relevantToasts.findIndex((t) => t.id === toast.id);
75 | const toastsBefore = relevantToasts.filter(
76 | (toast, i) => i < toastIndex && toast.visible
77 | ).length;
78 |
79 | const offset = relevantToasts
80 | .filter((t) => t.visible)
81 | .slice(...(reverseOrder ? [toastsBefore + 1] : [0, toastsBefore]))
82 | .reduce((acc, t) => acc + (t.height || 0) + gutter, 0);
83 |
84 | return offset;
85 | },
86 | }),
87 | [toasts, pausedAt]
88 | );
89 |
90 | return {
91 | toasts,
92 | handlers,
93 | };
94 | };
95 |
--------------------------------------------------------------------------------
/src/core/utils.ts:
--------------------------------------------------------------------------------
1 | export const genId = (() => {
2 | let count = 0;
3 | return () => {
4 | return (++count).toString();
5 | };
6 | })();
7 |
8 | export const createRectRef =
9 | (onRect: (rect: DOMRect) => void) => (el: HTMLElement | null) => {
10 | if (el) {
11 | setTimeout(() => {
12 | const boundingRect = el.getBoundingClientRect();
13 | onRect(boundingRect);
14 | });
15 | }
16 | };
17 |
18 | export const prefersReducedMotion = (() => {
19 | // Cache result
20 | let shouldReduceMotion: boolean | undefined = undefined;
21 |
22 | return () => {
23 | if (shouldReduceMotion === undefined && typeof window !== 'undefined') {
24 | const mediaQuery = matchMedia('(prefers-reduced-motion: reduce)');
25 | shouldReduceMotion = !mediaQuery || mediaQuery.matches;
26 | }
27 | return shouldReduceMotion;
28 | };
29 | })();
30 |
--------------------------------------------------------------------------------
/src/headless/index.ts:
--------------------------------------------------------------------------------
1 | import { toast } from '../core/toast';
2 |
3 | export type {
4 | DefaultToastOptions,
5 | IconTheme,
6 | Renderable,
7 | Toast,
8 | ToasterProps,
9 | ToastOptions,
10 | ToastPosition,
11 | ToastType,
12 | ValueFunction,
13 | ValueOrFunction,
14 | } from '../core/types';
15 |
16 | export { resolveValue } from '../core/types';
17 | export { useToaster } from '../core/use-toaster';
18 | export { useStore as useToasterStore } from '../core/store';
19 |
20 | export { toast };
21 | export default toast;
22 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { toast } from './core/toast';
2 |
3 | export * from './headless';
4 |
5 | export { ToastBar } from './components/toast-bar';
6 | export { ToastIcon } from './components/toast-icon';
7 | export { Toaster } from './components/toaster';
8 | export { CheckmarkIcon } from './components/checkmark';
9 | export { ErrorIcon } from './components/error';
10 | export { LoaderIcon } from './components/loader';
11 |
12 | export { toast };
13 | export default toast;
14 |
--------------------------------------------------------------------------------
/test/toast.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import { Toaster, toast } from '../src';
4 |
5 | describe('it', () => {
6 | it('renders without crashing', () => {
7 | const div = document.createElement('div');
8 | toast('Hello World');
9 | ReactDOM.render( , div);
10 | ReactDOM.unmountComponentAtNode(div);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
3 | "include": ["src", "types"],
4 | "compilerOptions": {
5 | "module": "esnext",
6 | "lib": ["dom", "esnext"],
7 | // output .d.ts declaration files for consumers
8 | "declaration": true,
9 | // output .js.map sourcemap files for consumers
10 | "sourceMap": true,
11 | // match output dir to input dir. e.g. dist/index instead of dist/src/index
12 | "rootDir": "./src",
13 | // stricter type-checking for stronger correctness. Recommended by TS
14 | "strict": true,
15 | // linter checks for common issues
16 | "noImplicitReturns": true,
17 | "noFallthroughCasesInSwitch": true,
18 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
19 | "noUnusedParameters": true,
20 | // use Node's module resolution algorithm, instead of the legacy TS one
21 | "moduleResolution": "node",
22 | // transpile JSX to React.createElement
23 | "jsx": "react",
24 | // interop between ESM and CJS modules. Recommended by TS
25 | "esModuleInterop": true,
26 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
27 | "skipLibCheck": true,
28 | // error out if import and file system have a casing mismatch. Recommended by TS
29 | "forceConsistentCasingInFileNames": true,
30 | "noEmit": true,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, Options } from 'tsup';
2 |
3 | const commonConfig: Options = {
4 | minify: true,
5 | dts: true,
6 | format: ['esm', 'cjs'],
7 | sourcemap: true,
8 | clean: true,
9 | };
10 | export default defineConfig([
11 | {
12 | ...commonConfig,
13 | entry: ['src/index.ts'],
14 | outDir: 'dist',
15 | },
16 | {
17 | ...commonConfig,
18 | entry: ['src/headless/index.ts'],
19 | outDir: 'headless',
20 | },
21 | ]);
22 |
--------------------------------------------------------------------------------