[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: fkhadra\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "**Do you want to request a _feature_ or report a _bug_?**\n\n**What is the current behavior?**\n\n**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your [Stackblitz](https://stackblitz.com/edit/react-toastify-getting-started) example below:**\n\n**What is the expected behavior?**\n\n**Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?**\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "**Before submitting a pull request,** please make sure the following is done:\n\n1. Fork [the repository](https://github.com/fkhadra/react-toastify) and create your branch from `main`.\n2. Run `pnpm i` in the repository root.\n3. If you've fixed a bug or added code that should be tested, add tests!\n4. Ensure the test suite passes (`pnpm test`).\n5. Run `pnpm start` to test your changes in the playground.\n6. Update the readme is needed\n7. Update the typescript definition is needed\n8. Format your code with [prettier](https://github.com/prettier/prettier) (`pnpm prettier`).\n9. Make sure your code lints (`pnpm lint:fix`).\n\nFor new features, please make sure that there is an issue related to it.\n\n**Learn more about contributing [here](https://github.com/fkhadra/react-toastify/blob/master/CONTRIBUTING.md)**\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "name: React-toastify CI\n\non: [pull_request, push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n  \n    steps:\n      - uses: actions/checkout@v4.2.2\n      - name: Install node\n        uses: actions/setup-node@v4.1.0\n        with:\n            node-version: '22.x'\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n        with:\n            version: 9 \n      - name: Install dependencies\n        run: pnpm i \n      # - name: Lint\n      #   run: yarn lint\n      - name: Setup\n        run: pnpm run setup\n      - name: Build\n        run: pnpm build\n      - name: Test\n        run: pnpm run test:run \n      - uses: actions/upload-artifact@v3\n        if: failure()\n        with:\n          name: cypress-screenshots\n          path: cypress/screenshots\n      - uses: actions/upload-artifact@v3\n        if: always()\n        with:\n          name: cypress-videos\n          path: cypress/videos\n      - name: Coveralls GitHub Action\n        uses: coverallsapp/github-action@v2.3.4\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\nnode_modules/\nlib/\n.sass-cache/\nnpm-debug.log\ncoverage/\nyarn-error.log\n.DS_STORE\ncjs/\nesm/\ndist/\n.cache\n/addons\n.nyc_output\ncypress/videos/*\ncypress/screenshots/*\n.husky"
  },
  {
    "path": ".nycrc.json",
    "content": "{\n  \"all\": true,\n  \"extends\": \"@istanbuljs/nyc-config-typescript\",\n  \"check-coverage\": true,\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"cypress/**/*.*\",\n    \"src/types.ts\",\n    \"**/*.d.ts\",\n    \"**/*.cy.tsx\",\n    \"**/*.cy.ts\"\n  ]\n}"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at fdkhadra@gmail.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing \n\n:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:\n\nWhen contributing to this repository, please first discuss the change you wish to make via issue before making a change. \n\nPlease note we have a code of conduct, please follow it in all your interactions with the project.\n\n## General Guidelines\n\n- Before starting to work on something, please open an issue first\n- If adding a new feature, write the corresponding test\n- Ensure that nothing get broke. You can use the playground for that\n- If applicable, update the [documentation](https://github.com/fkhadra/react-toastify-doc)\n- Use prettier before committing 😭\n- When solving a bug, please provide the steps to reproduce it(codesandbox or stackblitz are our best friends for that)\n- Tchill 👌\n\n## Setup\n\n### Pre-requisites\n\n- *Node:* `^18.0.0`\n- *Yarn*\n\n### Install\n\nClone the repository and create a local branch:\n\n```sh\ngit clone https://github.com/fkhadra/react-toastify.git\ncd react-toastify\n\ngit checkout -b my-branch\n```\n\nInstall dependencies:\n\n```sh\npnpm install \n// then\npnpm setup \n```\n\n## Developing\n\n```sh\n# launch the playground\npnpm start\n\n# Run tests 💩\npnpm test\n\n# Prettify all the things\npnpm prettier\n```\n\n### Playground dir\n\nThe playground let you test your changes, it's like the demo of react-toastify. Most of the time you don't need to modify it unless you add new features.\n\n### Src\n\n- [toast:](https://github.com/fkhadra/react-toastify/blob/main/src/core/toast.ts) Contain the exposed api (`toast.success...`).\n\n## License\nBy contributing, you agree that your contributions will be licensed under its [MIT License](https://github.com/fkhadra/react-toastify/blob/main/LICENSE).\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Fadi Khadra\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# React-Toastify\n\n[![Financial Contributors on Open Collective](https://opencollective.com/react-toastify/all/badge.svg?label=financial+contributors)](https://opencollective.com/react-toastify) ![React-toastify CI](https://github.com/fkhadra/react-toastify/workflows/React-toastify%20CI/badge.svg)\n![npm](https://img.shields.io/npm/dm/react-toastify.svg?label=%E2%8F%ACdownloads&style=for-the-badge)\n![npm](https://img.shields.io/npm/v/react-toastify.svg?style=for-the-badge)\n![NPM](https://img.shields.io/npm/l/react-toastify.svg?label=%F0%9F%93%9Clicense&style=for-the-badge)\n![Coveralls github](https://img.shields.io/coveralls/github/fkhadra/react-toastify.svg?label=%E2%9B%B1coverage&style=for-the-badge)\n\n\n![React toastify](https://user-images.githubusercontent.com/5574267/130804494-a9d2d69c-f170-4576-b2e1-0bb7f13dd92d.gif \"React toastify\")\n\n![stacked](https://github.com/fkhadra/react-toastify/assets/5574267/975c7c01-b95e-43cf-9100-256fa8ef2760)\n\n![custom-style](https://github.com/user-attachments/assets/311672f7-f98a-46f3-a2ab-a9d1a05186a7)\n\n🎉 React-Toastify allows you to add notifications to your app with ease.\n\n## Installation\n\n```\n$ npm install --save react-toastify\n$ yarn add react-toastify\n```\n\n```jsx\n  import React from 'react';\n\n  import { ToastContainer, toast } from 'react-toastify';\n  \n  function App(){\n    const notify = () => toast(\"Wow so easy!\");\n\n    return (\n      <div>\n        <button onClick={notify}>Notify!</button>\n        <ToastContainer />\n      </div>\n    );\n  }\n```\n\n## Documentation\n\nCheck the [documentation](https://fkhadra.github.io/react-toastify/introduction) to get you started!\n\n## Features\n\n- Easy to set up for real, you can make it work in less than 10sec!\n- Super easy to customize\n- RTL support\n- Swipe to close 👌\n- Can choose swipe direction\n- Super easy to use an animation of your choice. Works well with animate.css for example\n- Can display a react component inside the toast!\n- Has ```onOpen``` and ```onClose``` hooks. Both can access the props passed to the react component rendered inside the toast\n- Can remove a toast programmatically\n- Define behavior per toast\n- Pause toast when the window loses focus 👁\n- Fancy progress bar to display the remaining time\n- Possibility to update a toast\n- You can control the progress bar a la `nprogress` 😲\n- You can limit the number of toast displayed at the same time\n- Dark mode 🌒\n- Pause timer programmaticaly \n- Stacked notifications!\n- And much more !\n\n## Demo\n\n[A demo is worth a thousand words](https://fkhadra.github.io/react-toastify/introduction)\n\n\n## Contribute\n\nShow your ❤️ and support by giving a ⭐. Any suggestions are welcome! Take a look at the contributing guide.\n\nYou can also find me on [reactiflux](https://www.reactiflux.com/). My pseudo is Fadi.\n\n## Contributors\n\n### Code Contributors\n\nThis project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].\n<a href=\"https://github.com/fkhadra/react-toastify/graphs/contributors\"><img src=\"https://opencollective.com/react-toastify/contributors.svg?width=890&button=false\" /></a>\n\n### Financial Contributors\n\nBecome a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/react-toastify/contribute)]\n\n#### Individuals\n\n<a href=\"https://opencollective.com/react-toastify\"><img src=\"https://opencollective.com/react-toastify/individuals.svg?width=890\"></a>\n\n#### Organizations\n\nSupport this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/react-toastify/contribute)]\n\n<a href=\"https://opencollective.com/react-toastify/organization/0/website\"><img src=\"https://opencollective.com/react-toastify/organization/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/1/website\"><img src=\"https://opencollective.com/react-toastify/organization/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/2/website\"><img src=\"https://opencollective.com/react-toastify/organization/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/3/website\"><img src=\"https://opencollective.com/react-toastify/organization/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/4/website\"><img src=\"https://opencollective.com/react-toastify/organization/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/5/website\"><img src=\"https://opencollective.com/react-toastify/organization/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/6/website\"><img src=\"https://opencollective.com/react-toastify/organization/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/7/website\"><img src=\"https://opencollective.com/react-toastify/organization/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/8/website\"><img src=\"https://opencollective.com/react-toastify/organization/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/react-toastify/organization/9/website\"><img src=\"https://opencollective.com/react-toastify/organization/9/avatar.svg\"></a>\n\n## Release Notes\n\nYou can find the release note for the latest release [here](https://github.com/fkhadra/react-toastify/releases/latest)\n\nYou can browse them all [here](https://github.com/fkhadra/react-toastify/releases)\n\n## License\n\nLicensed under MIT\n"
  },
  {
    "path": "cypress/support/commands.ts",
    "content": "/// <reference types=\"cypress\" />\n/// <reference types=\"@testing-library/cypress\" />\n// ***********************************************\n// This example commands.ts shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add('login', (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This will overwrite an existing command --\n// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })\n//\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      resolveEntranceAnimation(): void;\n    }\n  }\n}\n\nimport '@4tw/cypress-drag-drop';\nimport '@testing-library/cypress/add-commands';\n\nCypress.Commands.add('resolveEntranceAnimation', () => {\n  cy.wait(800);\n});\n"
  },
  {
    "path": "cypress/support/component-index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <title>Components App</title>\n  </head>\n  <body>\n    <div data-cy-root></div>\n  </body>\n</html>"
  },
  {
    "path": "cypress/support/component.ts",
    "content": "// ***********************************************************\n// This example support/component.ts is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\n// cypress/support/e2e.js\nimport '@cypress/code-coverage/support';\nimport './commands';\nimport './style.css';\nimport '../../src/style.css';\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n\nimport { mount } from 'cypress/react18';\n\n// Augment the Cypress namespace to include type definitions for\n// your custom command.\n// Alternatively, can be defined in cypress/support/component.d.ts\n// with a <reference path=\"./component\" /> at the top of your spec.\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      mount: typeof mount;\n    }\n  }\n}\n\nCypress.Commands.add('mount', mount);\n\n// Example use:\n// cy.mount(<MyComponent />)\n"
  },
  {
    "path": "cypress/support/style.css",
    "content": "\n[data-cy-root]{\n  height: 80vh;\n}"
  },
  {
    "path": "cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  component: {\n    setupNodeEvents(on, config) {\n      require('@cypress/code-coverage/task')(on, config);\n      return config;\n    },\n    devServer: {\n      framework: 'react',\n      bundler: 'vite'\n    }\n  }\n});\n"
  },
  {
    "path": "lefthook.yml",
    "content": " pre-commit:\n   parallel: true\n   commands:\n     lint-staged:\n       glob: \"*.{js,ts,jsx,tsx,css}\"\n       run: pnpm lint-staged"
  },
  {
    "path": "package.json",
    "content": "{\n  \"version\": \"11.0.5\",\n  \"license\": \"MIT\",\n  \"description\": \"React notification made easy\",\n  \"keywords\": [\n    \"react\",\n    \"notification\",\n    \"toast\",\n    \"react-component\",\n    \"react-toastify\",\n    \"push\",\n    \"alert\",\n    \"snackbar\",\n    \"message\"\n  ],\n  \"files\": [\n    \"dist\",\n    \"addons\"\n  ],\n  \"scripts\": {\n    \"prepare\": \"lefthook install\",\n    \"setup\": \"pnpm link .\",\n    \"start\": \"cd playground && pnpm dev\",\n    \"test\": \"cypress open --component\",\n    \"test:run\": \"cypress run --component -b chrome\",\n    \"prettier\": \"prettier --write src\",\n    \"build\": \"tsup && cp src/style.css dist/ReactToastify.css && rm dist/unstyled.css*\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^18 || ^19\",\n    \"react-dom\": \"^18 || ^19\"\n  },\n  \"prettier\": {\n    \"printWidth\": 120,\n    \"semi\": true,\n    \"singleQuote\": true,\n    \"trailingComma\": \"none\",\n    \"arrowParens\": \"avoid\"\n  },\n  \"name\": \"react-toastify\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/fkhadra/react-toastify.git\"\n  },\n  \"author\": \"Fadi Khadra <fdkhadra@gmail.com> (https://fkhadra.github.io)\",\n  \"bugs\": {\n    \"url\": \"https://github.com/fkhadra/react-toastify/issues\"\n  },\n  \"homepage\": \"https://github.com/fkhadra/react-toastify#readme\",\n  \"devDependencies\": {\n    \"@4tw/cypress-drag-drop\": \"^2.2.5\",\n    \"@cypress/code-coverage\": \"^3.13.9\",\n    \"@istanbuljs/nyc-config-typescript\": \"^1.0.2\",\n    \"@testing-library/cypress\": \"^10.0.2\",\n    \"@types/node\": \"^22.10.2\",\n    \"@types/react\": \"^19.0.1\",\n    \"@types/react-dom\": \"^19.0.2\",\n    \"@vitejs/plugin-react\": \"^4.3.4\",\n    \"coveralls\": \"^3.1.1\",\n    \"cypress\": \"^13.16.1\",\n    \"lefthook\": \"^1.9.2\",\n    \"lint-staged\": \"^15.2.11\",\n    \"postcss\": \"^8.4.49\",\n    \"prettier\": \"3.4.2\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"tsup\": \"^8.3.5\",\n    \"typescript\": \"^5.7.2\",\n    \"vite\": \"^6.0.3\",\n    \"vite-plugin-istanbul\": \"^6.0.2\"\n  },\n  \"dependencies\": {\n    \"clsx\": \"^2.1.1\"\n  },\n  \"main\": \"dist/index.js\",\n  \"typings\": \"dist/index.d.ts\",\n  \"module\": \"dist/index.mjs\",\n  \"source\": \"src/index.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.js\"\n    },\n    \"./unstyled\": {\n      \"types\": \"./dist/unstyled.d.ts\",\n      \"import\": \"./dist/unstyled.mjs\",\n      \"require\": \"./dist/unstyled.js\"\n    },\n    \"./dist/ReactToastify.css\": \"./dist/ReactToastify.css\",\n    \"./ReactToastify.css\": \"./dist/ReactToastify.css\",\n    \"./package.json\": \"./package.json\",\n    \"./addons/use-notification-center\": {\n      \"types\": \"./addons/use-notification-center/index.d.ts\",\n      \"import\": \"./addons/use-notification-center/index.mjs\",\n      \"require\": \"./addons/use-notification-center/index.js\"\n    },\n    \"./notification-center\": {\n      \"types\": \"./addons/use-notification-center/index.d.ts\",\n      \"import\": \"./addons/use-notification-center/index.mjs\",\n      \"require\": \"./addons/use-notification-center/index.js\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.{js,jsx,ts,tsx,md,html,css}\": \"prettier --write\"\n  }\n}\n"
  },
  {
    "path": "playground/.eslintrc.cjs",
    "content": "module.exports = {\n  env: { browser: true, es2020: true },\n  extends: [\n    'eslint:recommended',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:react-hooks/recommended',\n  ],\n  parser: '@typescript-eslint/parser',\n  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },\n  plugins: ['react-refresh'],\n  rules: {\n    'react-refresh/only-export-components': 'warn',\n  },\n}\n"
  },
  {
    "path": "playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + React + TS</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"lint\": \"eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^19.0.0\",\n    \"@types/react-dom\": \"^19.0.0\",\n    \"@vitejs/plugin-react\": \"^4.3.4\",\n    \"typescript\": \"^5.7.2\",\n    \"vite\": \"^6.0.1\"\n  }\n}\n"
  },
  {
    "path": "playground/src/components/App.tsx",
    "content": "/**\n * The playground could use some love 💖. To the brave soul reading this\n * message, any help would be appreciated 🙏\n *\n * The code is full of bad assertion 😆\n */\n\nimport { Checkbox } from './Checkbox';\nimport { ContainerCode, ContainerCodeProps } from './ContainerCode';\nimport { Header } from './Header';\nimport { Radio } from './Radio';\nimport { ToastCode, ToastCodeProps } from './ToastCode';\nimport { flags, positions, themes, transitions, typs } from './constants';\n\nimport React from 'react';\nimport { Id, toast, ToastContainer } from '../../../src';\nimport { defaultProps } from '../../../src/components/ToastContainer';\n\n// Attach to window. Can be useful to debug\n// @ts-ignore\nwindow.toast = toast;\n\nclass App extends React.Component {\n  state = App.getDefaultState();\n  toastId: Id;\n  resolvePromise = true;\n\n  static getDefaultState() {\n    return {\n      ...defaultProps,\n      transition: 'bounce',\n      type: 'default',\n      progress: '',\n      disableAutoClose: false,\n      limit: 0,\n      theme: 'light'\n    };\n  }\n\n  handleReset = () =>\n    this.setState({\n      ...App.getDefaultState()\n    });\n\n  clearAll = () => toast.dismiss();\n\n  showToast = () => {\n    this.toastId =\n      this.state.type === 'default'\n        ? toast('🦄 Wow so easy !', { progress: this.state.progress })\n        : toast[this.state.type]('🚀 Wow so easy !', {\n            progress: this.state.progress\n          });\n  };\n\n  firePromise = () => {\n    toast.promise(\n      new Promise((resolve, reject) => {\n        setTimeout(() => {\n          this.resolvePromise ? resolve(null) : reject(null);\n          this.resolvePromise = !this.resolvePromise;\n        }, 3000);\n      }),\n      {\n        pending: 'Promise is pending',\n        success: 'Promise resolved 👌',\n        error: 'Promise rejected 🤯'\n      }\n    );\n  };\n\n  updateToast = () => toast.update(this.toastId, { progress: this.state.progress });\n\n  handleAutoCloseDelay = e =>\n    this.setState({\n      autoClose: e.target.value > 0 ? parseInt(e.target.value, 10) : 1\n    });\n\n  isDefaultProps() {\n    return (\n      this.state.position === 'top-right' &&\n      this.state.autoClose === 5000 &&\n      !this.state.disableAutoClose &&\n      !this.state.hideProgressBar &&\n      !this.state.newestOnTop &&\n      !this.state.rtl &&\n      this.state.pauseOnFocusLoss &&\n      this.state.pauseOnHover &&\n      this.state.closeOnClick &&\n      this.state.draggable &&\n      this.state.theme === 'light'\n    );\n  }\n\n  handleRadioOrSelect = e =>\n    this.setState({\n      [e.target.name]: e.target.name === 'limit' ? parseInt(e.target.value, 10) : e.target.value\n    });\n\n  toggleCheckbox = e =>\n    this.setState({\n      [e.target.name]: !this.state[e.target.name]\n    });\n\n  renderFlags() {\n    return flags.map(({ id, label }) => (\n      <li key={id}>\n        <Checkbox id={id} label={label} onChange={this.toggleCheckbox} checked={this.state[id]} />\n      </li>\n    ));\n  }\n\n  render() {\n    return (\n      <main>\n        <Header />\n        <div className=\"container\">\n          <p>\n            By default, all toasts will inherit ToastContainer's props. Props defined on toast supersede\n            ToastContainer's props. Props marked with * can only be set on the ToastContainer. The demo is not\n            exhaustive, check the repo for more!\n          </p>\n          <section className=\"container__options\">\n            <div>\n              <h3>Position</h3>\n              <ul>\n                <Radio\n                  options={positions}\n                  name=\"position\"\n                  checked={this.state.position as string}\n                  onChange={this.handleRadioOrSelect}\n                />\n              </ul>\n            </div>\n            <div>\n              <h3>Type</h3>\n              <ul>\n                <Radio options={typs} name=\"type\" checked={this.state.type} onChange={this.handleRadioOrSelect} />\n              </ul>\n            </div>\n            <div>\n              <h3>Options</h3>\n              <div className=\"options_wrapper\">\n                <label htmlFor=\"autoClose\">\n                  Delay\n                  <input\n                    type=\"number\"\n                    name=\"autoClose\"\n                    id=\"autoClose\"\n                    value={this.state.autoClose as unknown as string}\n                    onChange={this.handleAutoCloseDelay}\n                    disabled={this.state.disableAutoClose}\n                  />\n                  ms\n                </label>\n                <label htmlFor=\"transition\">\n                  Transition\n                  <select\n                    name=\"transition\"\n                    id=\"transition\"\n                    onChange={this.handleRadioOrSelect}\n                    value={this.state.transition}\n                  >\n                    {Object.keys(transitions).map(k => (\n                      <option key={k} value={k}>\n                        {k}\n                      </option>\n                    ))}\n                  </select>\n                </label>\n                <label htmlFor=\"theme\">\n                  Theme\n                  <select name=\"theme\" id=\"theme\" onChange={this.handleRadioOrSelect} value={this.state.theme}>\n                    {themes.map(k => (\n                      <option key={k} value={k}>\n                        {k}\n                      </option>\n                    ))}\n                  </select>\n                </label>\n                <label htmlFor=\"progress\">\n                  Progress\n                  <input\n                    type=\"number\"\n                    name=\"progress\"\n                    id=\"progress\"\n                    value={this.state.progress}\n                    onChange={this.handleRadioOrSelect}\n                  />\n                </label>\n                <label htmlFor=\"limit\">\n                  Limit\n                  <input\n                    type=\"number\"\n                    name=\"limit\"\n                    id=\"limit\"\n                    value={this.state.limit}\n                    onChange={this.handleRadioOrSelect}\n                  />\n                </label>\n              </div>\n              <ul>{this.renderFlags()}</ul>\n            </div>\n          </section>\n          <section>\n            <ContainerCode\n              {...(this.state as unknown as ContainerCodeProps)}\n              isDefaultProps={this.isDefaultProps() as boolean}\n            />\n            <ToastCode {...(this.state as unknown as ToastCodeProps)} />\n          </section>\n          <div className=\"cta__wrapper\">\n            <ul className=\"container__actions\">\n              <li>\n                <button className=\"btn\" onClick={this.showToast}>\n                  <span role=\"img\" aria-label=\"show alert\">\n                    🚀\n                  </span>{' '}\n                  Show Toast\n                </button>\n              </li>\n              <li>\n                <button className=\"btn\" onClick={this.firePromise}>\n                  Promise\n                </button>\n              </li>\n              <li>\n                <button className=\"btn\" onClick={this.updateToast}>\n                  Update\n                </button>\n              </li>\n              <li>\n                <button className=\"btn bg-red\" onClick={this.clearAll}>\n                  <span role=\"img\" aria-label=\"clear all\">\n                    💩\n                  </span>{' '}\n                  Clear All\n                </button>\n              </li>\n              <li>\n                <button className=\"btn bg-blue\" onClick={this.handleReset}>\n                  <span role=\"img\" aria-label=\"reset options\">\n                    🔄\n                  </span>{' '}\n                  Reset\n                </button>\n              </li>\n            </ul>\n          </div>\n        </div>\n        <ToastContainer\n          {...this.state}\n          transition={transitions[this.state.transition]}\n          autoClose={this.state.disableAutoClose ? false : this.state.autoClose}\n        />\n        <ToastContainer containerId=\"xxx\" position=\"top-left\" autoClose={false} theme=\"dark\" limit={3} />\n        <ToastContainer limit={3} containerId=\"yyy\" autoClose={false} position=\"top-right\" />\n      </main>\n    );\n  }\n}\n\nexport { App };\n"
  },
  {
    "path": "playground/src/components/Checkbox.tsx",
    "content": "import * as React from 'react';\n\ninterface CheckboxProps {\n  label: string;\n  id: string;\n  checked: boolean;\n  onChange: (e: React.ChangeEvent) => void;\n}\n\nexport const Checkbox = ({ label, onChange, id, checked }: CheckboxProps) => (\n  <label htmlFor={id}>\n    <input\n      id={id}\n      type=\"checkbox\"\n      name={id}\n      checked={checked}\n      onChange={onChange}\n    />\n    {label}\n  </label>\n);\n"
  },
  {
    "path": "playground/src/components/ContainerCode.tsx",
    "content": "import * as React from 'react';\nimport { ToastContainerProps } from '../../../src';\n\nfunction getProp<L, R>(prop: L, value: R) {\n  return value ? (\n    <div>\n      <span className=\"code__props\">{prop}</span>\n    </div>\n  ) : (\n    <div>\n      <span className=\"code__props\">{prop}</span>\n      {`={false}`}\n    </div>\n  );\n}\n\nexport interface ContainerCodeProps extends Partial<ToastContainerProps> {\n  isDefaultProps: boolean;\n  disableAutoClose: boolean;\n}\n\nexport const ContainerCode: React.FC<ContainerCodeProps> = ({\n  position,\n  disableAutoClose,\n  autoClose,\n  hideProgressBar,\n  newestOnTop,\n  closeOnClick,\n  pauseOnHover,\n  rtl,\n  pauseOnFocusLoss,\n  isDefaultProps,\n  draggable,\n  theme\n}) => (\n  <div>\n    <h3>Toast Container</h3>\n    <div className=\"code\">\n      <div>\n        <span>{`<`}</span>\n        <span className=\"code__component\">ToastContainer</span>\n      </div>\n      <div>\n        <span className=\"code__props\">position</span>\n        {`=\"${position}\"`}\n      </div>\n      <div>\n        <span className=\"code__props\">theme</span>\n        {`=\"${theme}\"`}\n      </div>\n      <div>\n        <span className=\"code__props\">autoClose</span>\n        {`={${disableAutoClose ? false : autoClose}}`}\n      </div>\n      {!disableAutoClose ? getProp('hideProgressBar', hideProgressBar) : ''}\n      {getProp('newestOnTop', newestOnTop)}\n      {getProp('closeOnClick', closeOnClick)}\n      {getProp('rtl', rtl)}\n      {getProp('pauseOnFocusLoss', pauseOnFocusLoss)}\n      {getProp('draggable', draggable)}\n      {!disableAutoClose ? getProp('pauseOnHover', pauseOnHover) : ''}\n      <div>\n        <span>{`/>`}</span>\n      </div>\n      {isDefaultProps && (\n        <div>\n          <div>{`{/* Same as */}`}</div>\n          <span>{`<`}</span>\n          <span className=\"code__component\">ToastContainer</span>\n          <span>{'/>'}</span>\n        </div>\n      )}\n    </div>\n  </div>\n);\n"
  },
  {
    "path": "playground/src/components/Header.tsx",
    "content": "import * as React from 'react';\n\nexport const Header = () => (\n  <header>\n    <a\n      href=\"https://github.com/fkhadra/react-toastify\"\n      className=\"github-corner\"\n      aria-label=\"View source on Github\"\n    >\n      <svg\n        width=\"80\"\n        height=\"80\"\n        viewBox=\"0 0 250 250\"\n        style={{\n          fill: '#FD6C6C',\n          color: '#fff',\n          position: 'absolute',\n          top: 0,\n          border: 0,\n          left: 0,\n          transform: 'scale(-1, 1)'\n        }}\n        aria-hidden=\"true\"\n      >\n        <path d=\"M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z\" />\n        <path\n          d=\"M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2\"\n          fill=\"currentColor\"\n          style={{ transformOrigin: '130px 106px' }}\n          className=\"octo-arm\"\n        />\n        <path\n          d=\"M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z\"\n          fill=\"currentColor\"\n          className=\"octo-body\"\n        />\n      </svg>\n    </a>\n    <h1>Welcome to React-toastify</h1>\n    <h5>React notification made easy !</h5>\n    <div>\n      <a className=\"btn\" href=\"https://fkhadra.github.io/react-toastify\">\n        <span role=\"img\" aria-label=\"link to github\">\n          📖\n        </span>{' '}\n        Documentation\n      </a>\n      <a\n        className=\"btn\"\n        href=\"https://github.com/fkhadra/react-toastify/stargazers\"\n      >\n        <span role=\"img\" aria-label=\"link to github\">\n          ⭐️\n        </span>{' '}\n        Become a stargazer\n      </a>\n    </div>\n  </header>\n);\n"
  },
  {
    "path": "playground/src/components/Radio.tsx",
    "content": "import * as React from 'react';\n\ninterface RadioProps {\n  options: Record<string, string>;\n  name: string;\n  onChange: (e: React.ChangeEvent) => void;\n  checked: string | boolean;\n}\n\nexport const Radio = ({\n  options,\n  name,\n  onChange,\n  checked = false\n}: RadioProps) => (\n  <>\n    {Object.keys(options).map(k => {\n      const option = options[k];\n\n      return (\n        <li key={`${name}-${option}`}>\n          <label htmlFor={option}>\n            <input\n              id={option}\n              type=\"radio\"\n              name={name}\n              value={option}\n              checked={option === checked}\n              onChange={onChange}\n            />\n            {option}\n          </label>\n        </li>\n      );\n    })}\n  </>\n);\n"
  },
  {
    "path": "playground/src/components/ToastCode.tsx",
    "content": "import * as React from 'react';\n\nimport { themes } from './constants';\n\nfunction getType(type: string) {\n  switch (type) {\n    case 'default':\n    default:\n      return 'toast';\n    case 'success':\n      return 'toast.success';\n    case 'error':\n      return 'toast.error';\n    case 'info':\n      return 'toast.info';\n    case 'warning':\n      return 'toast.warn';\n  }\n}\n\nexport interface ToastCodeProps {\n  position: string;\n  disableAutoClose: boolean;\n  autoClose: boolean | number;\n  hideProgressBar: boolean;\n  closeOnClick: boolean;\n  pauseOnHover: boolean;\n  type: string;\n  draggable: boolean;\n  progress: number;\n  theme: typeof themes[number];\n}\n\nexport const ToastCode: React.FC<ToastCodeProps> = ({\n  position,\n  disableAutoClose,\n  autoClose,\n  hideProgressBar,\n  closeOnClick,\n  pauseOnHover,\n  type,\n  draggable,\n  progress,\n  theme\n}) => (\n  <div>\n    <h3>Toast Emitter</h3>\n    <div className=\"code\">\n      <div>\n        <span className=\"code__component\">{getType(type)}</span>\n        {`('🦄 Wow so easy!', { `}\n      </div>\n      <div>\n        <span className=\"code__props\">position</span>\n        {`: \"${position}\"`},\n      </div>\n      <div>\n        <span className=\"code__props\">theme</span>\n        {`: \"${theme}\"`},\n      </div>\n      <div>\n        <span className=\"code__props\">autoClose</span>\n        {`: ${disableAutoClose ? false : autoClose}`},\n      </div>\n      <div>\n        <span className=\"code__props\">hideProgressBar</span>\n        {`: ${hideProgressBar ? 'true' : 'false'}`},\n      </div>\n      <div>\n        <span className=\"code__props\">closeOnClick</span>\n        {`: ${closeOnClick ? 'true' : 'false'}`},\n      </div>\n      <div>\n        <span className=\"code__props\">pauseOnHover</span>\n        {`: ${pauseOnHover ? 'true' : 'false'}`},\n      </div>\n      <div>\n        <span className=\"code__props\">draggable</span>\n        {`: ${draggable ? 'true' : 'false'}`},\n      </div>\n      {!Number.isNaN(progress) && (\n        <div>\n          <span className=\"code__props\">progress</span>\n          {`: ${progress}`},\n        </div>\n      )}\n      <div>{`});`}</div>\n    </div>\n  </div>\n);\n"
  },
  {
    "path": "playground/src/components/constants.ts",
    "content": "import { Bounce, Slide, Flip, Zoom } from '../../../src/index';\n\nexport const flags = [\n  {\n    id: 'disableAutoClose',\n    label: 'Disable auto-close'\n  },\n  {\n    id: 'hideProgressBar',\n    label: 'Hide progress bar(less fanciness!)'\n  },\n  {\n    id: 'newestOnTop',\n    label: 'Newest on top*'\n  },\n  {\n    id: 'closeOnClick',\n    label: 'Close on click'\n  },\n  {\n    id: 'pauseOnHover',\n    label: 'Pause delay on hover'\n  },\n  {\n    id: 'pauseOnFocusLoss',\n    label: 'Pause toast when the window loses focus'\n  },\n  {\n    id: 'rtl',\n    label: 'Right to left layout*'\n  },\n  {\n    id: 'draggable',\n    label: 'Allow to drag and close the toast'\n  }\n];\n\nexport const transitions = {\n  bounce: Bounce,\n  slide: Slide,\n  zoom: Zoom,\n  flip: Flip\n};\n\nexport const themes = <const>['light', 'dark', 'colored'];\n\nexport const positions = {\n  TOP_LEFT: 'top-left',\n  TOP_RIGHT: 'top-right',\n  TOP_CENTER: 'top-center',\n  BOTTOM_LEFT: 'bottom-left',\n  BOTTOM_RIGHT: 'bottom-right',\n  BOTTOM_CENTER: 'bottom-center'\n};\n\nexport const typs = {\n  INFO: 'info',\n  SUCCESS: 'success',\n  WARNING: 'warning',\n  ERROR: 'error',\n  DEFAULT: 'default'\n};\n"
  },
  {
    "path": "playground/src/index.css",
    "content": "@import url(https://fonts.googleapis.com/css?family=Titillium+Web);\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: 'Titillium Web', sans-serif;\n  min-height: 100vh;\n  background: linear-gradient(110deg, #1d4350, #a43931);\n  color: #fff;\n}\n\n* {\n  box-sizing: border-box;\n}\n\nmain {\n  display: grid;\n  grid-template-rows: auto 1fr;\n  grid-gap: 20px;\n}\n\nheader {\n  background: #222;\n  text-align: center;\n  padding: 30px 0;\n}\n\nh3 {\n  color: #fff;\n}\n\nheader h1 {\n  margin-top: 0;\n}\n\nul {\n  list-style: none;\n  padding: 0;\n}\n\ninput[type='number'] {\n  padding: 8px;\n  background-color: transparent;\n  box-shadow: none;\n  border: 1px solid;\n  margin: 0 5px;\n  border-radius: 5px;\n  border-color: #ac557b;\n  color: #fff;\n  width: 100px;\n}\n\ninput[type='radio'] {\n  margin-right: 8px;\n}\n\nselect {\n  padding: 8px;\n  padding: 8px;\n  background-color: transparent;\n  box-shadow: none;\n  border: 1px solid;\n  border-top-color: currentcolor;\n  border-right-color: currentcolor;\n  border-bottom-color: currentcolor;\n  border-left-color: currentcolor;\n  border-top-color: currentcolor;\n  border-right-color: currentcolor;\n  border-bottom-color: currentcolor;\n  border-left-color: currentcolor;\n  margin: 0 5px;\n  border-radius: 5px;\n  border-color: #ac557b;\n  color: #fff;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n}\n\n.container {\n  max-width: 1080px;\n  margin: auto;\n  width: 100%;\n  background: rgba(255, 255, 255, 0.1);\n  padding: 20px;\n  border-radius: 10px;\n  display: grid;\n  grid-template-columns: repeat(2, 1fr);\n  gap: 10px;\n}\n\n.container p {\n  grid-column: 1 / -1;\n  font-size: 13px;\n  font-style: italic;\n  background: #222;\n  padding: 5px;\n  border-left: 3px solid #a9547e;\n}\n\n.container__options {\n  display: grid;\n  grid-template-columns: repeat(2, 1fr);\n}\n\n.container__options div:last-child {\n  grid-column: 1 / -1;\n}\n\n.container__actions {\n  display: flex;\n}\n\n.cta__wrapper {\n    grid-column: span 2;\n}\n\n.btn {\n  color: #fff;\n  text-decoration: none;\n  padding: 8px 16px;\n  margin: 0 15px 0 0;\n  background: linear-gradient(100deg, #e96443, #904e95);\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),\n    0 3px 1px -2px rgba(0, 0, 0, 0.2);\n  border: none;\n  text-transform: capitalize;\n  cursor: pointer;\n  transition: transform 0.3s;\n  min-width: 120px;\n}\n\n.btn:hover {\n  transform: scale(1.1);\n}\n\n.bg-red {\n  background: #d13c3c;\n}\n\n.bg-blue {\n  background: #3b4149;\n}\n\n.code {\n  font-family: 'Source Code Pro', Menlo, Monaco, Courier, monospace;\n  font-size: 12px;\n  line-height: 1.4;\n  font-style: normal;\n  border-left: 3px solid #a9547e;\n  padding-left: 20px;\n  background: #222;\n}\n\n.code__component {\n  color: #66d9ef;\n}\n\n.code__props {\n  color: #a6e22e;\n}\n\n.code div {\n  margin-left: 20px;\n}\n\n.code div:first-child,\n.code div:last-child {\n  margin: 0;\n}\n\n.github-corner:hover .octo-arm {\n  animation: octocat-wave 560ms ease-in-out;\n}\n\n.options_wrapper {\n  display: grid;\n  grid-template-columns: repeat(3, auto);\n  gap: 18px;\n}\n\n@keyframes octocat-wave {\n  0%,\n  100% {\n    transform: rotate(0);\n  }\n  20%,\n  60% {\n    transform: rotate(-25deg);\n  }\n  40%,\n  80% {\n    transform: rotate(10deg);\n  }\n}\n\n@media (max-width: 500px) {\n  .github-corner:hover .octo-arm {\n    animation: none;\n  }\n  .github-corner .octo-arm {\n    animation: octocat-wave 560ms ease-in-out;\n  }\n}\n"
  },
  {
    "path": "playground/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './components/App';\nimport './index.css';\n\nReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);\n"
  },
  {
    "path": "playground/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "playground/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "playground/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "src/addons/use-notification-center/NotificationCenter.cy.tsx",
    "content": "import React from 'react';\n\nimport { toast, ToastContainer } from 'react-toastify';\nimport { NotificationCenterItem, useNotificationCenter, UseNotificationCenterParams } from './useNotificationCenter';\n\nfunction TestComponent(props: UseNotificationCenterParams) {\n  const [content, setContent] = React.useState('');\n  const [updateId, setUpdateId] = React.useState('');\n  const { unreadCount, markAllAsRead, markAsRead, notifications, remove, add, clear, update } = useNotificationCenter(\n    props || {}\n  );\n\n  const flex = {\n    display: 'flex',\n    gap: '1rem',\n    alignItems: 'center'\n  };\n\n  return (\n    <div>\n      <div style={flex}>\n        <button onClick={() => toast('hello')}>display notification</button>\n        <button onClick={markAllAsRead}>markAllAsRead</button>\n        <button onClick={clear}>clear</button>\n        <button onClick={() => add({ content })}>addNotification</button>\n        <button onClick={() => update(updateId, { content })}>updateNotification</button>\n      </div>\n      <ul>\n        <li>\n          <span>count</span>\n          <span data-testid=\"count\">{notifications.length}</span>\n        </li>\n        <li>\n          <span>unread count</span>\n          <span data-testid=\"unreadCount\">{unreadCount}</span>\n        </li>\n      </ul>\n\n      <input data-testid=\"content\" type=\"text\" onChange={e => setContent(e.target.value)} value={content} />\n      <input data-testid=\"updateId\" type=\"text\" onChange={e => setUpdateId(e.target.value)} value={updateId} />\n\n      <ul data-testid=\"notifications\">\n        {notifications.map(el => (\n          <li key={el.id} style={flex}>\n            {/* @ts-ignore */}\n            <span data-testid={`content-${el.id}`}>{el.content}</span>\n            <span data-testid={`read-${el.id}`}>{el.read.toString()}</span>\n            <button data-testid={`markAsRead-${el.id}`} onClick={() => markAsRead(el.id)}>\n              markAsRead\n            </button>\n            <button data-testid={`remove-${el.id}`} onClick={() => remove(el.id)}>\n              remove\n            </button>\n          </li>\n        ))}\n      </ul>\n      <ToastContainer />\n    </div>\n  );\n}\n\ndescribe('NotificationCenter', () => {\n  beforeEach(() => {\n    cy.mount(<TestComponent />);\n  });\n\n  it('listen for new notifications', () => {\n    cy.findByTestId('count').should('contain.text', 0);\n    cy.findByTestId('unreadCount').should('contain.text', 0);\n\n    // hacky asf???\n    cy.wait(1000).then(() => {\n      toast('msg');\n      cy.findByTestId('count').should('contain.text', 1, { timeout: 10000 });\n      cy.findByTestId('unreadCount').should('contain.text', 1);\n    });\n  });\n\n  it('add notification', () => {\n    cy.findByTestId('count').should('contain.text', 0);\n    cy.findByTestId('unreadCount').should('contain.text', 0);\n\n    cy.findByTestId('content').type('something');\n    cy.findByText('addNotification').click();\n\n    cy.findByText('something').should('exist');\n    cy.findByTestId('count').should('contain.text', 1);\n    cy.findByTestId('unreadCount').should('contain.text', 1);\n  });\n\n  it('update', () => {\n    const id = toast('msg');\n\n    cy.resolveEntranceAnimation();\n    cy.findByRole('alert').should('exist');\n\n    setTimeout(() => {\n      toast.update(id, {\n        render: 'msg updated'\n      });\n    }, 0);\n\n    cy.findAllByText('msg updated').should('exist');\n  });\n\n  describe('with initial state', () => {\n    const initialState: NotificationCenterItem[] = [\n      {\n        id: 1,\n        createdAt: Date.now(),\n        read: false,\n        content: 'noti1'\n      },\n      {\n        id: 2,\n        createdAt: Date.now(),\n        read: true,\n        content: 'noti2'\n      }\n    ];\n\n    beforeEach(() => {\n      cy.mount(<TestComponent data={initialState} />);\n    });\n\n    it('handle initial state', () => {\n      cy.findByTestId('count').should('contain.text', initialState.length);\n\n      cy.findByTestId('unreadCount').should('contain.text', 1);\n\n      initialState.forEach(v => {\n        cy.findByText(v.content as string).should('exist');\n      });\n    });\n\n    it('clear all', () => {\n      cy.findByTestId('count').should('contain.text', initialState.length);\n      cy.findByTestId('unreadCount').should('contain.text', 1);\n\n      cy.findByText('clear').click();\n\n      cy.findByTestId('count').should('contain.text', 0);\n      cy.findByTestId('unreadCount').should('contain.text', 0);\n    });\n\n    it('mark all as read', () => {\n      cy.findByTestId('unreadCount').should('contain.text', 1);\n\n      cy.findByText('markAllAsRead').click();\n\n      cy.findByTestId('unreadCount').should('contain.text', 0);\n    });\n  });\n});\n"
  },
  {
    "path": "src/addons/use-notification-center/index.ts",
    "content": "export * from './useNotificationCenter';\n"
  },
  {
    "path": "src/addons/use-notification-center/useNotificationCenter.ts",
    "content": "import { useState, useEffect, useRef } from 'react';\nimport { toast, ToastItem, Id } from 'react-toastify';\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;\n\nexport interface NotificationCenterItem<Data = {}> extends Optional<ToastItem<Data>, 'content' | 'data' | 'status'> {\n  read: boolean;\n  createdAt: number;\n}\n\nexport type SortFn<Data> = (l: NotificationCenterItem<Data>, r: NotificationCenterItem<Data>) => number;\n\nexport type FilterFn<Data = {}> = (item: NotificationCenterItem<Data>) => boolean;\n\nexport interface UseNotificationCenterParams<Data = {}> {\n  /**\n   * initial data to rehydrate the notification center\n   */\n  data?: NotificationCenterItem<Data>[];\n\n  /**\n   * By default, the notifications are sorted from the newest to the oldest using\n   * the `createdAt` field. Use this to provide your own sort function\n   *\n   * Usage:\n   * ```\n   * // old notifications first\n   * useNotificationCenter({\n   *   sort: ((l, r) => l.createdAt - r.createdAt)\n   * })\n   * ```\n   */\n  sort?: SortFn<Data>;\n\n  /**\n   * Keep the toast that meets the condition specified in the callback function.\n   *\n   * Usage:\n   * ```\n   * // keep only the toasts when hidden is set to false\n   * useNotificationCenter({\n   *   filter: item => item.data.hidden === false\n   * })\n   * ```\n   */\n  filter?: FilterFn<Data>;\n}\n\nexport interface UseNotificationCenter<Data> {\n  /**\n   * Contains all the notifications\n   */\n  notifications: NotificationCenterItem<Data>[];\n\n  /**\n   * Clear all notifications\n   */\n  clear(): void;\n\n  /**\n   * Mark all notification as read\n   */\n  markAllAsRead(): void;\n\n  /**\n   * Mark all notification as read or not.\n   *\n   * Usage:\n   * ```\n   * markAllAsRead(false) // mark all notification as not read\n   *\n   * markAllAsRead(true) // same as calling markAllAsRead()\n   * ```\n   */\n  markAllAsRead(read?: boolean): void;\n\n  /**\n   * Mark one or more notifications as read.\n   *\n   * Usage:\n   * ```\n   * markAsRead(\"anId\")\n   * markAsRead([\"a\",\"list\", \"of\", \"id\"])\n   * ```\n   */\n  markAsRead(id: Id | Id[]): void;\n\n  /**\n   * Mark one or more notifications as read.The second parameter let you mark the notification as read or not.\n   *\n   * Usage:\n   * ```\n   * markAsRead(\"anId\", false)\n   * markAsRead([\"a\",\"list\", \"of\", \"id\"], false)\n   *\n   * markAsRead(\"anId\", true) // same as markAsRead(\"anId\")\n   * ```\n   */\n  markAsRead(id: Id | Id[], read?: boolean): void;\n\n  /**\n   * Remove one or more notifications\n   *\n   * Usage:\n   * ```\n   * remove(\"anId\")\n   * remove([\"a\",\"list\", \"of\", \"id\"])\n   * ```\n   */\n  remove(id: Id | Id[]): void;\n\n  /**\n   * Push a notification to the notification center.\n   * Returns null when an item with the given id already exists\n   *\n   * Usage:\n   * ```\n   * const id = add({id: \"id\", content: \"test\", data: { foo: \"hello\" } })\n   *\n   * // Return the id of the notification, generate one if none provided\n   * const id = add({ data: {title: \"a title\", text: \"some text\"} })\n   * ```\n   */\n  add(item: Partial<NotificationCenterItem<Data>>): Id | null;\n\n  /**\n   * Update the notification that match the id\n   * Returns null when no matching notification found\n   *\n   * Usage:\n   * ```\n   * const id = update(\"anId\",  {content: \"test\", data: { foo: \"hello\" } })\n   *\n   * // It's also possible to update the id\n   * const id = update(\"anId\"m { id:\"anotherOne\", data: {title: \"a title\", text: \"some text\"} })\n   * ```\n   */\n  update(id: Id, item: Partial<NotificationCenterItem<Data>>): Id | null;\n\n  /**\n   * Retrieve one or more notifications\n   *\n   * Usage:\n   * ```\n   * find(\"anId\")\n   * find([\"a\",\"list\", \"of\", \"id\"])\n   * ```\n   */\n  find(id: Id): NotificationCenterItem<Data> | undefined;\n\n  /**\n   * Retrieve one or more notifications\n   *\n   * Usage:\n   * ```\n   * find(\"anId\")\n   * find([\"a\",\"list\", \"of\", \"id\"])\n   * ```\n   */\n  find(id: Id[]): NotificationCenterItem<Data>[] | undefined;\n\n  /**\n   * Retrieve the count for unread notifications\n   */\n  unreadCount: number;\n\n  /**\n   * Sort notifications using the newly provided function\n   *\n   * Usage:\n   * ```\n   * // old notifications first\n   * sort((l, r) => l.createdAt - r.createdAt)\n   * ```\n   */\n  sort(sort: SortFn<Data>): void;\n}\n\nexport function useNotificationCenter<Data = {}>(\n  params: UseNotificationCenterParams<Data> = {}\n): UseNotificationCenter<Data> {\n  const sortFn = useRef(params.sort || defaultSort);\n  const filterFn = useRef(params.filter || null);\n  const [notifications, setNotifications] = useState<NotificationCenterItem<Data>[]>(() => {\n    if (params.data) {\n      return filterFn.current\n        ? params.data.filter(filterFn.current).sort(sortFn.current)\n        : [...params.data].sort(sortFn.current);\n    }\n    return [];\n  });\n\n  useEffect(() => {\n    return toast.onChange(item => {\n      if (item.status === 'added' || item.status === 'updated') {\n        const newItem = decorate(item as NotificationCenterItem<Data>);\n        if (filterFn.current && !filterFn.current(newItem)) return;\n\n        setNotifications(prev => {\n          let nextState: NotificationCenterItem<Data>[] = [];\n          const updateIdx = prev.findIndex(v => v.id === newItem.id);\n\n          if (updateIdx !== -1) {\n            nextState = prev.slice();\n            Object.assign(nextState[updateIdx], newItem, {\n              createdAt: Date.now()\n            });\n          } else if (prev.length === 0) {\n            nextState = [newItem];\n          } else {\n            nextState = [newItem, ...prev];\n          }\n          return nextState.sort(sortFn.current);\n        });\n      }\n    });\n  }, []);\n\n  const remove = (id: Id | Id[]) => {\n    setNotifications(prev => prev.filter(Array.isArray(id) ? v => !id.includes(v.id) : v => v.id !== id));\n  };\n\n  const clear = () => {\n    setNotifications([]);\n  };\n\n  const markAllAsRead = (read = true) => {\n    setNotifications(prev =>\n      prev.map(v => {\n        v.read = read;\n        return v;\n      })\n    );\n  };\n\n  const markAsRead = (id: Id | Id[], read = true) => {\n    let map = (v: NotificationCenterItem<Data>) => {\n      if (v.id === id) v.read = read;\n      return v;\n    };\n\n    if (Array.isArray(id)) {\n      map = v => {\n        if (id.includes(v.id)) v.read = read;\n        return v;\n      };\n    }\n\n    setNotifications(prev => prev.map(map));\n  };\n\n  const find = (id: Id | Id[]) => {\n    return Array.isArray(id) ? notifications.filter(v => id.includes(v.id)) : notifications.find(v => v.id === id);\n  };\n\n  const add = (item: Partial<NotificationCenterItem<Data>>) => {\n    if (notifications.find(v => v.id === item.id)) return null;\n\n    const newItem = decorate(item);\n\n    setNotifications(prev => [...prev, newItem].sort(sortFn.current));\n\n    return newItem.id;\n  };\n\n  const update = (id: Id, item: Partial<NotificationCenterItem<Data>>) => {\n    const index = notifications.findIndex(v => v.id === id);\n\n    if (index !== -1) {\n      setNotifications(prev => {\n        const nextState = [...prev];\n        Object.assign(nextState[index], item, {\n          createdAt: item.createdAt || Date.now()\n        });\n\n        return nextState.sort(sortFn.current);\n      });\n\n      return item.id as Id;\n    }\n\n    return null;\n  };\n\n  const sort = (compareFn: SortFn<Data>) => {\n    sortFn.current = compareFn;\n    setNotifications(prev => prev.slice().sort(compareFn));\n  };\n\n  return {\n    notifications,\n    clear,\n    markAllAsRead,\n    markAsRead,\n    add,\n    update,\n    remove,\n    // @ts-ignore fixme: overloading issue\n    find,\n    sort,\n    get unreadCount() {\n      return notifications.reduce((prev, cur) => (!cur.read ? prev + 1 : prev), 0);\n    }\n  };\n}\n\nexport function decorate<Data>(item: NotificationCenterItem<Data> | Partial<NotificationCenterItem<Data>>) {\n  if (item.id == null) item.id = Date.now().toString(36).substring(2, 9);\n  if (!item.createdAt) item.createdAt = Date.now();\n  if (item.read == null) item.read = false;\n  return item as NotificationCenterItem<Data>;\n}\n\n// newest to oldest\nfunction defaultSort<Data>(l: NotificationCenterItem<Data>, r: NotificationCenterItem<Data>) {\n  return r.createdAt - l.createdAt;\n}\n"
  },
  {
    "path": "src/components/CloseButton.cy.tsx",
    "content": "import React from 'react';\nimport { CloseButton } from './CloseButton';\n\ndescribe('CloseButton', () => {\n  it('call close toast when clicking', () => {\n    const closeToast = cy.stub().as('closeToast');\n    cy.mount(<CloseButton closeToast={closeToast} type=\"default\" theme=\"light\" />);\n\n    cy.get('@closeToast').should('not.have.been.called');\n    cy.findByRole('button').click();\n    cy.get('@closeToast').should('have.been.called');\n  });\n\n  it('have a default aria-label', () => {\n    cy.mount(<CloseButton closeToast={cy.stub} type=\"default\" theme=\"light\" />);\n\n    cy.findByLabelText('close').should('exist');\n  });\n\n  it('set aria-label', () => {\n    cy.mount(<CloseButton closeToast={cy.stub} type=\"default\" theme=\"light\" ariaLabel=\"foobar\" />);\n\n    cy.findByLabelText('foobar').should('exist');\n  });\n});\n"
  },
  {
    "path": "src/components/CloseButton.tsx",
    "content": "import React from 'react';\nimport { Default } from '../utils';\nimport { CloseToastFunc, Theme, TypeOptions } from '../types';\n\nexport interface CloseButtonProps {\n  closeToast: CloseToastFunc;\n  type: TypeOptions;\n  ariaLabel?: string;\n  theme: Theme;\n}\n\nexport function CloseButton({ closeToast, theme, ariaLabel = 'close' }: CloseButtonProps) {\n  return (\n    <button\n      className={`${Default.CSS_NAMESPACE}__close-button ${Default.CSS_NAMESPACE}__close-button--${theme}`}\n      type=\"button\"\n      onClick={e => {\n        e.stopPropagation();\n        closeToast(true);\n      }}\n      aria-label={ariaLabel}\n    >\n      <svg aria-hidden=\"true\" viewBox=\"0 0 14 16\">\n        <path\n          fillRule=\"evenodd\"\n          d=\"M7.71 8.23l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75L1 11.98l3.75-3.75L1 4.48 2.48 3l3.75 3.75L9.98 3l1.48 1.48-3.75 3.75z\"\n        />\n      </svg>\n    </button>\n  );\n}\n"
  },
  {
    "path": "src/components/Icons.cy.tsx",
    "content": "import React from 'react';\nimport { TypeOptions } from '../types';\nimport { IconParams, getIcon } from './Icons';\n\nconst props: IconParams = {\n  theme: 'light',\n  type: 'default',\n  isLoading: false\n};\n\ndescribe('Icons', () => {\n  it('handle function', () => {\n    const C = getIcon({\n      ...props,\n      icon: () => <div>icon</div>\n    });\n\n    cy.mount(C);\n    cy.findByText('icon').should('exist');\n  });\n\n  it('handle react element', () => {\n    const C = getIcon({\n      ...props,\n      icon: <div>icon</div>\n    });\n\n    cy.mount(C);\n    cy.findByText('icon').should('exist');\n  });\n\n  it('handle loader', () => {\n    const C = getIcon({\n      ...props,\n      isLoading: true\n    });\n\n    cy.mount(C);\n    cy.get('[data-cy-root]').should('have.length', 1);\n  });\n\n  it('handle built-in icons', () => {\n    for (const t of ['info', 'warning', 'success', 'error', 'spinner']) {\n      const C = getIcon({\n        ...props,\n        type: t as TypeOptions\n      });\n\n      cy.mount(C);\n      cy.get('[data-cy-root]').should('have.length', 1);\n    }\n  });\n});\n"
  },
  {
    "path": "src/components/Icons.tsx",
    "content": "import React, { cloneElement, isValidElement } from 'react';\n\nimport { Theme, ToastProps, TypeOptions } from '../types';\nimport { Default, isFn } from '../utils';\n\n/**\n * Used when providing custom icon\n */\nexport interface IconProps {\n  theme: Theme;\n  type: TypeOptions;\n  isLoading?: boolean;\n}\n\nexport type BuiltInIconProps = React.SVGProps<SVGSVGElement> & IconProps;\n\nconst Svg: React.FC<BuiltInIconProps> = ({ theme, type, isLoading, ...rest }) => (\n  <svg\n    viewBox=\"0 0 24 24\"\n    width=\"100%\"\n    height=\"100%\"\n    fill={theme === 'colored' ? 'currentColor' : `var(--toastify-icon-color-${type})`}\n    {...rest}\n  />\n);\n\nfunction Warning(props: BuiltInIconProps) {\n  return (\n    <Svg {...props}>\n      <path d=\"M23.32 17.191L15.438 2.184C14.728.833 13.416 0 11.996 0c-1.42 0-2.733.833-3.443 2.184L.533 17.448a4.744 4.744 0 000 4.368C1.243 23.167 2.555 24 3.975 24h16.05C22.22 24 24 22.044 24 19.632c0-.904-.251-1.746-.68-2.44zm-9.622 1.46c0 1.033-.724 1.823-1.698 1.823s-1.698-.79-1.698-1.822v-.043c0-1.028.724-1.822 1.698-1.822s1.698.79 1.698 1.822v.043zm.039-12.285l-.84 8.06c-.057.581-.408.943-.897.943-.49 0-.84-.367-.896-.942l-.84-8.065c-.057-.624.25-1.095.779-1.095h1.91c.528.005.84.476.784 1.1z\" />\n    </Svg>\n  );\n}\n\nfunction Info(props: BuiltInIconProps) {\n  return (\n    <Svg {...props}>\n      <path d=\"M12 0a12 12 0 1012 12A12.013 12.013 0 0012 0zm.25 5a1.5 1.5 0 11-1.5 1.5 1.5 1.5 0 011.5-1.5zm2.25 13.5h-4a1 1 0 010-2h.75a.25.25 0 00.25-.25v-4.5a.25.25 0 00-.25-.25h-.75a1 1 0 010-2h1a2 2 0 012 2v4.75a.25.25 0 00.25.25h.75a1 1 0 110 2z\" />\n    </Svg>\n  );\n}\n\nfunction Success(props: BuiltInIconProps) {\n  return (\n    <Svg {...props}>\n      <path d=\"M12 0a12 12 0 1012 12A12.014 12.014 0 0012 0zm6.927 8.2l-6.845 9.289a1.011 1.011 0 01-1.43.188l-4.888-3.908a1 1 0 111.25-1.562l4.076 3.261 6.227-8.451a1 1 0 111.61 1.183z\" />\n    </Svg>\n  );\n}\n\nfunction Error(props: BuiltInIconProps) {\n  return (\n    <Svg {...props}>\n      <path d=\"M11.983 0a12.206 12.206 0 00-8.51 3.653A11.8 11.8 0 000 12.207 11.779 11.779 0 0011.8 24h.214A12.111 12.111 0 0024 11.791 11.766 11.766 0 0011.983 0zM10.5 16.542a1.476 1.476 0 011.449-1.53h.027a1.527 1.527 0 011.523 1.47 1.475 1.475 0 01-1.449 1.53h-.027a1.529 1.529 0 01-1.523-1.47zM11 12.5v-6a1 1 0 012 0v6a1 1 0 11-2 0z\" />\n    </Svg>\n  );\n}\n\nfunction Spinner() {\n  return <div className={`${Default.CSS_NAMESPACE}__spinner`} />;\n}\n\nexport const Icons = {\n  info: Info,\n  warning: Warning,\n  success: Success,\n  error: Error,\n  spinner: Spinner\n};\n\nconst maybeIcon = (type: string): type is keyof typeof Icons => type in Icons;\n\nexport type IconParams = Pick<ToastProps, 'theme' | 'icon' | 'type' | 'isLoading'>;\n\nexport function getIcon({ theme, type, isLoading, icon }: IconParams) {\n  let Icon: React.ReactNode = null;\n  const iconProps = { theme, type };\n\n  if (icon === false) {\n    // hide\n  } else if (isFn(icon)) {\n    Icon = icon({ ...iconProps, isLoading });\n  } else if (isValidElement(icon)) {\n    Icon = cloneElement(icon, iconProps);\n  } else if (isLoading) {\n    Icon = Icons.spinner();\n  } else if (maybeIcon(type)) {\n    Icon = Icons[type](iconProps);\n  }\n\n  return Icon;\n}\n"
  },
  {
    "path": "src/components/ProgressBar.cy.tsx",
    "content": "import React from 'react';\nimport { Theme } from '../types';\nimport { ProgressBar } from './ProgressBar';\n\nconst getProps = () => ({\n  delay: 5000,\n  isRunning: true,\n  rtl: false,\n  closeToast: cy.stub,\n  isIn: true,\n  theme: ['colored', 'light', 'dark'][Math.floor(Math.random() * 3)] as Theme\n});\n\nconst Wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n  <div\n    style={{\n      padding: '1rem',\n      position: 'fixed',\n      top: 0,\n      left: 0,\n      right: 0,\n      outline: '1px solid'\n    }}\n  >\n    {children}\n  </div>\n);\n\ndescribe('ProgressBar', () => {\n  it('merge className', () => {\n    cy.mount(\n      <Wrapper>\n        <ProgressBar {...getProps()} className=\"test\" />\n      </Wrapper>\n    );\n\n    cy.get('.test').should('exist');\n  });\n\n  it('merge className in function form', () => {\n    cy.mount(\n      <Wrapper>\n        <ProgressBar {...getProps()} className={() => 'test'} />\n      </Wrapper>\n    );\n\n    cy.get('.test').should('exist');\n  });\n\n  it('trigger closeToast when animation end', () => {\n    const closeToast = cy.stub().as('closeToast');\n    const delay = 1000;\n    cy.mount(\n      <Wrapper>\n        <ProgressBar {...getProps()} closeToast={closeToast} delay={delay} />\n      </Wrapper>\n    );\n\n    cy.get('@closeToast').should('not.have.been.called');\n    cy.wait(delay);\n    cy.get('@closeToast').should('have.been.called');\n  });\n\n  it('hide the progress bar', () => {\n    cy.mount(\n      <Wrapper>\n        <ProgressBar {...getProps()} hide />\n      </Wrapper>\n    );\n\n    cy.get('[role=progressbar]').should('exist').should('not.be.visible');\n  });\n\n  it('pause the progress bar', () => {\n    cy.mount(\n      <Wrapper>\n        <ProgressBar {...getProps()} isRunning={false} />\n      </Wrapper>\n    );\n\n    cy.findByRole('progressbar').should('have.attr', 'style').and('include', 'animation-play-state: paused');\n  });\n\n  it('control progress bar', () => {\n    cy.mount(\n      <Wrapper>\n        <ProgressBar {...getProps()} controlledProgress progress={0.7} />\n      </Wrapper>\n    );\n\n    cy.findByRole('progressbar').should('have.attr', 'style').and('include', 'scaleX(0.7)');\n  });\n});\n"
  },
  {
    "path": "src/components/ProgressBar.tsx",
    "content": "import React from 'react';\nimport cx from 'clsx';\n\nimport { Default, isFn, Type } from '../utils';\nimport { Theme, ToastClassName, TypeOptions } from '../types';\n\nexport interface ProgressBarProps {\n  /**\n   * The animation delay which determine when to close the toast\n   */\n  delay: number;\n\n  /**\n   * The animation is running or paused\n   */\n  isRunning: boolean;\n\n  /**\n   * Func to close the current toast\n   */\n  closeToast: () => void;\n\n  /**\n   * Optional type : info, success ...\n   */\n  type?: TypeOptions;\n\n  /**\n   * The theme that is currently used\n   */\n  theme: Theme;\n\n  /**\n   * Hide or not the progress bar\n   */\n  hide?: boolean;\n\n  /**\n   * Optional className\n   */\n  className?: ToastClassName;\n\n  /**\n   * Tell whether a controlled progress bar is used\n   */\n  controlledProgress?: boolean;\n\n  /**\n   * Controlled progress value\n   */\n  progress?: number | string;\n\n  /**\n   * Support rtl content\n   */\n  rtl?: boolean;\n\n  /**\n   * Tell if the component is visible on screen or not\n   */\n  isIn?: boolean;\n}\n\nexport function ProgressBar({\n  delay,\n  isRunning,\n  closeToast,\n  type = Type.DEFAULT,\n  hide,\n  className,\n  controlledProgress,\n  progress,\n  rtl,\n  isIn,\n  theme\n}: ProgressBarProps) {\n  const isHidden = hide || (controlledProgress && progress === 0);\n  const style: React.CSSProperties = {\n    animationDuration: `${delay}ms`,\n    animationPlayState: isRunning ? 'running' : 'paused'\n  };\n\n  if (controlledProgress) style.transform = `scaleX(${progress})`;\n  const defaultClassName = cx(\n    `${Default.CSS_NAMESPACE}__progress-bar`,\n    controlledProgress\n      ? `${Default.CSS_NAMESPACE}__progress-bar--controlled`\n      : `${Default.CSS_NAMESPACE}__progress-bar--animated`,\n    `${Default.CSS_NAMESPACE}__progress-bar-theme--${theme}`,\n    `${Default.CSS_NAMESPACE}__progress-bar--${type}`,\n    {\n      [`${Default.CSS_NAMESPACE}__progress-bar--rtl`]: rtl\n    }\n  );\n  const classNames = isFn(className)\n    ? className({\n        rtl,\n        type,\n        defaultClassName\n      })\n    : cx(defaultClassName, className);\n\n  // 🧐 controlledProgress is derived from progress\n  // so if controlledProgress is set\n  // it means that this is also the case for progress\n  const animationEvent = {\n    [controlledProgress && (progress as number)! >= 1 ? 'onTransitionEnd' : 'onAnimationEnd']:\n      controlledProgress && (progress as number)! < 1\n        ? null\n        : () => {\n            isIn && closeToast();\n          }\n  };\n\n  // TODO: add aria-valuenow, aria-valuemax, aria-valuemin\n\n  return (\n    <div className={`${Default.CSS_NAMESPACE}__progress-bar--wrp`} data-hidden={isHidden}>\n      <div\n        className={`${Default.CSS_NAMESPACE}__progress-bar--bg ${Default.CSS_NAMESPACE}__progress-bar-theme--${theme} ${Default.CSS_NAMESPACE}__progress-bar--${type}`}\n      />\n      <div\n        role=\"progressbar\"\n        aria-hidden={isHidden ? 'true' : 'false'}\n        aria-label=\"notification timer\"\n        className={classNames}\n        style={style}\n        {...animationEvent}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/Toast.cy.tsx",
    "content": "import React from 'react';\nimport { DraggableDirection, ToastProps } from '../types';\nimport { Default } from '../utils';\nimport { Toast } from './Toast';\nimport { defaultProps } from './ToastContainer';\n\nconst REQUIRED_PROPS = {\n  ...defaultProps,\n  isIn: true,\n  autoClose: false,\n  closeToast: () => {},\n  type: 'default',\n  toastId: 'id',\n  key: 'key',\n  collapseAll: () => {}\n} as ToastProps;\n\nconst cssClasses = {\n  rtl: `.${Default.CSS_NAMESPACE}__toast--rtl`,\n  closeOnClick: `.${Default.CSS_NAMESPACE}__toast--close-on-click`,\n  progressBar: `.${Default.CSS_NAMESPACE}__progress-bar`,\n  progressBarController: `.${Default.CSS_NAMESPACE}__progress-bar--controlled`,\n  closeButton: `.${Default.CSS_NAMESPACE}__close-button`,\n  container: `.${Default.CSS_NAMESPACE}__toast-container`\n};\n\nconst progressBar = {\n  isRunning: () => {\n    cy.wait(100);\n    cy.findByRole('progressbar').should('have.attr', 'style').and('include', 'animation-play-state: running');\n  },\n  isPaused: () => {\n    cy.wait(100);\n    cy.findByRole('progressbar')\n      .should('have.attr', 'style')\n      .and('include', 'animation-play-state: paused')\n      .as('pause progress bar');\n  },\n  isControlled: (progress: number) => {\n    cy.wait(100);\n    cy.get(cssClasses.progressBarController).should('exist');\n    cy.findByRole('progressbar').should('have.attr', 'style').and('include', `scaleX(${progress})`);\n  }\n};\n\ndescribe('Toast', () => {\n  for (const { name, className } of [\n    {\n      name: 'string',\n      className: 'container-class'\n    },\n    {\n      name: 'function',\n      className: () => 'container-class'\n    }\n  ]) {\n    it(`merge container when using ${name}`, () => {\n      cy.mount(\n        <Toast {...REQUIRED_PROPS} className={className}>\n          FooBar\n        </Toast>\n      );\n\n      cy.get('.container-class').should('exist');\n    });\n  }\n\n  it('support rtl', () => {\n    cy.mount(\n      <Toast {...REQUIRED_PROPS} rtl>\n        FooBar\n      </Toast>\n    );\n\n    cy.get(cssClasses.rtl).should('have.css', 'direction', 'rtl');\n  });\n\n  describe('closeOnClick', () => {\n    it('call closeToast when enabled', () => {\n      const closeToast = cy.stub().as('closeToast');\n\n      cy.mount(\n        <Toast {...REQUIRED_PROPS} closeOnClick closeToast={closeToast}>\n          FooBar\n        </Toast>\n      );\n\n      cy.findByRole('alert').click();\n      cy.get('@closeToast').should('have.been.called');\n    });\n\n    it('does not call closeToast when disabled', () => {\n      const closeToast = cy.stub().as('closeToast');\n\n      cy.mount(\n        <Toast {...REQUIRED_PROPS} closeOnClick={false} closeToast={closeToast}>\n          FooBar\n        </Toast>\n      );\n\n      cy.findByRole('alert').click();\n      cy.get('@closeToast').should('not.have.been.called');\n    });\n  });\n\n  describe('autoClose', () => {\n    it('does not render progress bar when false', () => {\n      cy.mount(\n        <Toast {...REQUIRED_PROPS} autoClose={false}>\n          FooBar\n        </Toast>\n      );\n\n      cy.findByRole('progressbar').should('not.exist');\n    });\n\n    it('resume and pause progress bar', () => {\n      cy.mount(\n        <Toast {...REQUIRED_PROPS} autoClose={5000}>\n          hello\n        </Toast>\n      );\n\n      cy.resolveEntranceAnimation();\n\n      cy.findByRole('alert').should('be.visible').trigger('mouseover');\n      progressBar.isPaused();\n\n      cy.findByRole('alert').trigger('mouseout');\n      progressBar.isRunning();\n\n      cy.findByRole('alert').trigger('mouseover');\n      progressBar.isPaused();\n    });\n  });\n\n  it('does not render close button when closeButton is false', () => {\n    cy.mount(\n      <Toast {...REQUIRED_PROPS} closeButton={false}>\n        FooBar\n      </Toast>\n    );\n\n    cy.findByLabelText('close').should('not.exist');\n  });\n\n  it('resume and pause progress bar when pauseOnFocusLoss is enabled', () => {\n    cy.mount(\n      <Toast {...REQUIRED_PROPS} autoClose={5000} pauseOnFocusLoss>\n        hello\n      </Toast>\n    );\n\n    cy.resolveEntranceAnimation();\n    progressBar.isRunning();\n\n    cy.window().blur();\n    progressBar.isPaused();\n\n    cy.window().focus();\n    progressBar.isRunning();\n  });\n\n  it('does not pause progress bar when pauseOnHover is disabled', () => {\n    cy.mount(\n      <Toast {...REQUIRED_PROPS} autoClose={5000} pauseOnHover={false}>\n        hello\n      </Toast>\n    );\n\n    cy.resolveEntranceAnimation();\n\n    cy.findByRole('alert').trigger('mouseover');\n    progressBar.isRunning();\n  });\n\n  describe('controller progress bar', () => {\n    it('set the correct progress value bar disregarding autoClose value', () => {\n      cy.mount(\n        <Toast {...REQUIRED_PROPS} progress={0.3} autoClose={false}>\n          hello\n        </Toast>\n      );\n\n      cy.resolveEntranceAnimation();\n\n      progressBar.isControlled(0.3);\n\n      cy.mount(\n        <Toast {...REQUIRED_PROPS} progress={0.3} autoClose={5000}>\n          hello\n        </Toast>\n      );\n\n      cy.resolveEntranceAnimation();\n\n      progressBar.isControlled(0.3);\n    });\n\n    it('call closeToast when progress value is >= 1', () => {\n      const closeToast = cy.stub().as('closeToast');\n      cy.mount(\n        <Toast {...REQUIRED_PROPS} progress={1.1} closeToast={closeToast}>\n          hello\n        </Toast>\n      );\n\n      cy.findByRole('progressbar').trigger('transitionend');\n      cy.get('@closeToast').should('have.been.called');\n    });\n  });\n\n  it('call closeToast when autoClose duration exceeded', () => {\n    const closeToast = cy.stub().as('closeToast');\n    cy.mount(\n      <Toast {...REQUIRED_PROPS} autoClose={200} closeToast={closeToast}>\n        hello\n      </Toast>\n    );\n\n    cy.get('@closeToast').should('have.been.called');\n  });\n\n  it('attach specified attributes: role, id, etc...', () => {\n    const style: React.CSSProperties = {\n      background: 'purple'\n    };\n\n    cy.mount(\n      <Toast {...REQUIRED_PROPS} role=\"status\" toastId=\"foo\" style={style}>\n        hello\n      </Toast>\n    );\n\n    cy.resolveEntranceAnimation();\n\n    cy.findByRole('status').should('exist');\n    cy.get('#foo').should('exist');\n\n    cy.findByRole('status').should('have.attr', 'style').and('include', 'background: purple');\n  });\n\n  for (const { type, value } of [\n    {\n      type: 'string',\n      value: 'hello'\n    },\n    {\n      type: 'react element',\n      value: <div>hello</div>\n    },\n    {\n      type: 'function',\n      value: () => <div>hello</div>\n    }\n  ]) {\n    it(`render ${type}`, () => {\n      cy.mount(<Toast {...REQUIRED_PROPS}>{value}</Toast>);\n\n      cy.findByText('hello').should('exist');\n    });\n  }\n\n  it('override default closeButton', () => {\n    cy.mount(\n      <Toast {...REQUIRED_PROPS} closeButton={<span>💩</span>}>\n        hello\n      </Toast>\n    );\n    cy.resolveEntranceAnimation();\n\n    cy.findByText('💩').should('exist');\n  });\n\n  it('fallback to default closeButton', () => {\n    cy.mount(\n      <Toast {...REQUIRED_PROPS} closeButton={true}>\n        hello\n      </Toast>\n    );\n    cy.resolveEntranceAnimation();\n\n    cy.findByLabelText('close').should('exist');\n  });\n\n  describe('Drag event', () => {\n    beforeEach(() => {\n      cy.viewport('macbook-16');\n    });\n\n    for (const { axis, delta } of [\n      { axis: 'x', delta: { deltaX: -300 } },\n      { axis: 'y', delta: { deltaY: 300 } }\n    ]) {\n      it(`close toast when dragging on ${axis}-axis`, () => {\n        cy.mount(\n          <div style={{ width: '300px', position: 'fixed', right: 0 }}>\n            <Toast\n              {...REQUIRED_PROPS}\n              autoClose={5000}\n              draggable\n              draggableDirection={axis as DraggableDirection}\n              closeToast={cy.stub().as('closeToast')}\n            >\n              hello\n            </Toast>\n          </div>\n        );\n\n        cy.resolveEntranceAnimation();\n\n        cy.findByRole('alert').move(delta);\n        cy.get('@closeToast').should('have.been.called');\n      });\n    }\n\n    for (const { axis, delta } of [\n      { axis: 'x', delta: { deltaX: -100 } },\n      { axis: 'y', delta: { deltaY: 40 } }\n    ]) {\n      it(`does not close toast when dragging on ${axis}-axis`, () => {\n        cy.mount(\n          <div style={{ width: '300px', position: 'fixed', right: 0 }}>\n            <Toast\n              {...REQUIRED_PROPS}\n              autoClose={5000}\n              draggable\n              draggableDirection={axis as DraggableDirection}\n              closeToast={cy.stub().as('closeToast')}\n            >\n              hello\n            </Toast>\n          </div>\n        );\n\n        cy.resolveEntranceAnimation();\n\n        cy.findByRole('alert').move(delta);\n        cy.get('@closeToast').should('not.have.been.called');\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "src/components/Toast.tsx",
    "content": "import cx from 'clsx';\nimport React, { cloneElement, isValidElement } from 'react';\n\nimport { useToast } from '../hooks/useToast';\nimport { ToastProps } from '../types';\nimport { Default, isFn, renderContent } from '../utils';\nimport { CloseButton } from './CloseButton';\nimport { ProgressBar } from './ProgressBar';\nimport { getIcon } from './Icons';\n\nexport const Toast: React.FC<ToastProps> = props => {\n  const { isRunning, preventExitTransition, toastRef, eventHandlers, playToast } = useToast(props);\n  const {\n    closeButton,\n    children,\n    autoClose,\n    onClick,\n    type,\n    hideProgressBar,\n    closeToast,\n    transition: Transition,\n    position,\n    className,\n    style,\n    progressClassName,\n    updateId,\n    role,\n    progress,\n    rtl,\n    toastId,\n    deleteToast,\n    isIn,\n    isLoading,\n    closeOnClick,\n    theme,\n    ariaLabel\n  } = props;\n  const defaultClassName = cx(\n    `${Default.CSS_NAMESPACE}__toast`,\n    `${Default.CSS_NAMESPACE}__toast-theme--${theme}`,\n    `${Default.CSS_NAMESPACE}__toast--${type}`,\n    {\n      [`${Default.CSS_NAMESPACE}__toast--rtl`]: rtl\n    },\n    {\n      [`${Default.CSS_NAMESPACE}__toast--close-on-click`]: closeOnClick\n    }\n  );\n  const cssClasses = isFn(className)\n    ? className({\n        rtl,\n        position,\n        type,\n        defaultClassName\n      })\n    : cx(defaultClassName, className);\n  const icon = getIcon(props);\n  const isProgressControlled = !!progress || !autoClose;\n\n  const closeButtonProps = { closeToast, type, theme };\n  let Close: React.ReactNode = null;\n\n  if (closeButton === false) {\n    // hide\n  } else if (isFn(closeButton)) {\n    Close = closeButton(closeButtonProps);\n  } else if (isValidElement(closeButton)) {\n    Close = cloneElement(closeButton, closeButtonProps);\n  } else {\n    Close = CloseButton(closeButtonProps);\n  }\n\n  return (\n    <Transition\n      isIn={isIn}\n      done={deleteToast}\n      position={position}\n      preventExitTransition={preventExitTransition}\n      nodeRef={toastRef}\n      playToast={playToast}\n    >\n      <div\n        id={toastId as string}\n        tabIndex={0}\n        onClick={onClick}\n        data-in={isIn}\n        className={cssClasses}\n        {...eventHandlers}\n        style={style}\n        ref={toastRef}\n        {...(isIn && { role: role, 'aria-label': ariaLabel })}\n      >\n        {icon != null && (\n          <div\n            className={cx(`${Default.CSS_NAMESPACE}__toast-icon`, {\n              [`${Default.CSS_NAMESPACE}--animate-icon ${Default.CSS_NAMESPACE}__zoom-enter`]: !isLoading\n            })}\n          >\n            {icon}\n          </div>\n        )}\n        {renderContent(children, props, !isRunning)}\n        {Close}\n        {!props.customProgressBar && (\n          <ProgressBar\n            {...(updateId && !isProgressControlled ? { key: `p-${updateId}` } : {})}\n            rtl={rtl}\n            theme={theme}\n            delay={autoClose as number}\n            isRunning={isRunning}\n            isIn={isIn}\n            closeToast={closeToast}\n            hide={hideProgressBar}\n            type={type}\n            className={progressClassName}\n            controlledProgress={isProgressControlled}\n            progress={progress || 0}\n          />\n        )}\n      </div>\n    </Transition>\n  );\n};\n"
  },
  {
    "path": "src/components/ToastContainer.tsx",
    "content": "import cx from 'clsx';\nimport React, { useEffect, useRef, useState } from 'react';\n\nimport { toast } from '../core';\nimport { useToastContainer } from '../hooks';\nimport { useIsomorphicLayoutEffect } from '../hooks/useIsomorphicLayoutEffect';\nimport { ToastContainerProps, ToastPosition } from '../types';\nimport { Default, Direction, isFn, parseClassName } from '../utils';\nimport { Toast } from './Toast';\nimport { Bounce } from './Transitions';\n\nexport const defaultProps: ToastContainerProps = {\n  position: 'top-right',\n  transition: Bounce,\n  autoClose: 5000,\n  closeButton: true,\n  pauseOnHover: true,\n  pauseOnFocusLoss: true,\n  draggable: 'touch',\n  draggablePercent: Default.DRAGGABLE_PERCENT as number,\n  draggableDirection: Direction.X,\n  role: 'alert',\n  theme: 'light',\n  'aria-label': 'Notifications Alt+T',\n  hotKeys: e => e.altKey && e.code === 'KeyT'\n};\n\nexport function ToastContainer(props: ToastContainerProps) {\n  let containerProps: ToastContainerProps = {\n    ...defaultProps,\n    ...props\n  };\n  const stacked = props.stacked;\n  const [collapsed, setIsCollapsed] = useState(true);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const { getToastToRender, isToastActive, count } = useToastContainer(containerProps);\n  const { className, style, rtl, containerId, hotKeys } = containerProps;\n\n  function getClassName(position: ToastPosition) {\n    const defaultClassName = cx(\n      `${Default.CSS_NAMESPACE}__toast-container`,\n      `${Default.CSS_NAMESPACE}__toast-container--${position}`,\n      { [`${Default.CSS_NAMESPACE}__toast-container--rtl`]: rtl }\n    );\n    return isFn(className)\n      ? className({\n          position,\n          rtl,\n          defaultClassName\n        })\n      : cx(defaultClassName, parseClassName(className));\n  }\n\n  function collapseAll() {\n    if (stacked) {\n      setIsCollapsed(true);\n      toast.play();\n    }\n  }\n\n  useIsomorphicLayoutEffect(() => {\n    if (stacked) {\n      const nodes = containerRef.current!.querySelectorAll('[data-in=\"true\"]');\n      const gap = 12;\n      const isTop = containerProps.position?.includes('top');\n      let usedHeight = 0;\n      let prevS = 0;\n\n      Array.from(nodes)\n        .reverse()\n        .forEach((n, i) => {\n          const node = n as HTMLElement;\n          node.classList.add(`${Default.CSS_NAMESPACE}__toast--stacked`);\n\n          if (i > 0) node.dataset.collapsed = `${collapsed}`;\n\n          if (!node.dataset.pos) node.dataset.pos = isTop ? 'top' : 'bot';\n\n          const y = usedHeight * (collapsed ? 0.2 : 1) + (collapsed ? 0 : gap * i);\n\n          node.style.setProperty('--y', `${isTop ? y : y * -1}px`);\n          node.style.setProperty('--g', `${gap}`);\n          node.style.setProperty('--s', `${1 - (collapsed ? prevS : 0)}`);\n\n          usedHeight += node.offsetHeight;\n          prevS += 0.025;\n        });\n    }\n  }, [collapsed, count, stacked]);\n\n  useEffect(() => {\n    function focusFirst(e: KeyboardEvent) {\n      const node = containerRef.current;\n      if (hotKeys(e)) {\n        (node.querySelector('[tabIndex=\"0\"]') as HTMLElement)?.focus();\n        setIsCollapsed(false);\n        toast.pause();\n      }\n      if (e.key === 'Escape' && (document.activeElement === node || node?.contains(document.activeElement))) {\n        setIsCollapsed(true);\n        toast.play();\n      }\n    }\n\n    document.addEventListener('keydown', focusFirst);\n\n    return () => {\n      document.removeEventListener('keydown', focusFirst);\n    };\n  }, [hotKeys]);\n\n  return (\n    <section\n      ref={containerRef}\n      className={Default.CSS_NAMESPACE as string}\n      id={containerId as string}\n      onMouseEnter={() => {\n        if (stacked) {\n          setIsCollapsed(false);\n          toast.pause();\n        }\n      }}\n      onMouseLeave={collapseAll}\n      aria-live=\"polite\"\n      aria-atomic=\"false\"\n      aria-relevant=\"additions text\"\n      aria-label={containerProps['aria-label']}\n    >\n      {getToastToRender((position, toastList) => {\n        const containerStyle: React.CSSProperties = !toastList.length\n          ? { ...style, pointerEvents: 'none' }\n          : { ...style };\n\n        return (\n          <div\n            tabIndex={-1}\n            className={getClassName(position)}\n            data-stacked={stacked}\n            style={containerStyle}\n            key={`c-${position}`}\n          >\n            {toastList.map(({ content, props: toastProps }) => {\n              return (\n                <Toast\n                  {...toastProps}\n                  stacked={stacked}\n                  collapseAll={collapseAll}\n                  isIn={isToastActive(toastProps.toastId, toastProps.containerId)}\n                  key={`t-${toastProps.key}`}\n                >\n                  {content}\n                </Toast>\n              );\n            })}\n          </div>\n        );\n      })}\n    </section>\n  );\n}\n"
  },
  {
    "path": "src/components/Transitions.tsx",
    "content": "import { cssTransition, Default } from '../utils';\n\nconst getConfig = (animationName: string, appendPosition = false) => ({\n  enter: `${Default.CSS_NAMESPACE}--animate ${Default.CSS_NAMESPACE}__${animationName}-enter`,\n  exit: `${Default.CSS_NAMESPACE}--animate ${Default.CSS_NAMESPACE}__${animationName}-exit`,\n  appendPosition\n});\n\nconst Bounce = cssTransition(getConfig('bounce', true));\n\nconst Slide = cssTransition(getConfig('slide', true));\n\nconst Zoom = cssTransition(getConfig('zoom'));\n\nconst Flip = cssTransition(getConfig('flip'));\n\nexport { Bounce, Slide, Zoom, Flip };\n"
  },
  {
    "path": "src/components/index.tsx",
    "content": "export * from './CloseButton';\nexport * from './ProgressBar';\nexport { ToastContainer } from './ToastContainer';\nexport * from './Transitions';\nexport * from './Toast';\nexport * from './Icons';\n"
  },
  {
    "path": "src/core/containerObserver.ts",
    "content": "import {\n  Id,\n  NotValidatedToastProps,\n  OnChangeCallback,\n  Toast,\n  ToastContainerProps,\n  ToastContent,\n  ToastProps\n} from '../types';\nimport { canBeRendered, getAutoCloseDelay, isNum, parseClassName, toToastItem } from '../utils';\n\ntype Notify = () => void;\n\nexport type ContainerObserver = ReturnType<typeof createContainerObserver>;\n\nexport function createContainerObserver(\n  id: Id,\n  containerProps: ToastContainerProps,\n  dispatchChanges: OnChangeCallback\n) {\n  let toastKey = 1;\n  let toastCount = 0;\n  let queue: Toast[] = [];\n  let snapshot: Toast[] = [];\n  let props = containerProps;\n  const toasts = new Map<Id, Toast>();\n  const listeners = new Set<Notify>();\n\n  const observe = (notify: Notify) => {\n    listeners.add(notify);\n    return () => listeners.delete(notify);\n  };\n\n  const notify = () => {\n    snapshot = Array.from(toasts.values());\n    listeners.forEach(cb => cb());\n  };\n\n  const shouldIgnoreToast = ({ containerId, toastId, updateId }: NotValidatedToastProps) => {\n    const containerMismatch = containerId ? containerId !== id : id !== 1;\n    const isDuplicate = toasts.has(toastId) && updateId == null;\n\n    return containerMismatch || isDuplicate;\n  };\n\n  const toggle = (v: boolean, id?: Id) => {\n    toasts.forEach(t => {\n      if (id == null || id === t.props.toastId) t.toggle?.(v);\n    });\n  };\n\n  const markAsRemoved = (v: Toast) => {\n    v.props?.onClose?.(v.removalReason);\n    v.isActive = false;\n  };\n\n  const removeToast = (id?: Id) => {\n    if (id == null) {\n      toasts.forEach(markAsRemoved);\n    } else {\n      const t = toasts.get(id);\n      if (t) markAsRemoved(t);\n    }\n    notify();\n  };\n\n  const clearQueue = () => {\n    toastCount -= queue.length;\n    queue = [];\n  };\n\n  const addActiveToast = (toast: Toast) => {\n    const { toastId, updateId } = toast.props;\n    const isNew = updateId == null;\n\n    if (toast.staleId) toasts.delete(toast.staleId);\n    toast.isActive = true;\n\n    toasts.set(toastId, toast);\n    notify();\n    dispatchChanges(toToastItem(toast, isNew ? 'added' : 'updated'));\n\n    if (isNew) toast.props.onOpen?.();\n  };\n\n  const buildToast = <TData = unknown>(content: ToastContent<TData>, options: NotValidatedToastProps) => {\n    if (shouldIgnoreToast(options)) return;\n\n    const { toastId, updateId, data, staleId, delay } = options;\n\n    const isNotAnUpdate = updateId == null;\n\n    if (isNotAnUpdate) toastCount++;\n\n    const toastProps = {\n      ...props,\n      style: props.toastStyle,\n      key: toastKey++,\n      ...Object.fromEntries(Object.entries(options).filter(([_, v]) => v != null)),\n      toastId,\n      updateId,\n      data,\n      isIn: false,\n      className: parseClassName(options.className || props.toastClassName),\n      progressClassName: parseClassName(options.progressClassName || props.progressClassName),\n      autoClose: options.isLoading ? false : getAutoCloseDelay(options.autoClose, props.autoClose),\n      closeToast(reason?: true) {\n        toasts.get(toastId)!.removalReason = reason;\n        removeToast(toastId);\n      },\n      deleteToast() {\n        const toastToRemove = toasts.get(toastId);\n\n        if (toastToRemove == null) return;\n\n        dispatchChanges(toToastItem(toastToRemove, 'removed'));\n        toasts.delete(toastId);\n\n        toastCount--;\n        if (toastCount < 0) toastCount = 0;\n\n        if (queue.length > 0) {\n          addActiveToast(queue.shift());\n          return;\n        }\n\n        notify();\n      }\n    } as ToastProps;\n\n    toastProps.closeButton = props.closeButton;\n\n    if (options.closeButton === false || canBeRendered(options.closeButton)) {\n      toastProps.closeButton = options.closeButton;\n    } else if (options.closeButton === true) {\n      toastProps.closeButton = canBeRendered(props.closeButton) ? props.closeButton : true;\n    }\n\n    const activeToast = {\n      content,\n      props: toastProps,\n      staleId\n    } as Toast;\n\n    // not handling limit + delay by design. Waiting for user feedback first\n    if (props.limit && props.limit > 0 && toastCount > props.limit && isNotAnUpdate) {\n      queue.push(activeToast);\n    } else if (isNum(delay)) {\n      setTimeout(() => {\n        addActiveToast(activeToast);\n      }, delay);\n    } else {\n      addActiveToast(activeToast);\n    }\n  };\n\n  return {\n    id,\n    props,\n    observe,\n    toggle,\n    removeToast,\n    toasts,\n    clearQueue,\n    buildToast,\n    setProps(p: ToastContainerProps) {\n      props = p;\n    },\n    setToggle: (id: Id, fn: (v: boolean) => void) => {\n      const t = toasts.get(id);\n      if (t) t.toggle = fn;\n    },\n    isToastActive: (id: Id) => toasts.get(id)?.isActive,\n    getSnapshot: () => snapshot\n  };\n}\n"
  },
  {
    "path": "src/core/genToastId.ts",
    "content": "let TOAST_ID = 1;\n\nexport const genToastId = () => `${TOAST_ID++}`;\n"
  },
  {
    "path": "src/core/index.ts",
    "content": "export * from './toast';\n"
  },
  {
    "path": "src/core/store.ts",
    "content": "import {\n  ClearWaitingQueueParams,\n  Id,\n  NotValidatedToastProps,\n  OnChangeCallback,\n  ToastContainerProps,\n  ToastContent,\n  ToastItem,\n  ToastOptions\n} from '../types';\nimport { Default, canBeRendered, isId } from '../utils';\nimport { ContainerObserver, createContainerObserver } from './containerObserver';\n\ninterface EnqueuedToast {\n  content: ToastContent<any>;\n  options: NotValidatedToastProps;\n}\n\ninterface RemoveParams {\n  id?: Id;\n  containerId: Id;\n}\n\nconst containers = new Map<Id, ContainerObserver>();\nlet renderQueue: EnqueuedToast[] = [];\nconst listeners = new Set<OnChangeCallback>();\n\nconst dispatchChanges = (data: ToastItem) => listeners.forEach(cb => cb(data));\n\nconst hasContainers = () => containers.size > 0;\n\nfunction flushRenderQueue() {\n  renderQueue.forEach(v => pushToast(v.content, v.options));\n  renderQueue = [];\n}\n\nexport const getToast = (id: Id, { containerId }: ToastOptions) =>\n  containers.get(containerId || Default.CONTAINER_ID)?.toasts.get(id);\n\nexport function isToastActive(id: Id, containerId?: Id) {\n  if (containerId) return !!containers.get(containerId)?.isToastActive(id);\n\n  let isActive = false;\n  containers.forEach(c => {\n    if (c.isToastActive(id)) isActive = true;\n  });\n\n  return isActive;\n}\n\nexport function removeToast(params?: Id | RemoveParams) {\n  if (!hasContainers()) {\n    renderQueue = renderQueue.filter(v => params != null && v.options.toastId !== params);\n    return;\n  }\n\n  if (params == null || isId(params)) {\n    containers.forEach(c => {\n      c.removeToast(params as Id);\n    });\n  } else if (params && ('containerId' in params || 'id' in params)) {\n    const container = containers.get(params.containerId);\n    container\n      ? container.removeToast(params.id)\n      : containers.forEach(c => {\n          c.removeToast(params.id);\n        });\n  }\n}\n\nexport const clearWaitingQueue = (p: ClearWaitingQueueParams = {}) => {\n  containers.forEach(c => {\n    if (c.props.limit && (!p.containerId || c.id === p.containerId)) {\n      c.clearQueue();\n    }\n  });\n};\n\nexport function pushToast<TData>(content: ToastContent<TData>, options: NotValidatedToastProps) {\n  if (!canBeRendered(content)) return;\n  if (!hasContainers()) renderQueue.push({ content, options });\n\n  containers.forEach(c => {\n    c.buildToast(content, options);\n  });\n}\n\ninterface ToggleToastParams {\n  id?: Id;\n  containerId?: Id;\n}\n\ntype RegisterToggleOpts = {\n  id: Id;\n  containerId?: Id;\n  fn: (v: boolean) => void;\n};\n\nexport function registerToggle(opts: RegisterToggleOpts) {\n  containers.get(opts.containerId || Default.CONTAINER_ID)?.setToggle(opts.id, opts.fn);\n}\n\nexport function toggleToast(v: boolean, opt?: ToggleToastParams) {\n  containers.forEach(c => {\n    if (opt == null || !opt?.containerId) {\n      c.toggle(v, opt?.id);\n    } else if (opt?.containerId === c.id) {\n      c.toggle(v, opt?.id);\n    }\n  });\n}\n\nexport function registerContainer(props: ToastContainerProps) {\n  const id = props.containerId || Default.CONTAINER_ID;\n  return {\n    subscribe(notify: () => void) {\n      const container = createContainerObserver(id, props, dispatchChanges);\n\n      containers.set(id, container);\n      const unobserve = container.observe(notify);\n      flushRenderQueue();\n\n      return () => {\n        unobserve();\n        containers.delete(id);\n      };\n    },\n    setProps(p: ToastContainerProps) {\n      containers.get(id)?.setProps(p);\n    },\n    getSnapshot() {\n      return containers.get(id)?.getSnapshot();\n    }\n  };\n}\n\nexport function onChange(cb: OnChangeCallback) {\n  listeners.add(cb);\n\n  return () => {\n    listeners.delete(cb);\n  };\n}\n"
  },
  {
    "path": "src/core/toast.cy.tsx",
    "content": "import React from 'react';\nimport { ToastContainer } from '../components';\nimport { toast } from './toast';\n\nbeforeEach(() => {\n  cy.viewport('macbook-15');\n});\n\ndescribe('without container', () => {\n  it('enqueue toasts till container is mounted', () => {\n    toast('msg1');\n    toast('msg2');\n\n    cy.findByText('msg1').should('not.exist');\n    cy.findByText('msg2').should('not.exist');\n\n    cy.mount(<ToastContainer autoClose={false} />);\n\n    cy.resolveEntranceAnimation();\n    cy.findByText('msg1').should('exist');\n    cy.findByText('msg2').should('exist');\n  });\n\n  it('remove toast from render queue', () => {\n    toast('msg1');\n    const id = toast('msg2');\n    toast.dismiss(id);\n\n    cy.mount(<ToastContainer autoClose={false} />);\n    cy.resolveEntranceAnimation();\n\n    cy.findByText('msg1').should('exist');\n    cy.findByText('msg2').should('not.exist');\n  });\n});\n\ndescribe('with container', () => {\n  beforeEach(() => {\n    cy.mount(\n      <>\n        <ToastContainer autoClose={false} closeOnClick />\n        <button onClick={() => toast('msg')}>display msg</button>\n      </>\n    );\n  });\n\n  it('render toast', () => {\n    cy.mount(\n      <>\n        <ToastContainer autoClose={false} closeOnClick />\n        <button onClick={() => toast('msg')}>display msg</button>\n      </>\n    );\n    cy.findByRole('button').click();\n    cy.findByText('msg').should('exist');\n  });\n\n  it('return a new id each time a notification is pushed', () => {\n    const firstId = toast('Hello');\n    const secondId = toast('Hello');\n\n    expect(firstId).not.to.be.eq(secondId);\n  });\n\n  it('use the provided toastId from options', () => {\n    const toastId = 11;\n    const id = toast('Hello', { toastId });\n\n    expect(id).to.be.eq(toastId);\n  });\n\n  it('handle change event', () => {\n    toast.onChange(cy.stub().as('onChange'));\n    const id = 'qq';\n\n    cy.mount(\n      <>\n        <button\n          onClick={() => {\n            toast('msg', { data: 'xxxx', toastId: id });\n          }}\n        >\n          display msg\n        </button>\n        <button\n          onClick={() => {\n            toast.update(id, {\n              render: 'world'\n            });\n          }}\n        >\n          update\n        </button>\n        <button onClick={() => toast.dismiss(id)}>remove</button>\n        <ToastContainer />\n      </>\n    );\n\n    cy.findByRole('button', { name: 'display msg' }).click();\n\n    cy.get('@onChange').should('have.been.calledWithMatch', {\n      status: 'added',\n      content: 'msg',\n      data: 'xxxx'\n    });\n\n    cy.findByRole('button', { name: 'update' }).click();\n\n    cy.get('@onChange').should('have.been.calledWithMatch', {\n      status: 'updated',\n      content: 'world'\n    });\n\n    // cy.wait(1000);\n\n    // cy.findByRole('button', { name: 'remove' }).click();\n    //\n    // cy.get('@onChange').should('have.been.calledWithMatch', {\n    //   status: 'removed'\n    // });\n  });\n\n  it('unsubscribe from change event', () => {\n    const unsub = toast.onChange(cy.stub().as('onChange'));\n    unsub();\n    cy.findByRole('button').click();\n    cy.get('@onChange').should('not.have.been.called');\n  });\n\n  describe('sa', () => {\n    // it('be able remove toast programmatically', () => {\n    //   const id = 'test';\n    //\n    //   cy.mount(\n    //     <>\n    //       <button\n    //         onClick={() => {\n    //           toast('msg', { toastId: id });\n    //         }}\n    //       >\n    //         display msg\n    //       </button>\n    //       <button onClick={() => toast.dismiss(id)}>remove</button>\n    //       <ToastContainer />\n    //     </>\n    //   );\n    //\n    //   cy.findByRole('button', { name: 'display msg' }).click();\n    //   cy.findByText('msg').should('exist');\n    //\n    //   cy.findByRole('button', { name: 'remove' }).click();\n    //   cy.resolveEntranceAnimation();\n    //   cy.findByText('msg').should('not.exist');\n    // });\n\n    it('pause and resume notification', () => {\n      const id = toast('msg', {\n        autoClose: 10000\n      });\n\n      cy.findByRole('progressbar').as('progressBar');\n\n      cy.get('@progressBar')\n        .should('have.attr', 'style')\n        .and('include', 'animation-play-state: running')\n        .then(() => {\n          toast.pause({ id });\n          cy.get('@progressBar')\n            .should('have.attr', 'style')\n            .and('include', 'animation-play-state: paused')\n            .then(() => {\n              toast.play({ id });\n\n              cy.get('@progressBar').should('have.attr', 'style').and('include', 'animation-play-state: running');\n            });\n        });\n    });\n  });\n\n  describe('update function', () => {\n    it('update an existing toast', () => {\n      const id = toast('msg');\n\n      cy.resolveEntranceAnimation();\n      cy.findByText('msg')\n        .should('exist')\n        .then(() => {\n          toast.update(id, {\n            render: 'foobar'\n          });\n\n          cy.findByText('msg').should('not.exist');\n          cy.findByText('foobar').should('exist');\n        })\n        .then(() => {\n          toast.update(id, {\n            render: 'bazbar'\n          });\n          cy.findByText('foobar').should('not.exist');\n          cy.findByText('bazbar').should('exist');\n        });\n    });\n\n    it('keep the same content', () => {\n      const id = toast('msg');\n\n      cy.resolveEntranceAnimation();\n      cy.findByText('msg').should('exist');\n      cy.get('.myClass')\n        .should('not.exist')\n        .then(() => {\n          toast.update(id, {\n            className: 'myClass'\n          });\n\n          cy.get('.myClass').should('exist');\n          cy.findByText('msg').should('exist');\n        });\n    });\n\n    it('update a toast only when it exists', () => {\n      toast.update(0, {\n        render: 'msg'\n      });\n\n      cy.resolveEntranceAnimation();\n      cy.findByText('msg').should('not.exist');\n    });\n\n    it('update the toastId', () => {\n      const id = toast('msg');\n      const nextId = 123;\n\n      cy.resolveEntranceAnimation();\n\n      cy.findByText('msg')\n        .should('exist')\n        .then(() => {\n          expect(toast.isActive(id)).to.be.true;\n          toast.update(id, {\n            render: 'foobar',\n            toastId: nextId\n          });\n        });\n\n      cy.findByText('foobar')\n        .should('exist')\n        .then(() => {\n          expect(toast.isActive(id)).to.be.false;\n          expect(toast.isActive(nextId)).to.be.true;\n        });\n    });\n  });\n\n  it('can append classNames', () => {\n    toast('msg', {\n      className: 'class1',\n      progressClassName: 'class3'\n    });\n\n    cy.get('.class1').should('exist');\n    cy.get('.class3').should('exist');\n  });\n\n  it('uses syntactic sugar for different notification type', () => {\n    toast('default');\n    toast.success('success');\n    toast.error('error');\n    toast.warning('warning');\n    toast.info('info');\n    toast.warn('warn');\n    toast.dark('dark');\n\n    cy.resolveEntranceAnimation();\n\n    cy.findByText('default').should('exist');\n    cy.findByText('success').should('exist');\n    cy.findByText('error').should('exist');\n    cy.findByText('warning').should('exist');\n    cy.findByText('info').should('exist');\n    cy.findByText('warn').should('exist');\n    cy.findByText('dark').should('exist');\n  });\n\n  it('handle controlled progress bar', () => {\n    const id = toast('msg', {\n      progress: 0.3\n    });\n\n    cy.resolveEntranceAnimation();\n    cy.findByRole('progressbar')\n      .should('have.attr', 'style')\n      .and('include', 'scaleX(0.3)')\n      .then(() => {\n        toast.done(id);\n        cy.findByRole('progressbar').should('have.attr', 'style').and('include', 'scaleX(1)');\n      });\n  });\n\n  it('handle rejected promise', () => {\n    function rejectPromise() {\n      return new Promise((_, reject) => {\n        setTimeout(() => {\n          reject(new Error('oops'));\n        }, 2000);\n      });\n    }\n\n    toast.promise<unknown, Error>(rejectPromise, {\n      pending: 'loading',\n      error: {\n        render(props) {\n          return <>{props.data?.message}</>;\n        }\n      }\n    });\n\n    cy.resolveEntranceAnimation();\n    cy.findByText('loading').should('exist');\n\n    cy.wait(2000);\n    cy.findByText('loading').should('not.exist');\n    cy.findByText('oops').should('exist');\n  });\n\n  it('handle resolved promise', () => {\n    function resolvePromise() {\n      return new Promise<string>((resolve, _) => {\n        setTimeout(() => {\n          resolve('it worked');\n        }, 2000);\n      });\n    }\n\n    toast.promise<string>(resolvePromise, {\n      pending: 'loading',\n      success: {\n        render(props) {\n          return <>{props.data}</>;\n        }\n      }\n    });\n\n    cy.resolveEntranceAnimation();\n    cy.findByText('loading').should('exist');\n\n    cy.wait(2000);\n    cy.findByText('loading').should('not.exist');\n    cy.findByText('it worked').should('exist');\n  });\n\n  it('support onOpen and onClose callback', () => {\n    const id = 'hello';\n\n    cy.mount(\n      <>\n        <button\n          onClick={() => {\n            toast('msg', {\n              toastId: id,\n              onOpen: cy.stub().as('onOpen'),\n              onClose: cy.stub().as('onClose')\n            });\n          }}\n        >\n          display msg\n        </button>\n        <button onClick={() => toast.dismiss(id)}>remove</button>\n        <ToastContainer />\n      </>\n    );\n\n    cy.findByRole('button', { name: 'display msg' }).click();\n    cy.get('@onOpen').should('have.been.calledOnce');\n\n    cy.findByRole('button', { name: 'remove' }).click();\n    cy.get('@onClose').should('have.been.calledOnce');\n  });\n\n  xit('remove all toasts', () => {\n    cy.mount(\n      <>\n        <button\n          onClick={() => {\n            toast('msg1');\n            // toast('msg2');\n          }}\n        >\n          display msg\n        </button>\n        <button\n          onClick={() => {\n            toast.dismiss();\n          }}\n        >\n          remove\n        </button>\n        <ToastContainer />\n      </>\n    );\n\n    cy.findByRole('button', { name: 'display msg' }).click();\n    cy.findByText('msg1').should('exist');\n\n    cy.findByRole('button', { name: 'remove' }).click();\n    cy.wait(2000);\n    cy.findByText('msg1').should('not.exist');\n  });\n});\n\ndescribe.skip('with multi containers', () => {\n  const Containers = {\n    First: 'first',\n    Second: 'second',\n    Third: 'third'\n  };\n\n  it('clear waiting queue for a given container', () => {\n    cy.mount(\n      <>\n        <div style={{ display: 'grid', placeItems: 'center' }}>\n          <button\n            onClick={() => {\n              toast('msg1-c1', {\n                containerId: Containers.First\n              });\n              toast('msg2-c1', {\n                containerId: Containers.First\n              });\n            }}\n          >\n            first\n          </button>\n          <button\n            onClick={() => {\n              toast('msg1-c2', {\n                containerId: Containers.Second\n              });\n              toast('msg2-c2', {\n                containerId: Containers.Second\n              });\n            }}\n          >\n            second\n          </button>\n          <button\n            onClick={() => {\n              toast.clearWaitingQueue({ containerId: Containers.First });\n            }}\n          >\n            clear\n          </button>\n        </div>\n\n        <ToastContainer autoClose={false} position=\"top-left\" limit={1} containerId={Containers.First} closeOnClick />\n        <ToastContainer autoClose={false} position=\"top-right\" limit={1} containerId={Containers.Second} closeOnClick />\n      </>\n    );\n    cy.findByRole('button', { name: 'first' }).click();\n    cy.findByRole('button', { name: 'second' }).click();\n    cy.resolveEntranceAnimation();\n\n    cy.findByText('msg2-c1').should('not.exist');\n    cy.findByText('msg2-c2').should('not.exist');\n\n    cy.findByText('msg1-c1').should('exist');\n    cy.findByText('msg1-c2').should('exist');\n\n    cy.findByText('msg1-c1').then(() => {\n      cy.findByRole('button', { name: 'clear' }).click();\n      cy.findByText('msg1-c1')\n        .click()\n        .then(() => {\n          cy.resolveEntranceAnimation();\n          cy.findByText('msg1-c1').should('not.exist');\n          cy.findByText('msg2-c1').should('not.exist');\n        });\n    });\n  });\n\n  it('update a toast even when using multi containers', () => {\n    const id = 'boo';\n\n    cy.mount(\n      <>\n        <button\n          onClick={() => {\n            toast('second container', {\n              toastId: id,\n              containerId: Containers.Second\n            });\n          }}\n        >\n          notify\n        </button>\n        <button\n          onClick={() => {\n            toast.update(id, {\n              render: 'second container updated',\n              containerId: Containers.Second\n            });\n          }}\n        >\n          update\n        </button>\n        <ToastContainer autoClose={false} position=\"top-right\" containerId={Containers.Second} closeOnClick />\n      </>\n    );\n    cy.findByRole('button', { name: 'notify' }).click();\n    cy.resolveEntranceAnimation();\n\n    cy.findByText('second container')\n      .should('exist')\n      .then(() => {\n        cy.findByRole('button', { name: 'update' }).click();\n        cy.findByText('second container updated').should('exist');\n      });\n  });\n\n  xit('remove toast for a given container', () => {\n    const toastId = '123';\n\n    cy.mount(\n      <>\n        <div style={{ display: 'grid', placeItems: 'center' }}>\n          <button\n            onClick={() => {\n              toast('second container', {\n                toastId,\n                containerId: Containers.Second\n              });\n            }}\n          >\n            notify\n          </button>\n          <button\n            onClick={() => {\n              toast.dismiss({\n                containerId: Containers.Second,\n                id: toastId\n              });\n            }}\n          >\n            clear\n          </button>\n        </div>\n\n        <ToastContainer autoClose={false} position=\"top-right\" containerId={Containers.Second} closeOnClick />\n      </>\n    );\n\n    cy.findByRole('button', { name: 'notify' }).click();\n    cy.resolveEntranceAnimation();\n\n    cy.findByText('second container')\n      .should('exist')\n      .then(() => {\n        cy.findByRole('button', { name: 'clear' }).click();\n\n        cy.findByText('second container').should('not.exist');\n      });\n  });\n\n  xit('remove all toasts for a given container', () => {\n    const toastId = '123';\n\n    cy.mount(\n      <>\n        <div style={{ display: 'grid', placeItems: 'center' }}>\n          <button\n            onClick={() => {\n              toast('first container', {\n                toastId,\n                containerId: Containers.First\n              });\n              toast('third container', {\n                toastId,\n                containerId: Containers.Third\n              });\n              toast('third container second toast', {\n                containerId: Containers.Third\n              });\n            }}\n          >\n            notify\n          </button>\n          <button\n            onClick={() => {\n              toast.dismiss({\n                containerId: Containers.Third\n              });\n            }}\n          >\n            clear third\n          </button>\n          <button\n            onClick={() => {\n              toast.dismiss({ containerId: 'Non-Existing Container Id' });\n            }}\n          >\n            clear non-existent\n          </button>\n        </div>\n\n        <ToastContainer autoClose={false} position=\"top-left\" containerId={Containers.First} closeOnClick />\n        <ToastContainer autoClose={false} position=\"top-right\" containerId={Containers.Second} closeOnClick />\n        <ToastContainer autoClose={false} position=\"top-right\" containerId={Containers.Third} closeOnClick />\n      </>\n    );\n\n    cy.findByRole('button', { name: 'notify' }).click();\n\n    cy.resolveEntranceAnimation();\n\n    cy.findByText('first container').should('exist');\n    cy.findByText('third container second toast').should('exist');\n    cy.findByText('third container')\n      .should('exist')\n      .then(() => {\n        cy.findByRole('button', { name: 'clear third' }).click();\n        cy.resolveEntranceAnimation();\n\n        cy.findByText('first container').should('exist');\n        cy.findByText('third container').should('not.exist');\n        cy.findByText('third container second toast').should('not.exist');\n        cy.findByText('first container')\n          .should('exist')\n          .then(() => {\n            cy.findByRole('button', { name: 'clear non-existent' }).click();\n            cy.findByText('first container').should('not.exist');\n            cy.findByText('third container').should('not.exist');\n          });\n      });\n  });\n\n  describe('with limit', () => {\n    beforeEach(() => {\n      cy.mount(<ToastContainer autoClose={false} limit={2} closeOnClick />);\n    });\n    it('limit the number of toast displayed', () => {\n      toast('msg1');\n      toast('msg2');\n      toast('msg3');\n      cy.resolveEntranceAnimation();\n\n      cy.findByText('msg3').should('not.exist');\n      cy.findByText('msg1').should('exist');\n      cy.findByText('msg2')\n        .should('exist')\n        .click()\n        .then(() => {\n          cy.resolveEntranceAnimation();\n          cy.findByText('msg3').should('exist');\n        });\n    });\n\n    it('clear waiting queue', () => {\n      toast('msg1');\n      toast('msg2');\n      toast('msg3');\n      cy.resolveEntranceAnimation();\n\n      cy.findByText('msg3').should('not.exist');\n      cy.findByText('msg1').should('exist');\n      cy.findByText('msg2')\n        .should('exist')\n        .then(() => {\n          toast.clearWaitingQueue();\n          cy.findByText('msg2')\n            .click()\n            .then(() => {\n              cy.resolveEntranceAnimation();\n              cy.findByText('msg3').should('not.exist');\n            });\n        });\n    });\n  });\n});\n\ndescribe('with stacked container', () => {\n  it('render toasts', () => {\n    cy.mount(<ToastContainer autoClose={false} stacked />);\n    toast('hello 1');\n    toast('hello 2');\n    toast('hello 3');\n\n    cy.findByText('hello 1').should('exist').and('not.be.visible');\n    cy.findByText('hello 2').should('exist').and('not.be.visible');\n    cy.findByText('hello 3').should('exist').and('be.visible');\n  });\n});\n"
  },
  {
    "path": "src/core/toast.ts",
    "content": "import {\n  ClearWaitingQueueFunc,\n  Id,\n  IdOpts,\n  NotValidatedToastProps,\n  OnChangeCallback,\n  ToastContent,\n  ToastOptions,\n  ToastProps,\n  TypeOptions,\n  UpdateOptions\n} from '../types';\nimport { isFn, isNum, isStr, Type } from '../utils';\nimport { genToastId } from './genToastId';\nimport { clearWaitingQueue, getToast, isToastActive, onChange, pushToast, removeToast, toggleToast } from './store';\n\n/**\n * Generate a toastId or use the one provided\n */\nfunction getToastId<TData>(options?: ToastOptions<TData>) {\n  return options && (isStr(options.toastId) || isNum(options.toastId)) ? options.toastId : genToastId();\n}\n\n/**\n * If the container is not mounted, the toast is enqueued\n */\nfunction dispatchToast<TData>(content: ToastContent<TData>, options: NotValidatedToastProps): Id {\n  pushToast(content, options);\n  return options.toastId;\n}\n\n/**\n * Merge provided options with the defaults settings and generate the toastId\n */\nfunction mergeOptions<TData>(type: string, options?: ToastOptions<TData>) {\n  return {\n    ...options,\n    type: (options && options.type) || type,\n    toastId: getToastId(options)\n  } as NotValidatedToastProps;\n}\n\nfunction createToastByType(type: string) {\n  return <TData = unknown>(content: ToastContent<TData>, options?: ToastOptions<TData>) =>\n    dispatchToast(content, mergeOptions(type, options));\n}\n\nfunction toast<TData = unknown>(content: ToastContent<TData>, options?: ToastOptions<TData>) {\n  return dispatchToast(content, mergeOptions(Type.DEFAULT, options));\n}\n\ntoast.loading = <TData = unknown>(content: ToastContent<TData>, options?: ToastOptions<TData>) =>\n  dispatchToast(\n    content,\n    mergeOptions(Type.DEFAULT, {\n      isLoading: true,\n      autoClose: false,\n      closeOnClick: false,\n      closeButton: false,\n      draggable: false,\n      ...options\n    })\n  );\n\nexport interface ToastPromiseParams<TData = unknown, TError = unknown, TPending = unknown> {\n  pending?: string | UpdateOptions<TPending>;\n  success?: string | UpdateOptions<TData>;\n  error?: string | UpdateOptions<TError>;\n}\n\nfunction handlePromise<TData = unknown, TError = unknown, TPending = unknown>(\n  promise: Promise<TData> | (() => Promise<TData>),\n  { pending, error, success }: ToastPromiseParams<TData, TError, TPending>,\n  options?: ToastOptions<TData>\n) {\n  let id: Id;\n\n  if (pending) {\n    id = isStr(pending)\n      ? toast.loading(pending, options)\n      : toast.loading(pending.render, {\n          ...options,\n          ...(pending as ToastOptions)\n        } as ToastOptions<TPending>);\n  }\n\n  const resetParams = {\n    isLoading: null,\n    autoClose: null,\n    closeOnClick: null,\n    closeButton: null,\n    draggable: null\n  };\n\n  const resolver = <T>(type: TypeOptions, input: string | UpdateOptions<T> | undefined, result: T) => {\n    // Remove the toast if the input has not been provided. This prevents the toast from hanging\n    // in the pending state if a success/error toast has not been provided.\n    if (input == null) {\n      toast.dismiss(id);\n      return;\n    }\n\n    const baseParams = {\n      type,\n      ...resetParams,\n      ...options,\n      data: result\n    };\n    const params = isStr(input) ? { render: input } : input;\n\n    // if the id is set we know that it's an update\n    if (id) {\n      toast.update(id, {\n        ...baseParams,\n        ...params\n      } as UpdateOptions);\n    } else {\n      // using toast.promise without loading\n      toast(params!.render, {\n        ...baseParams,\n        ...params\n      } as ToastOptions<T>);\n    }\n\n    return result;\n  };\n\n  const p = isFn(promise) ? promise() : promise;\n\n  //call the resolvers only when needed\n  p.then(result => resolver('success', success, result)).catch(err => resolver('error', error, err));\n\n  return p;\n}\n\n/**\n * Supply a promise or a function that return a promise and the notification will be updated if it resolves or fails.\n * When the promise is pending a spinner is displayed by default.\n * `toast.promise` returns the provided promise so you can chain it.\n *\n * Simple example:\n *\n * ```\n * toast.promise(MyPromise,\n *  {\n *    pending: 'Promise is pending',\n *    success: 'Promise resolved 👌',\n *    error: 'Promise rejected 🤯'\n *  }\n * )\n *\n * ```\n *\n * Advanced usage:\n * ```\n * toast.promise<{name: string}, {message: string}, undefined>(\n *    resolveWithSomeData,\n *    {\n *      pending: {\n *        render: () => \"I'm loading\",\n *        icon: false,\n *      },\n *      success: {\n *        render: ({data}) => `Hello ${data.name}`,\n *        icon: \"🟢\",\n *      },\n *      error: {\n *        render({data}){\n *          // When the promise reject, data will contains the error\n *          return <MyErrorComponent message={data.message} />\n *        }\n *      }\n *    }\n * )\n * ```\n */\ntoast.promise = handlePromise;\ntoast.success = createToastByType(Type.SUCCESS);\ntoast.info = createToastByType(Type.INFO);\ntoast.error = createToastByType(Type.ERROR);\ntoast.warning = createToastByType(Type.WARNING);\ntoast.warn = toast.warning;\ntoast.dark = (content: ToastContent, options?: ToastOptions) =>\n  dispatchToast(\n    content,\n    mergeOptions(Type.DEFAULT, {\n      theme: 'dark',\n      ...options\n    })\n  );\n\ninterface RemoveParams {\n  id?: Id;\n  containerId: Id;\n}\n\nfunction dismiss(params: RemoveParams): void;\nfunction dismiss(params?: Id): void;\nfunction dismiss(params?: Id | RemoveParams) {\n  removeToast(params);\n}\n\n/**\n * Remove toast programmatically\n *\n * - Remove all toasts:\n * ```\n * toast.dismiss()\n * ```\n *\n * - Remove all toasts that belongs to a given container\n * ```\n * toast.dismiss({ container: \"123\" })\n * ```\n *\n * - Remove toast that has a given id regardless the container\n * ```\n * toast.dismiss({ id: \"123\" })\n * ```\n *\n * - Remove toast that has a given id for a specific container\n * ```\n * toast.dismiss({ id: \"123\", containerId: \"12\" })\n * ```\n */\ntoast.dismiss = dismiss;\n\n/**\n * Clear waiting queue when limit is used\n */\ntoast.clearWaitingQueue = clearWaitingQueue as ClearWaitingQueueFunc;\n\n/**\n * Check if a toast is active\n *\n * - Check regardless the container\n * ```\n * toast.isActive(\"123\")\n * ```\n *\n * - Check in a specific container\n * ```\n * toast.isActive(\"123\", \"containerId\")\n * ```\n */\ntoast.isActive = isToastActive;\n\n/**\n * Update a toast, see https://fkhadra.github.io/react-toastify/update-toast/ for more\n *\n * Example:\n * ```\n * // With a string\n * toast.update(toastId, {\n *    render: \"New content\",\n *    type: \"info\",\n * });\n *\n * // Or with a component\n * toast.update(toastId, {\n *    render: MyComponent\n * });\n *\n * // Or a function\n * toast.update(toastId, {\n *    render: () => <div>New content</div>\n * });\n *\n * // Apply a transition\n * toast.update(toastId, {\n *   render: \"New Content\",\n *   type: toast.TYPE.INFO,\n *   transition: Rotate\n * })\n * ```\n */\ntoast.update = <TData = unknown>(toastId: Id, options: UpdateOptions<TData> = {}) => {\n  const toast = getToast(toastId, options as ToastOptions);\n\n  if (toast) {\n    const { props: oldOptions, content: oldContent } = toast;\n\n    const nextOptions = {\n      delay: 100,\n      ...oldOptions,\n      ...options,\n      toastId: options.toastId || toastId,\n      updateId: genToastId()\n    } as ToastProps & UpdateOptions;\n\n    if (nextOptions.toastId !== toastId) nextOptions.staleId = toastId;\n\n    const content = nextOptions.render || oldContent;\n    delete nextOptions.render;\n\n    dispatchToast(content, nextOptions);\n  }\n};\n\n/**\n * Used for controlled progress bar. It will automatically close the notification.\n *\n * If you don't want your notification to be clsoed when the timer is done you should use `toast.update` instead as follow instead:\n *\n * ```\n * toast.update(id, {\n *    progress: null, // remove controlled progress bar\n *    render: \"ok\",\n *    type: \"success\",\n *    autoClose: 5000 // set autoClose to the desired value\n *   });\n * ```\n */\ntoast.done = (id: Id) => {\n  toast.update(id, {\n    progress: 1\n  });\n};\n\n/**\n * Subscribe to change when a toast is added, removed and updated\n *\n * Usage:\n * ```\n * const unsubscribe = toast.onChange((payload) => {\n *   switch (payload.status) {\n *   case \"added\":\n *     // new toast added\n *     break;\n *   case \"updated\":\n *     // toast updated\n *     break;\n *   case \"removed\":\n *     // toast has been removed\n *     break;\n *   }\n * })\n * ```\n */\ntoast.onChange = onChange as (cb: OnChangeCallback) => () => void;\n\n/**\n * Play a toast(s) timer progammatically\n *\n * Usage:\n *\n * - Play all toasts\n * ```\n * toast.play()\n * ```\n *\n * - Play all toasts for a given container\n * ```\n * toast.play({ containerId: \"123\" })\n * ```\n *\n * - Play toast that has a given id regardless the container\n * ```\n * toast.play({ id: \"123\" })\n * ```\n *\n * - Play toast that has a given id for a specific container\n * ```\n * toast.play({ id: \"123\", containerId: \"12\" })\n * ```\n */\ntoast.play = (opts?: IdOpts) => toggleToast(true, opts);\n\n/**\n * Pause a toast(s) timer progammatically\n *\n * Usage:\n *\n * - Pause all toasts\n * ```\n * toast.pause()\n * ```\n *\n * - Pause all toasts for a given container\n * ```\n * toast.pause({ containerId: \"123\" })\n * ```\n *\n * - Pause toast that has a given id regardless the container\n * ```\n * toast.pause({ id: \"123\" })\n * ```\n *\n * - Pause toast that has a given id for a specific container\n * ```\n * toast.pause({ id: \"123\", containerId: \"12\" })\n * ```\n */\ntoast.pause = (opts?: IdOpts) => toggleToast(false, opts);\n\nexport { toast };\n"
  },
  {
    "path": "src/hooks/index.ts",
    "content": "export * from './useToastContainer';\nexport * from './useToast';\n"
  },
  {
    "path": "src/hooks/useIsomorphicLayoutEffect.ts",
    "content": "import { useEffect, useLayoutEffect } from 'react';\n\nexport const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n"
  },
  {
    "path": "src/hooks/useToast.ts",
    "content": "import { DOMAttributes, useEffect, useRef, useState } from 'react';\n\nimport { ToastProps } from '../types';\nimport { Default, Direction } from '../utils';\nimport { registerToggle } from '../core/store';\n\ninterface Draggable {\n  start: number;\n  delta: number;\n  removalDistance: number;\n  canCloseOnClick: boolean;\n  canDrag: boolean;\n  didMove: boolean;\n}\n\nexport function useToast(props: ToastProps) {\n  const [isRunning, setIsRunning] = useState(false);\n  const [preventExitTransition, setPreventExitTransition] = useState(false);\n  const toastRef = useRef<HTMLDivElement>(null);\n  const drag = useRef<Draggable>({\n    start: 0,\n    delta: 0,\n    removalDistance: 0,\n    canCloseOnClick: true,\n    canDrag: false,\n    didMove: false\n  }).current;\n  const { autoClose, pauseOnHover, closeToast, onClick, closeOnClick } = props;\n\n  registerToggle({\n    id: props.toastId,\n    containerId: props.containerId,\n    fn: setIsRunning\n  });\n\n  useEffect(() => {\n    if (props.pauseOnFocusLoss) {\n      bindFocusEvents();\n\n      return () => {\n        unbindFocusEvents();\n      };\n    }\n  }, [props.pauseOnFocusLoss]);\n\n  function bindFocusEvents() {\n    if (!document.hasFocus()) pauseToast();\n\n    window.addEventListener('focus', playToast);\n    window.addEventListener('blur', pauseToast);\n  }\n\n  function unbindFocusEvents() {\n    window.removeEventListener('focus', playToast);\n    window.removeEventListener('blur', pauseToast);\n  }\n\n  function onDragStart(e: React.PointerEvent<HTMLElement>) {\n    if (props.draggable === true || props.draggable === e.pointerType) {\n      bindDragEvents();\n      const toast = toastRef.current!;\n      drag.canCloseOnClick = true;\n      drag.canDrag = true;\n      toast.style.transition = 'none';\n\n      if (props.draggableDirection === Direction.X) {\n        drag.start = e.clientX;\n        drag.removalDistance = toast.offsetWidth * (props.draggablePercent / 100);\n      } else {\n        drag.start = e.clientY;\n        drag.removalDistance =\n          (toast.offsetHeight *\n            (props.draggablePercent === Default.DRAGGABLE_PERCENT\n              ? props.draggablePercent * 1.5\n              : props.draggablePercent)) /\n          100;\n      }\n    }\n  }\n\n  function onDragTransitionEnd(e: React.PointerEvent<HTMLElement>) {\n    const { top, bottom, left, right } = toastRef.current!.getBoundingClientRect();\n\n    if (\n      e.nativeEvent.type !== 'touchend' &&\n      props.pauseOnHover &&\n      e.clientX >= left &&\n      e.clientX <= right &&\n      e.clientY >= top &&\n      e.clientY <= bottom\n    ) {\n      pauseToast();\n    } else {\n      playToast();\n    }\n  }\n\n  function playToast() {\n    setIsRunning(true);\n  }\n\n  function pauseToast() {\n    setIsRunning(false);\n  }\n\n  function bindDragEvents() {\n    drag.didMove = false;\n    document.addEventListener('pointermove', onDragMove);\n    document.addEventListener('pointerup', onDragEnd);\n  }\n\n  function unbindDragEvents() {\n    document.removeEventListener('pointermove', onDragMove);\n    document.removeEventListener('pointerup', onDragEnd);\n  }\n\n  function onDragMove(e: PointerEvent) {\n    const toast = toastRef.current!;\n    if (drag.canDrag && toast) {\n      drag.didMove = true;\n      if (isRunning) pauseToast();\n      if (props.draggableDirection === Direction.X) {\n        drag.delta = e.clientX - drag.start;\n      } else {\n        drag.delta = e.clientY - drag.start;\n      }\n\n      // prevent false positive during a toast click\n      if (drag.start !== e.clientX) drag.canCloseOnClick = false;\n      const translate =\n        props.draggableDirection === 'x' ? `${drag.delta}px, var(--y)` : `0, calc(${drag.delta}px + var(--y))`;\n      toast.style.transform = `translate3d(${translate},0)`;\n      toast.style.opacity = `${1 - Math.abs(drag.delta / drag.removalDistance)}`;\n    }\n  }\n\n  function onDragEnd() {\n    unbindDragEvents();\n    const toast = toastRef.current!;\n    if (drag.canDrag && drag.didMove && toast) {\n      drag.canDrag = false;\n      if (Math.abs(drag.delta) > drag.removalDistance) {\n        setPreventExitTransition(true);\n        props.closeToast(true);\n        props.collapseAll();\n        return;\n      }\n\n      toast.style.transition = 'transform 0.2s, opacity 0.2s';\n      toast.style.removeProperty('transform');\n      toast.style.removeProperty('opacity');\n    }\n  }\n\n  const eventHandlers: DOMAttributes<HTMLElement> = {\n    onPointerDown: onDragStart,\n    onPointerUp: onDragTransitionEnd\n  };\n\n  if (autoClose && pauseOnHover) {\n    eventHandlers.onMouseEnter = pauseToast;\n\n    // progress control is delegated to the container\n    if (!props.stacked) eventHandlers.onMouseLeave = playToast;\n  }\n\n  // prevent toast from closing when user drags the toast\n  if (closeOnClick) {\n    eventHandlers.onClick = (e: React.MouseEvent) => {\n      onClick && onClick(e);\n      drag.canCloseOnClick && closeToast(true);\n    };\n  }\n\n  return {\n    playToast,\n    pauseToast,\n    isRunning,\n    preventExitTransition,\n    toastRef,\n    eventHandlers\n  };\n}\n"
  },
  {
    "path": "src/hooks/useToastContainer.ts",
    "content": "import { useRef, useSyncExternalStore } from 'react';\nimport { isToastActive, registerContainer } from '../core/store';\nimport { Toast, ToastContainerProps, ToastPosition } from '../types';\n\nexport function useToastContainer(props: ToastContainerProps) {\n  const { subscribe, getSnapshot, setProps } = useRef(registerContainer(props)).current;\n  setProps(props);\n  const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)?.slice();\n\n  function getToastToRender<T>(cb: (position: ToastPosition, toastList: Toast[]) => T) {\n    if (!snapshot) return [];\n\n    const toRender = new Map<ToastPosition, Toast[]>();\n\n    if (props.newestOnTop) snapshot.reverse();\n\n    snapshot.forEach(toast => {\n      const { position } = toast.props;\n      toRender.has(position) || toRender.set(position, []);\n      toRender.get(position)!.push(toast);\n    });\n\n    return Array.from(toRender, p => cb(p[0], p[1]));\n  }\n\n  return {\n    getToastToRender,\n    isToastActive,\n    count: snapshot?.length\n  };\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import './style.css';\n\nexport { cssTransition, collapseToast } from './utils';\nexport { ToastContainer, Bounce, Flip, Slide, Zoom, Icons } from './components';\nexport type { IconProps, CloseButton } from './components';\nexport type { ToastPromiseParams } from './core';\nexport { toast } from './core';\nexport type {\n  TypeOptions,\n  Theme,\n  ToastPosition,\n  ToastContentProps,\n  ToastContent,\n  ToastTransition,\n  ToastClassName,\n  ClearWaitingQueueParams,\n  DraggableDirection,\n  ToastOptions,\n  UpdateOptions,\n  ToastContainerProps,\n  ToastTransitionProps,\n  Id,\n  ToastItem,\n  ClearWaitingQueueFunc,\n  OnChangeCallback,\n  ToastIcon\n} from './types';\nexport type { CloseButtonProps } from './components/CloseButton';\n"
  },
  {
    "path": "src/style.css",
    "content": ":root {\n  --toastify-color-light: #fff;\n  --toastify-color-dark: #121212;\n  --toastify-color-info: #3498db;\n  --toastify-color-success: #07bc0c;\n  --toastify-color-warning: #f1c40f;\n  --toastify-color-error: hsl(6, 78%, 57%);\n  --toastify-color-transparent: rgba(255, 255, 255, 0.7);\n\n  --toastify-icon-color-info: var(--toastify-color-info);\n  --toastify-icon-color-success: var(--toastify-color-success);\n  --toastify-icon-color-warning: var(--toastify-color-warning);\n  --toastify-icon-color-error: var(--toastify-color-error);\n\n  --toastify-container-width: fit-content;\n  --toastify-toast-width: 320px;\n  --toastify-toast-offset: 16px;\n  --toastify-toast-top: max(var(--toastify-toast-offset), env(safe-area-inset-top));\n  --toastify-toast-right: max(var(--toastify-toast-offset), env(safe-area-inset-right));\n  --toastify-toast-left: max(var(--toastify-toast-offset), env(safe-area-inset-left));\n  --toastify-toast-bottom: max(var(--toastify-toast-offset), env(safe-area-inset-bottom));\n  --toastify-toast-background: #fff;\n  --toastify-toast-padding: 14px;\n  --toastify-toast-min-height: 64px;\n  --toastify-toast-max-height: 800px;\n  --toastify-toast-bd-radius: 6px;\n  --toastify-toast-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);\n  --toastify-font-family: sans-serif;\n  --toastify-z-index: 9999;\n  --toastify-text-color-light: #757575;\n  --toastify-text-color-dark: #fff;\n\n  /* Used only for colored theme */\n  --toastify-text-color-info: #fff;\n  --toastify-text-color-success: #fff;\n  --toastify-text-color-warning: #fff;\n  --toastify-text-color-error: #fff;\n\n  --toastify-spinner-color: #616161;\n  --toastify-spinner-color-empty-area: #e0e0e0;\n  --toastify-color-progress-light: linear-gradient(to right, #4cd964, #5ac8fa, #007aff, #34aadc, #5856d6, #ff2d55);\n  --toastify-color-progress-dark: #bb86fc;\n  --toastify-color-progress-info: var(--toastify-color-info);\n  --toastify-color-progress-success: var(--toastify-color-success);\n  --toastify-color-progress-warning: var(--toastify-color-warning);\n  --toastify-color-progress-error: var(--toastify-color-error);\n  /* used to control the opacity of the progress trail */\n  --toastify-color-progress-bgo: 0.2;\n}\n\n.Toastify__toast-container {\n  z-index: var(--toastify-z-index);\n  -webkit-transform: translate3d(0, 0, var(--toastify-z-index));\n  position: fixed;\n  width: var(--toastify-container-width);\n  box-sizing: border-box;\n  color: #fff;\n  display: flex;\n  flex-direction: column;\n}\n\n.Toastify__toast-container--top-left {\n  top: var(--toastify-toast-top);\n  left: var(--toastify-toast-left);\n}\n.Toastify__toast-container--top-center {\n  top: var(--toastify-toast-top);\n  left: 50%;\n  transform: translateX(-50%);\n  align-items: center;\n}\n.Toastify__toast-container--top-right {\n  top: var(--toastify-toast-top);\n  right: var(--toastify-toast-right);\n  align-items: end;\n}\n.Toastify__toast-container--bottom-left {\n  bottom: var(--toastify-toast-bottom);\n  left: var(--toastify-toast-left);\n}\n.Toastify__toast-container--bottom-center {\n  bottom: var(--toastify-toast-bottom);\n  left: 50%;\n  transform: translateX(-50%);\n  align-items: center;\n}\n.Toastify__toast-container--bottom-right {\n  bottom: var(--toastify-toast-bottom);\n  right: var(--toastify-toast-right);\n  align-items: end;\n}\n\n.Toastify__toast {\n  --y: 0;\n  position: relative;\n  touch-action: none;\n  width: var(--toastify-toast-width);\n  min-height: var(--toastify-toast-min-height);\n  box-sizing: border-box;\n  margin-bottom: 1rem;\n  padding: var(--toastify-toast-padding);\n  border-radius: var(--toastify-toast-bd-radius);\n  box-shadow: var(--toastify-toast-shadow);\n  max-height: var(--toastify-toast-max-height);\n  font-family: var(--toastify-font-family);\n  /* webkit only issue #791 */\n  z-index: 0;\n  /* inner swag */\n  display: flex;\n  flex: 1 auto;\n  align-items: center;\n  word-break: break-word;\n}\n\n@media only screen and (max-width: 480px) {\n  .Toastify__toast-container {\n    width: 100vw;\n    left: env(safe-area-inset-left);\n    margin: 0;\n  }\n  .Toastify__toast-container--top-left,\n  .Toastify__toast-container--top-center,\n  .Toastify__toast-container--top-right {\n    top: env(safe-area-inset-top);\n    transform: translateX(0);\n  }\n  .Toastify__toast-container--bottom-left,\n  .Toastify__toast-container--bottom-center,\n  .Toastify__toast-container--bottom-right {\n    bottom: env(safe-area-inset-bottom);\n    transform: translateX(0);\n  }\n  .Toastify__toast-container--rtl {\n    right: env(safe-area-inset-right);\n    left: initial;\n  }\n  .Toastify__toast {\n    --toastify-toast-width: 100%;\n    margin-bottom: 0;\n    border-radius: 0;\n  }\n}\n\n.Toastify__toast-container[data-stacked='true'] {\n  width: var(--toastify-toast-width);\n}\n\n.Toastify__toast--stacked {\n  position: absolute;\n  width: 100%;\n  transform: translate3d(0, var(--y), 0) scale(var(--s));\n  transition: transform 0.3s;\n}\n\n.Toastify__toast--stacked[data-collapsed] .Toastify__toast-body,\n.Toastify__toast--stacked[data-collapsed] .Toastify__close-button {\n  transition: opacity 0.1s;\n}\n\n.Toastify__toast--stacked[data-collapsed='false'] {\n  overflow: visible;\n}\n\n.Toastify__toast--stacked[data-collapsed='true']:not(:last-child) > * {\n  opacity: 0;\n}\n\n.Toastify__toast--stacked:after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  height: calc(var(--g) * 1px);\n  bottom: 100%;\n}\n\n.Toastify__toast--stacked[data-pos='top'] {\n  top: 0;\n}\n\n.Toastify__toast--stacked[data-pos='bot'] {\n  bottom: 0;\n}\n\n.Toastify__toast--stacked[data-pos='bot'].Toastify__toast--stacked:before {\n  transform-origin: top;\n}\n\n.Toastify__toast--stacked[data-pos='top'].Toastify__toast--stacked:before {\n  transform-origin: bottom;\n}\n\n.Toastify__toast--stacked:before {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  height: 100%;\n  transform: scaleY(3);\n  z-index: -1;\n}\n\n.Toastify__toast--rtl {\n  direction: rtl;\n}\n\n.Toastify__toast--close-on-click {\n  cursor: pointer;\n}\n\n.Toastify__toast-icon {\n  margin-inline-end: 10px;\n  width: 22px;\n  flex-shrink: 0;\n  display: flex;\n}\n\n.Toastify--animate {\n  animation-fill-mode: both;\n  animation-duration: 0.5s;\n}\n\n.Toastify--animate-icon {\n  animation-fill-mode: both;\n  animation-duration: 0.3s;\n}\n\n.Toastify__toast-theme--dark {\n  background: var(--toastify-color-dark);\n  color: var(--toastify-text-color-dark);\n}\n\n.Toastify__toast-theme--light {\n  background: var(--toastify-color-light);\n  color: var(--toastify-text-color-light);\n}\n\n.Toastify__toast-theme--colored.Toastify__toast--default {\n  background: var(--toastify-color-light);\n  color: var(--toastify-text-color-light);\n}\n\n.Toastify__toast-theme--colored.Toastify__toast--info {\n  color: var(--toastify-text-color-info);\n  background: var(--toastify-color-info);\n}\n\n.Toastify__toast-theme--colored.Toastify__toast--success {\n  color: var(--toastify-text-color-success);\n  background: var(--toastify-color-success);\n}\n\n.Toastify__toast-theme--colored.Toastify__toast--warning {\n  color: var(--toastify-text-color-warning);\n  background: var(--toastify-color-warning);\n}\n\n.Toastify__toast-theme--colored.Toastify__toast--error {\n  color: var(--toastify-text-color-error);\n  background: var(--toastify-color-error);\n}\n\n.Toastify__progress-bar-theme--light {\n  background: var(--toastify-color-progress-light);\n}\n\n.Toastify__progress-bar-theme--dark {\n  background: var(--toastify-color-progress-dark);\n}\n\n.Toastify__progress-bar--info {\n  background: var(--toastify-color-progress-info);\n}\n\n.Toastify__progress-bar--success {\n  background: var(--toastify-color-progress-success);\n}\n\n.Toastify__progress-bar--warning {\n  background: var(--toastify-color-progress-warning);\n}\n\n.Toastify__progress-bar--error {\n  background: var(--toastify-color-progress-error);\n}\n\n.Toastify__progress-bar-theme--colored.Toastify__progress-bar--info,\n.Toastify__progress-bar-theme--colored.Toastify__progress-bar--success,\n.Toastify__progress-bar-theme--colored.Toastify__progress-bar--warning,\n.Toastify__progress-bar-theme--colored.Toastify__progress-bar--error {\n  background: var(--toastify-color-transparent);\n}\n\n.Toastify__close-button {\n  color: #fff;\n  position: absolute;\n  top: 6px;\n  right: 6px;\n  background: transparent;\n  outline: none;\n  border: none;\n  padding: 0;\n  cursor: pointer;\n  opacity: 0.7;\n  transition: 0.3s ease;\n  z-index: 1;\n}\n\n.Toastify__toast--rtl .Toastify__close-button {\n  left: 6px;\n  right: unset;\n}\n\n.Toastify__close-button--light {\n  color: #000;\n  opacity: 0.3;\n}\n\n.Toastify__close-button > svg {\n  fill: currentColor;\n  height: 16px;\n  width: 14px;\n}\n\n.Toastify__close-button:hover,\n.Toastify__close-button:focus {\n  opacity: 1;\n}\n\n@keyframes Toastify__trackProgress {\n  0% {\n    transform: scaleX(1);\n  }\n  100% {\n    transform: scaleX(0);\n  }\n}\n\n.Toastify__progress-bar {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 1;\n  opacity: 0.7;\n  transform-origin: left;\n}\n\n.Toastify__progress-bar--animated {\n  animation: Toastify__trackProgress linear 1 forwards;\n}\n\n.Toastify__progress-bar--controlled {\n  transition: transform 0.2s;\n}\n\n.Toastify__progress-bar--rtl {\n  right: 0;\n  left: initial;\n  transform-origin: right;\n  border-bottom-left-radius: initial;\n}\n\n.Toastify__progress-bar--wrp {\n  position: absolute;\n  overflow: hidden;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 5px;\n  border-bottom-left-radius: var(--toastify-toast-bd-radius);\n  border-bottom-right-radius: var(--toastify-toast-bd-radius);\n}\n\n.Toastify__progress-bar--wrp[data-hidden='true'] {\n  opacity: 0;\n}\n\n.Toastify__progress-bar--bg {\n  opacity: var(--toastify-color-progress-bgo);\n  width: 100%;\n  height: 100%;\n}\n\n.Toastify__spinner {\n  width: 20px;\n  height: 20px;\n  box-sizing: border-box;\n  border: 2px solid;\n  border-radius: 100%;\n  border-color: var(--toastify-spinner-color-empty-area);\n  border-right-color: var(--toastify-spinner-color);\n  animation: Toastify__spin 0.65s linear infinite;\n}\n\n@keyframes Toastify__bounceInRight {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  from {\n    opacity: 0;\n    transform: translate3d(3000px, 0, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(-25px, 0, 0);\n  }\n  75% {\n    transform: translate3d(10px, 0, 0);\n  }\n  90% {\n    transform: translate3d(-5px, 0, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n\n@keyframes Toastify__bounceOutRight {\n  20% {\n    opacity: 1;\n    transform: translate3d(-20px, var(--y), 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(2000px, var(--y), 0);\n  }\n}\n\n@keyframes Toastify__bounceInLeft {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  0% {\n    opacity: 0;\n    transform: translate3d(-3000px, 0, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(25px, 0, 0);\n  }\n  75% {\n    transform: translate3d(-10px, 0, 0);\n  }\n  90% {\n    transform: translate3d(5px, 0, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n\n@keyframes Toastify__bounceOutLeft {\n  20% {\n    opacity: 1;\n    transform: translate3d(20px, var(--y), 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(-2000px, var(--y), 0);\n  }\n}\n\n@keyframes Toastify__bounceInUp {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  from {\n    opacity: 0;\n    transform: translate3d(0, 3000px, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(0, -20px, 0);\n  }\n  75% {\n    transform: translate3d(0, 10px, 0);\n  }\n  90% {\n    transform: translate3d(0, -5px, 0);\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n\n@keyframes Toastify__bounceOutUp {\n  20% {\n    transform: translate3d(0, calc(var(--y) - 10px), 0);\n  }\n  40%,\n  45% {\n    opacity: 1;\n    transform: translate3d(0, calc(var(--y) + 20px), 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(0, -2000px, 0);\n  }\n}\n\n@keyframes Toastify__bounceInDown {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  0% {\n    opacity: 0;\n    transform: translate3d(0, -3000px, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(0, 25px, 0);\n  }\n  75% {\n    transform: translate3d(0, -10px, 0);\n  }\n  90% {\n    transform: translate3d(0, 5px, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n\n@keyframes Toastify__bounceOutDown {\n  20% {\n    transform: translate3d(0, calc(var(--y) - 10px), 0);\n  }\n  40%,\n  45% {\n    opacity: 1;\n    transform: translate3d(0, calc(var(--y) + 20px), 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(0, 2000px, 0);\n  }\n}\n\n.Toastify__bounce-enter--top-left,\n.Toastify__bounce-enter--bottom-left {\n  animation-name: Toastify__bounceInLeft;\n}\n\n.Toastify__bounce-enter--top-right,\n.Toastify__bounce-enter--bottom-right {\n  animation-name: Toastify__bounceInRight;\n}\n\n.Toastify__bounce-enter--top-center {\n  animation-name: Toastify__bounceInDown;\n}\n\n.Toastify__bounce-enter--bottom-center {\n  animation-name: Toastify__bounceInUp;\n}\n\n.Toastify__bounce-exit--top-left,\n.Toastify__bounce-exit--bottom-left {\n  animation-name: Toastify__bounceOutLeft;\n}\n\n.Toastify__bounce-exit--top-right,\n.Toastify__bounce-exit--bottom-right {\n  animation-name: Toastify__bounceOutRight;\n}\n\n.Toastify__bounce-exit--top-center {\n  animation-name: Toastify__bounceOutUp;\n}\n\n.Toastify__bounce-exit--bottom-center {\n  animation-name: Toastify__bounceOutDown;\n}\n\n@keyframes Toastify__zoomIn {\n  from {\n    opacity: 0;\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n  50% {\n    opacity: 1;\n  }\n}\n\n@keyframes Toastify__zoomOut {\n  from {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0;\n    transform: translate3d(0, var(--y), 0) scale3d(0.3, 0.3, 0.3);\n  }\n  to {\n    opacity: 0;\n  }\n}\n\n.Toastify__zoom-enter {\n  animation-name: Toastify__zoomIn;\n}\n\n.Toastify__zoom-exit {\n  animation-name: Toastify__zoomOut;\n}\n\n@keyframes Toastify__flipIn {\n  from {\n    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n    animation-timing-function: ease-in;\n    opacity: 0;\n  }\n  40% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n    animation-timing-function: ease-in;\n  }\n  60% {\n    transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n    opacity: 1;\n  }\n  80% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n  }\n  to {\n    transform: perspective(400px);\n  }\n}\n\n@keyframes Toastify__flipOut {\n  from {\n    transform: translate3d(0, var(--y), 0) perspective(400px);\n  }\n  30% {\n    transform: translate3d(0, var(--y), 0) perspective(400px) rotate3d(1, 0, 0, -20deg);\n    opacity: 1;\n  }\n  to {\n    transform: translate3d(0, var(--y), 0) perspective(400px) rotate3d(1, 0, 0, 90deg);\n    opacity: 0;\n  }\n}\n\n.Toastify__flip-enter {\n  animation-name: Toastify__flipIn;\n}\n\n.Toastify__flip-exit {\n  animation-name: Toastify__flipOut;\n}\n\n@keyframes Toastify__slideInRight {\n  from {\n    transform: translate3d(110%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, var(--y), 0);\n  }\n}\n\n@keyframes Toastify__slideInLeft {\n  from {\n    transform: translate3d(-110%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, var(--y), 0);\n  }\n}\n\n@keyframes Toastify__slideInUp {\n  from {\n    transform: translate3d(0, 110%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, var(--y), 0);\n  }\n}\n\n@keyframes Toastify__slideInDown {\n  from {\n    transform: translate3d(0, -110%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, var(--y), 0);\n  }\n}\n\n@keyframes Toastify__slideOutRight {\n  from {\n    transform: translate3d(0, var(--y), 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(110%, var(--y), 0);\n  }\n}\n\n@keyframes Toastify__slideOutLeft {\n  from {\n    transform: translate3d(0, var(--y), 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-110%, var(--y), 0);\n  }\n}\n\n@keyframes Toastify__slideOutDown {\n  from {\n    transform: translate3d(0, var(--y), 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 500px, 0);\n  }\n}\n\n@keyframes Toastify__slideOutUp {\n  from {\n    transform: translate3d(0, var(--y), 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -500px, 0);\n  }\n}\n\n.Toastify__slide-enter--top-left,\n.Toastify__slide-enter--bottom-left {\n  animation-name: Toastify__slideInLeft;\n}\n\n.Toastify__slide-enter--top-right,\n.Toastify__slide-enter--bottom-right {\n  animation-name: Toastify__slideInRight;\n}\n\n.Toastify__slide-enter--top-center {\n  animation-name: Toastify__slideInDown;\n}\n\n.Toastify__slide-enter--bottom-center {\n  animation-name: Toastify__slideInUp;\n}\n\n.Toastify__slide-exit--top-left,\n.Toastify__slide-exit--bottom-left {\n  animation-name: Toastify__slideOutLeft;\n  animation-timing-function: ease-in;\n  animation-duration: 0.3s;\n}\n\n.Toastify__slide-exit--top-right,\n.Toastify__slide-exit--bottom-right {\n  animation-name: Toastify__slideOutRight;\n  animation-timing-function: ease-in;\n  animation-duration: 0.3s;\n}\n\n.Toastify__slide-exit--top-center {\n  animation-name: Toastify__slideOutUp;\n  animation-timing-function: ease-in;\n  animation-duration: 0.3s;\n}\n\n.Toastify__slide-exit--bottom-center {\n  animation-name: Toastify__slideOutDown;\n  animation-timing-function: ease-in;\n  animation-duration: 0.3s;\n}\n\n@keyframes Toastify__spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "src/tests.cy.tsx",
    "content": "import React from 'react';\nimport { ToastContainer } from './components';\nimport { toast } from './core';\nimport { ToastContentProps } from './types';\n\nit('allows to specify the reason when calling closeToast', () => {\n  const onCloseFunc = cy.stub().as('onCloseFunc');\n\n  function CustomNotification({ closeToast }: ToastContentProps) {\n    return (\n      <button\n        onClick={() => {\n          closeToast('foobar');\n        }}\n      >\n        closeme\n      </button>\n    );\n  }\n\n  cy.mount(\n    <div>\n      <button\n        onClick={() => {\n          toast(CustomNotification, {\n            onClose: onCloseFunc\n          });\n        }}\n      >\n        notify\n      </button>\n      <ToastContainer autoClose={false} />\n    </div>\n  );\n\n  cy.findByRole('button', { name: 'notify' }).click();\n  cy.findByRole('alert').should('exist');\n  cy.findByRole('button', { name: 'closeme' }).click();\n\n  cy.get('@onCloseFunc').should('have.been.calledWith', 'foobar');\n});\n\nit('focus notification when alt+t is pressed', () => {\n  cy.mount(\n    <div>\n      <button\n        onClick={() => {\n          toast('hello', {\n            ariaLabel: 'notification'\n          });\n        }}\n      >\n        notify\n      </button>\n      <ToastContainer autoClose={false} />\n    </div>\n  );\n\n  cy.findByRole('button', { name: 'notify' }).click();\n  cy.resolveEntranceAnimation();\n  cy.findByRole('alert').should('exist');\n  cy.get('body').type('{alt+t}');\n  cy.focused().should('have.attr', 'role', 'alert').and('have.attr', 'aria-label', 'notification');\n});\n"
  },
  {
    "path": "src/types.ts",
    "content": "import React, { HTMLAttributes } from 'react';\nimport { CloseButtonProps, IconProps } from './components';\nimport { clearWaitingQueue } from './core/store';\n\ntype Nullable<T> = {\n  [P in keyof T]: T[P] | null;\n};\n\nexport type TypeOptions = 'info' | 'success' | 'warning' | 'error' | 'default';\n\nexport type Theme = 'light' | 'dark' | 'colored' | (string & {});\n\nexport type ToastPosition = 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left';\n\nexport type CloseToastFunc = ((reason?: boolean | string) => void) & ((e: React.MouseEvent) => void);\n\nexport interface ToastContentProps<Data = unknown> {\n  closeToast: CloseToastFunc;\n  toastProps: ToastProps;\n  isPaused: boolean;\n  data: Data;\n}\n\nexport type ToastContent<T = unknown> = React.ReactNode | ((props: ToastContentProps<T>) => React.ReactNode);\n\nexport type ToastIcon = false | ((props: IconProps) => React.ReactNode) | React.ReactElement<IconProps>;\n\nexport type Id = number | string;\n\nexport type ToastTransition = React.FC<ToastTransitionProps> | React.ComponentClass<ToastTransitionProps>;\n\n/**\n * ClassName for the elements - can take a function to build a classname or a raw string that is cx'ed to defaults\n */\nexport type ToastClassName =\n  | ((context?: { type?: TypeOptions; defaultClassName?: string; position?: ToastPosition; rtl?: boolean }) => string)\n  | string;\n\nexport interface ClearWaitingQueueParams {\n  containerId?: Id;\n}\n\nexport type DraggableDirection = 'x' | 'y';\n\ninterface CommonOptions {\n  /**\n   * Pause the timer when the mouse hover the toast.\n   * `Default: true`\n   */\n  pauseOnHover?: boolean;\n\n  /**\n   * Pause the toast when the window loses focus.\n   * `Default: true`\n   */\n  pauseOnFocusLoss?: boolean;\n\n  /**\n   * Remove the toast when clicked.\n   * `Default: false`\n   */\n  closeOnClick?: boolean;\n\n  /**\n   * Set the delay in ms to close the toast automatically.\n   * Use `false` to prevent the toast from closing.\n   * `Default: 5000`\n   */\n  autoClose?: number | false;\n\n  /**\n   * Set the default position to use.\n   * `One of: 'top-right', 'top-center', 'top-left', 'bottom-right', 'bottom-center', 'bottom-left'`\n   * `Default: 'top-right'`\n   */\n  position?: ToastPosition;\n\n  /**\n   * Pass a custom close button.\n   * To remove the close button pass `false`\n   */\n  closeButton?: boolean | ((props: CloseButtonProps) => React.ReactNode) | React.ReactElement<CloseButtonProps>;\n\n  /**\n   * An optional css class to set for the progress bar.\n   */\n  progressClassName?: ToastClassName;\n\n  /**\n   * Hide or show the progress bar.\n   * `Default: false`\n   */\n  hideProgressBar?: boolean;\n\n  /**\n   * Pass a custom transition see https://fkhadra.github.io/react-toastify/custom-animation/\n   */\n  transition?: ToastTransition;\n\n  /**\n   * Allow toast to be draggable\n   * `Default: 'touch'`\n   */\n  draggable?: boolean | 'mouse' | 'touch';\n\n  /**\n   * The percentage of the toast's width it takes for a drag to dismiss a toast\n   * `Default: 80`\n   */\n  draggablePercent?: number;\n\n  /**\n   * Specify in which direction should you swipe to dismiss the toast\n   * `Default: \"x\"`\n   */\n\n  draggableDirection?: DraggableDirection;\n\n  /**\n   * Define the ARIA role for the toast\n   * `Default: alert`\n   *  https://www.w3.org/WAI/PF/aria/roles\n   */\n  role?: string;\n\n  /**\n   * Set id to handle multiple container\n   */\n  containerId?: Id;\n\n  /**\n   * Fired when clicking inside toaster\n   */\n  onClick?: (event: React.MouseEvent) => void;\n\n  /**\n   * Support right to left display.\n   * `Default: false`\n   */\n  rtl?: boolean;\n\n  /**\n   * Used to display a custom icon. Set it to `false` to prevent\n   * the icons from being displayed\n   */\n  icon?: ToastIcon;\n\n  /**\n   * Theme to use.\n   * `One of: 'light', 'dark', 'colored'`\n   * `Default: 'light'`\n   */\n  theme?: Theme;\n\n  /**\n   * When set to `true` the built-in progress bar won't be rendered at all. Autoclose delay won't have any effect as well\n   * This is only used when you want to replace the progress bar with your own.\n   *\n   * See https://stackblitz.com/edit/react-toastify-custom-progress-bar?file=src%2FApp.tsx for an example.\n   */\n  customProgressBar?: boolean;\n}\n\nexport interface ToastOptions<Data = unknown> extends CommonOptions {\n  /**\n   * An optional css class to set.\n   */\n  className?: ToastClassName;\n\n  /**\n   * Called when toast is mounted.\n   */\n  onOpen?: () => void;\n\n  /**\n   * Called when toast is unmounted.\n   * The callback first argument is the closure reason.\n   * It is \"true\" when the notification is closed by a user action like clicking on the close button.\n   */\n  onClose?: (reason?: boolean | string) => void;\n\n  /**\n   * An optional inline style to apply.\n   */\n  style?: React.CSSProperties;\n\n  /**\n   * Set the toast type.\n   * `One of: 'info', 'success', 'warning', 'error', 'default'`\n   */\n  type?: TypeOptions;\n\n  /**\n   * Set a custom `toastId`\n   */\n  toastId?: Id;\n\n  /**\n   * Used during update\n   */\n  updateId?: Id;\n\n  /**\n   * Set the percentage for the controlled progress bar. `Value must be between 0 and 1.`\n   */\n  progress?: number;\n\n  /**\n   * Let you provide any data, useful when you are using your own component\n   */\n  data?: Data;\n\n  /**\n   * Let you specify the aria-label\n   */\n  ariaLabel?: string;\n\n  /**\n   * Add a delay in ms before the toast appear.\n   */\n  delay?: number;\n\n  isLoading?: boolean;\n}\n\nexport interface UpdateOptions<T = unknown> extends Nullable<ToastOptions<T>> {\n  /**\n   * Used to update a toast.\n   * Pass any valid ReactNode(string, number, component)\n   */\n  render?: ToastContent<T>;\n}\n\nexport interface ToastContainerProps extends CommonOptions, Pick<HTMLAttributes<HTMLElement>, 'aria-label'> {\n  /**\n   * An optional css class to set.\n   */\n  className?: ToastClassName;\n\n  /**\n   * Will stack the toast with the newest on the top.\n   */\n  stacked?: boolean;\n\n  /**\n   * Whether or not to display the newest toast on top.\n   * `Default: false`\n   */\n  newestOnTop?: boolean;\n\n  /**\n   * An optional inline style to apply.\n   */\n  style?: React.CSSProperties;\n\n  /**\n   * An optional inline style to apply for the toast.\n   */\n  toastStyle?: React.CSSProperties;\n\n  /**\n   * An optional css class for the toast.\n   */\n  toastClassName?: ToastClassName;\n\n  /**\n   * Limit the number of toast displayed at the same time\n   */\n  limit?: number;\n\n  /**\n   * Shortcut to focus the first notification with the keyboard\n   * `default: Alt+t`\n   *\n   * ```\n   * // focus when user presses ⌘ + F\n   * const matchShortcut = (e: KeyboardEvent) => e.metaKey && e.key === 'f'\n   * ```\n   */\n  hotKeys?: (e: KeyboardEvent) => boolean;\n}\n\nexport interface ToastTransitionProps {\n  isIn: boolean;\n  done: () => void;\n  position: ToastPosition | string;\n  preventExitTransition: boolean;\n  nodeRef: React.RefObject<HTMLElement>;\n  children?: React.ReactNode;\n  playToast(): void;\n}\n\n/**\n * @INTERNAL\n */\nexport interface ToastProps extends ToastOptions {\n  isIn: boolean;\n  staleId?: Id;\n  toastId: Id;\n  key: Id;\n  transition: ToastTransition;\n  closeToast: CloseToastFunc;\n  position: ToastPosition;\n  children?: ToastContent;\n  draggablePercent: number;\n  draggableDirection?: DraggableDirection;\n  progressClassName?: ToastClassName;\n  className?: ToastClassName;\n  deleteToast: () => void;\n  theme: Theme;\n  type: TypeOptions;\n  collapseAll: () => void;\n  stacked?: boolean;\n}\n\n/**\n * @INTERNAL\n */\nexport interface NotValidatedToastProps extends Partial<ToastProps> {\n  toastId: Id;\n}\n\n/**\n * @INTERNAL\n */\nexport interface Toast {\n  content: ToastContent;\n  props: ToastProps;\n  toggle?: (v: boolean) => void;\n  removalReason?: true | undefined;\n  isActive: boolean;\n  staleId?: Id;\n}\n\nexport type ToastItemStatus = 'added' | 'removed' | 'updated';\n\nexport interface ToastItem<Data = {}> {\n  content: ToastContent<Data>;\n  id: Id;\n  theme?: Theme;\n  type?: TypeOptions;\n  isLoading?: boolean;\n  containerId?: Id;\n  data: Data;\n  icon?: ToastIcon;\n  status: ToastItemStatus;\n  reason?: boolean | string;\n}\n\nexport type OnChangeCallback = (toast: ToastItem) => void;\n\nexport type IdOpts = {\n  id?: Id;\n  containerId?: Id;\n};\n\nexport type ClearWaitingQueueFunc = typeof clearWaitingQueue;\n"
  },
  {
    "path": "src/utils/collapseToast.ts",
    "content": "import { Default } from './constant';\n\n/**\n * Used to collapse toast after exit animation\n */\nexport function collapseToast(node: HTMLElement, done: () => void, duration = Default.COLLAPSE_DURATION) {\n  const { scrollHeight, style } = node;\n\n  requestAnimationFrame(() => {\n    style.minHeight = 'initial';\n    style.height = scrollHeight + 'px';\n    style.transition = `all ${duration}ms`;\n\n    requestAnimationFrame(() => {\n      style.height = '0';\n      style.padding = '0';\n      style.margin = '0';\n      setTimeout(done, duration as number);\n    });\n  });\n}\n"
  },
  {
    "path": "src/utils/constant.ts",
    "content": "export const enum Type {\n  INFO = 'info',\n  SUCCESS = 'success',\n  WARNING = 'warning',\n  ERROR = 'error',\n  DEFAULT = 'default'\n}\n\nexport const enum Default {\n  COLLAPSE_DURATION = 300,\n  DEBOUNCE_DURATION = 50,\n  CSS_NAMESPACE = 'Toastify',\n  DRAGGABLE_PERCENT = 80,\n  CONTAINER_ID = 1\n}\n\nexport const enum Direction {\n  X = 'x',\n  Y = 'y'\n}\n"
  },
  {
    "path": "src/utils/cssTransition.tsx",
    "content": "import React, { useEffect, useLayoutEffect, useRef } from 'react';\nimport { collapseToast } from './collapseToast';\nimport { Default } from './constant';\n\nimport { ToastTransitionProps } from '../types';\n\nexport interface CSSTransitionProps {\n  /**\n   * Css class to apply when toast enter\n   */\n  enter: string;\n\n  /**\n   * Css class to apply when toast leave\n   */\n  exit: string;\n\n  /**\n   * Append current toast position to the classname.\n   * If multiple classes are provided, only the last one will get the position\n   * For instance `myclass--top-center`...\n   * `Default: false`\n   */\n  appendPosition?: boolean;\n\n  /**\n   * Collapse toast smoothly when exit animation end\n   * `Default: true`\n   */\n  collapse?: boolean;\n\n  /**\n   * Collapse transition duration\n   * `Default: 300`\n   */\n  collapseDuration?: number;\n}\n\nconst enum AnimationStep {\n  Enter,\n  Exit\n}\n\n/**\n * Css animation that just work.\n * You could use animate.css for instance\n *\n *\n * ```\n * cssTransition({\n *   enter: \"animate__animated animate__bounceIn\",\n *   exit: \"animate__animated animate__bounceOut\"\n * })\n * ```\n *\n */\nexport function cssTransition({\n  enter,\n  exit,\n  appendPosition = false,\n  collapse = true,\n  collapseDuration = Default.COLLAPSE_DURATION\n}: CSSTransitionProps) {\n  return function ToastTransition({\n    children,\n    position,\n    preventExitTransition,\n    done,\n    nodeRef,\n    isIn,\n    playToast\n  }: ToastTransitionProps) {\n    const enterClassName = appendPosition ? `${enter}--${position}` : enter;\n    const exitClassName = appendPosition ? `${exit}--${position}` : exit;\n    const animationStep = useRef(AnimationStep.Enter);\n\n    useLayoutEffect(() => {\n      const node = nodeRef.current!;\n      const classToToken = enterClassName.split(' ');\n\n      const onEntered = (e: AnimationEvent) => {\n        if (e.target !== nodeRef.current) return;\n\n        playToast();\n        node.removeEventListener('animationend', onEntered);\n        node.removeEventListener('animationcancel', onEntered);\n        if (animationStep.current === AnimationStep.Enter && e.type !== 'animationcancel') {\n          node.classList.remove(...classToToken);\n        }\n      };\n\n      const onEnter = () => {\n        node.classList.add(...classToToken);\n        node.addEventListener('animationend', onEntered);\n        node.addEventListener('animationcancel', onEntered);\n      };\n\n      onEnter();\n    }, []);\n\n    useEffect(() => {\n      const node = nodeRef.current!;\n\n      const onExited = () => {\n        node.removeEventListener('animationend', onExited);\n        collapse ? collapseToast(node, done, collapseDuration) : done();\n      };\n\n      const onExit = () => {\n        animationStep.current = AnimationStep.Exit;\n        node.className += ` ${exitClassName}`;\n        node.addEventListener('animationend', onExited);\n      };\n\n      if (!isIn) preventExitTransition ? onExited() : onExit();\n    }, [isIn]);\n\n    return <>{children}</>;\n  };\n}\n"
  },
  {
    "path": "src/utils/index.ts",
    "content": "export * from './propValidator';\nexport * from './constant';\nexport * from './cssTransition';\nexport * from './collapseToast';\nexport * from './mapper';\n"
  },
  {
    "path": "src/utils/mapper.ts",
    "content": "import { Toast, ToastContentProps, ToastItem, ToastItemStatus, ToastProps } from '../types';\nimport { cloneElement, isValidElement, ReactElement } from 'react';\nimport { isFn, isStr } from './propValidator';\n\nexport function toToastItem(toast: Toast, status: ToastItemStatus): ToastItem {\n  return {\n    content: renderContent(toast.content, toast.props),\n    containerId: toast.props.containerId,\n    id: toast.props.toastId,\n    theme: toast.props.theme,\n    type: toast.props.type,\n    data: toast.props.data || {},\n    isLoading: toast.props.isLoading,\n    icon: toast.props.icon,\n    reason: toast.removalReason,\n    status\n  };\n}\n\nexport function renderContent(content: unknown, props: ToastProps, isPaused: boolean = false) {\n  if (isValidElement(content) && !isStr(content.type)) {\n    return cloneElement<ToastContentProps>(content as ReactElement<any>, {\n      closeToast: props.closeToast,\n      toastProps: props,\n      data: props.data,\n      isPaused\n    });\n  } else if (isFn(content)) {\n    return content({\n      closeToast: props.closeToast,\n      toastProps: props,\n      data: props.data,\n      isPaused\n    });\n  }\n\n  return content;\n}\n"
  },
  {
    "path": "src/utils/propValidator.ts",
    "content": "import { isValidElement } from 'react';\nimport { Id } from '../types';\n\nexport const isNum = (v: any): v is Number => typeof v === 'number' && !isNaN(v);\n\nexport const isStr = (v: any): v is String => typeof v === 'string';\n\nexport const isFn = (v: any): v is Function => typeof v === 'function';\n\nexport const isId = (v: unknown): v is Id => isStr(v) || isNum(v);\n\nexport const parseClassName = (v: any) => (isStr(v) || isFn(v) ? v : null);\n\nexport const getAutoCloseDelay = (toastAutoClose?: false | number, containerAutoClose?: false | number) =>\n  toastAutoClose === false || (isNum(toastAutoClose) && toastAutoClose > 0) ? toastAutoClose : containerAutoClose;\n\nexport const canBeRendered = <T>(content: T): boolean =>\n  isValidElement(content) || isStr(content) || isFn(content) || isNum(content);\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"jsx\": \"react\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"lib\": [\"es2015\", \"dom\"]\n  }\n}"
  },
  {
    "path": "tsup.config.ts",
    "content": "import { defineConfig, Options } from 'tsup';\n\nconst injectFunc = `\nfunction injectStyle(css) {\n  if (!css || typeof document === 'undefined') return\n\n  const head = document.head || document.getElementsByTagName('head')[0]\n  const style = document.createElement('style')\n  style.type = 'text/css'\n          \n  if(head.firstChild) {\n    head.insertBefore(style, head.firstChild)\n  } else {\n    head.appendChild(style)\n  }\n\n  if(style.styleSheet) {\n    style.styleSheet.cssText = css\n  } else {\n    style.appendChild(document.createTextNode(css))\n  }\n}\n`;\n\nconst baseConfig: Options = {\n  minify: true,\n  target: 'es2018',\n  sourcemap: true,\n  dts: true,\n  format: ['esm', 'cjs'],\n  injectStyle: css => {\n    return `${injectFunc}injectStyle(${css});`;\n  },\n  banner: {\n    js: '\"use client\";'\n  }\n};\n\nexport default defineConfig([\n  {\n    ...baseConfig,\n    entry: ['src/index.ts'],\n    external: ['react'],\n    clean: ['dist']\n  },\n  {\n    ...baseConfig,\n    injectStyle: false,\n    entry: { unstyled: 'src/index.ts' },\n    external: ['react'],\n    clean: ['dist']\n  },\n  {\n    ...baseConfig,\n    entry: {\n      'use-notification-center/index': 'src/addons/use-notification-center/index.ts'\n    },\n    external: ['react', 'react-toastify'],\n    clean: ['addons'],\n    outDir: 'addons'\n  }\n]);\n"
  },
  {
    "path": "vite.config.mts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport istanbul from 'vite-plugin-istanbul';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [\n    react(),\n    istanbul({\n      cypress: true,\n      requireEnv: false\n    })\n  ]\n});\n"
  }
]