├── .github └── workflows │ ├── main.yml │ └── size.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── assets └── header.svg ├── jest.config.js ├── 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 ├── setup.ts └── 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 & test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: pnpm/action-setup@v4 10 | - name: Use Node.js ${{ matrix.node-version }} 11 | uses: actions/setup-node@v2 12 | with: 13 | node-version: ${{ matrix.node-version }} 14 | cache: 'pnpm' 15 | - name: Install dependencies 16 | run: pnpm install 17 | - name: Build package 18 | run: pnpm build 19 | - name: Test 20 | run: pnpm run test --ci --coverage 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@v4 11 | - uses: andresz1/size-limit-action@v1 12 | with: 13 | github_token: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | coverage 6 | dist 7 | /headless 8 | .vscode 9 | .vercel 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /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 | react-hot-toast - Try it out 2 | 3 |
4 | NPM Version 5 | minzipped size 6 | Build Status 7 | 8 |
9 |
10 |
Smoking hot Notifications for React.
11 |
Lightweight, customizable and beautiful by default.
12 |
13 |
14 | Website 15 | · 16 | Documentation 17 | · 18 | Twitter 19 |
20 | 21 |
22 |
23 | Cooked by Timo Lins 👨‍🍳 24 |
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 pnpm 40 | 41 | ```sh 42 | pnpm 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 | 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 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'jsdom', 5 | setupFilesAfterEnv: ['/test/setup.ts'], 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hot-toast", 3 | "description": "Smoking hot React Notifications. Lightweight, customizable and beautiful by default.", 4 | "version": "2.5.2", 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": "jest --runInBand", 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.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 | "@jest/types": "^29.6.3", 77 | "@size-limit/preset-small-lib": "^7.0.8", 78 | "@testing-library/jest-dom": "^6.6.3", 79 | "@testing-library/react": "^16.1.0", 80 | "@types/jest": "^29.5.14", 81 | "@types/react": "^18.3.18", 82 | "@types/react-dom": "^18.3.5", 83 | "jest": "^29.7.0", 84 | "jest-environment-jsdom": "^29.7.0", 85 | "prettier": "^2.8.8", 86 | "react": "^18.3.1", 87 | "react-dom": "^18.3.1", 88 | "size-limit": "^7.0.8", 89 | "ts-jest": "^29.2.5", 90 | "tslib": "^2.8.1", 91 | "tsup": "^6.7.0", 92 | "typescript": "^5.7.2" 93 | }, 94 | "dependencies": { 95 | "csstype": "^3.1.3", 96 | "goober": "^2.1.16" 97 | }, 98 | "peerDependencies": { 99 | "react": ">=16", 100 | "react-dom": ">=16" 101 | }, 102 | "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" 103 | } 104 | -------------------------------------------------------------------------------- /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/timolins/react-hot-toast/7f7845cd02a577f9f0d2e573b93aa136e5d5caa0/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 |
46 | 47 | 51 | 52 | 56 | GitHub 57 | 58 |
59 | 60 |
61 | 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 | React.ReactElement; 4 | children?: React.ReactNode; 5 | }> = ({ onClick, children, emoji }) => ( 6 | 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 | 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 |
62 | ⚡️ Real-time analytics by{' '} 63 | 69 | Splitbee 70 | 71 |
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 | 126 | 127 | ));`, 128 | 129 | action: () => { 130 | toast((t) => ( 131 | 132 | Custom and bold 133 | 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 | 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 | 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 | 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 | 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.3.0", 10 | "@mdx-js/react": "^2.3.0", 11 | "@next/mdx": "^12.3.4", 12 | "@svgr/webpack": "^6.5.1", 13 | "@types/prismjs": "^1.26.5", 14 | "@vercel/analytics": "^0.1.11", 15 | "clsx": "^1.1.1", 16 | "next": "^12.3.4", 17 | "next-seo": "^5.15.0", 18 | "postcss": "^8.4.49", 19 | "prism-react-renderer": "^1.3.5", 20 | "react": "^18.3.1", 21 | "react-dom": "^18.3.1", 22 | "react-hot-toast": "link:../", 23 | "rehype-slug": "^5.1.0" 24 | }, 25 | "devDependencies": { 26 | "@tailwindcss/typography": "^0.5.15", 27 | "@types/node": "^18.19.68", 28 | "@types/react": "^18.3.18", 29 | "@types/react-dom": "^18.3.5", 30 | "autoprefixer": "^10.4.20", 31 | "next-compose-plugins": "^2.2.1", 32 | "remark-gfm": "^3.0.1", 33 | "tailwindcss": "^3.4.17", 34 | "typescript": "^4.9.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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 | import { Analytics } from '@vercel/analytics/react'; 7 | 8 | 9 | import { MDXProvider } from '@mdx-js/react'; 10 | import { Code } from '../components/code'; 11 | 12 | const components = { 13 | a: (props) => ( 14 | 15 | 16 | 17 | ), 18 | h1: (props) => { 19 | const id = props.id || ''; 20 | return ( 21 |

22 | 23 | 26 | {props.children} 27 | 28 | 29 |

30 | ); 31 | }, 32 | h2: (props) => { 33 | const id = props.id || ''; 34 | return ( 35 |

36 | 37 | 40 | {props.children} 41 | 42 | 43 |

44 | ); 45 | }, 46 | h3: (props) => { 47 | const id = props.id || ''; 48 | return ( 49 |

50 | 51 | 54 | {props.children} 55 | 56 | 57 |

58 | ); 59 | }, 60 | code: (props) => 61 | props.className ? ( 62 | 63 | ) : ( 64 | 68 | ), 69 | }; 70 | 71 | function MyApp({ Component, pageProps }) { 72 | return ( 73 | <> 74 | 75 | {process.browser && ( 76 |