[
  {
    "path": ".github/workflows/playwright.yml",
    "content": "name: Playwright Tests\non:\n  push:\n    branches: [main, master]\n  pull_request:\n    branches: [main, master]\njobs:\n  test:\n    timeout-minutes: 60\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 20\n      - run: npm install pnpm@9.15.9 -g\n      - run: pnpm install --no-frozen-lockfile\n      - run: pnpm build\n      - run: npx playwright install --with-deps\n      - run: pnpm test || exit 1\n      - uses: actions/upload-artifact@v4\n        if: always()\n        with:\n          name: playwright-report\n          path: playwright-report/\n          retention-days: 30\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\ndist\n\n\n# dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# testing\ncoverage\n\n# next.js\n.next/\nout/\nbuild\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# local env files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# turbo\n.turbo\n/test-results/\n/playwright-report/\n/playwright/.cache/\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  semi: true,\n  singleQuote: true,\n  tabWidth: 2,\n  trailingComma: 'all',\n  printWidth: 120,\n};\n"
  },
  {
    "path": "FUNDING.yml",
    "content": "github: emilkowalski\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2023 Emil Kowalski\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": "https://github.com/vallezw/sonner/assets/50796600/59b95cb7-9068-4f3e-8469-0b35d9de5cf0\n\n[Sonner](https://sonner.emilkowal.ski/) is an opinionated toast component for React. You can read more about why and how it was built [here](https://emilkowal.ski/ui/building-a-toast-component).\n\n## Usage\n\nTo start using the library, install it in your project:\n\n```bash\nnpm install sonner\n```\n\nAdd `<Toaster />` to your app, it will be the place where all your toasts will be rendered.\nAfter that you can use `toast()` from anywhere in your app.\n\n```jsx\nimport { Toaster, toast } from 'sonner';\n\n// ...\n\nfunction App() {\n  return (\n    <div>\n      <Toaster />\n      <button onClick={() => toast('My first toast')}>Give me a toast</button>\n    </div>\n  );\n}\n```\n\n## Documentation\n\nFind the full API reference in the [documentation](https://sonner.emilkowal.ski/getting-started).\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"sonner\",\n  \"version\": \"2.0.7\",\n  \"description\": \"An opinionated toast component for React.\",\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist/index.d.mts\",\n        \"default\": \"./dist/index.mjs\"\n      },\n      \"require\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.js\"\n      },\n      \"default\": \"./dist/index.js\"\n    },\n    \"./dist/styles.css\": \"./dist/styles.css\"\n  },\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"dev\": \"bunchee --watch\",\n    \"build\": \"bunchee && cp src/styles.css dist/styles.css\",\n    \"type-check\": \"tsc --noEmit\",\n    \"dev:website\": \"turbo run dev --filter=website...\",\n    \"dev:test\": \"turbo run dev --filter=test...\",\n    \"format\": \"prettier --write .\",\n    \"test\": \"playwright test\"\n  },\n  \"keywords\": [\n    \"react\",\n    \"notifications\",\n    \"toast\",\n    \"snackbar\",\n    \"message\"\n  ],\n  \"author\": \"Emil Kowalski <e@emilkowal.ski>\",\n  \"license\": \"MIT\",\n  \"homepage\": \"https://sonner.emilkowal.ski/\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/emilkowalski/sonner.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/emilkowalski/sonner/issues\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"^1.49.1\",\n    \"@types/node\": \"^18.11.13\",\n    \"@types/react\": \"^18.0.26\",\n    \"bunchee\": \"6.3.3\",\n    \"prettier\": \"^2.8.4\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"turbo\": \"1.6\",\n    \"typescript\": \"^4.8.4\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^18.0.0 || ^19.0.0 || ^19.0.0-rc\",\n    \"react-dom\": \"^18.0.0 || ^19.0.0 || ^19.0.0-rc\"\n  },\n  \"packageManager\": \"pnpm@9.15.9\"\n}\n"
  },
  {
    "path": "playwright.config.ts",
    "content": "import { defineConfig, devices } from '@playwright/test';\n\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\n// require('dotenv').config();\n\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport default defineConfig({\n  testDir: './test',\n  /* Maximum time one test can run for. */\n  timeout: 30 * 1000,\n  expect: {\n    /**\n     * Maximum time expect() should wait for the condition to be met.\n     * For example in `await expect(locator).toHaveText();`\n     */\n    timeout: 5000,\n  },\n  /* Run tests in files in parallel */\n  fullyParallel: true,\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: 'html',\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    trace: 'on-first-retry',\n    baseURL: 'http://localhost:3000',\n  },\n  webServer: {\n    command: 'npm run dev',\n    url: 'http://localhost:3000',\n    cwd: './test',\n    reuseExistingServer: !process.env.CI,\n  },\n  /* Configure projects for major browsers */\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] },\n    },\n\n    // {\n    //   name: 'firefox',\n    //   use: { ...devices['Desktop Firefox'] },\n    // },\n\n    {\n      name: 'webkit',\n      use: { ...devices['Desktop Safari'] },\n    },\n\n    /* Test against mobile viewports. */\n    // {\n    //   name: 'Mobile Chrome',\n    //   use: { ...devices['Pixel 5'] },\n    // },\n    // {\n    //   name: 'Mobile Safari',\n    //   use: { ...devices['iPhone 12'] },\n    // },\n\n    /* Test against branded browsers. */\n    // {\n    //   name: 'Microsoft Edge',\n    //   use: { channel: 'msedge' },\n    // },\n    // {\n    //   name: 'Google Chrome',\n    //   use: { channel: 'chrome' },\n    // },\n  ],\n});\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - 'website'\n  - '.'\n  - 'test'\n"
  },
  {
    "path": "src/assets.tsx",
    "content": "'use client';\nimport React from 'react';\nimport type { ToastTypes } from './types';\n\nexport const getAsset = (type: ToastTypes): JSX.Element | null => {\n  switch (type) {\n    case 'success':\n      return SuccessIcon;\n\n    case 'info':\n      return InfoIcon;\n\n    case 'warning':\n      return WarningIcon;\n\n    case 'error':\n      return ErrorIcon;\n\n    default:\n      return null;\n  }\n};\n\nconst bars = Array(12).fill(0);\n\nexport const Loader = ({ visible, className }: { visible: boolean; className?: string }) => {\n  return (\n    <div className={['sonner-loading-wrapper', className].filter(Boolean).join(' ')} data-visible={visible}>\n      <div className=\"sonner-spinner\">\n        {bars.map((_, i) => (\n          <div className=\"sonner-loading-bar\" key={`spinner-bar-${i}`} />\n        ))}\n      </div>\n    </div>\n  );\n};\n\nconst SuccessIcon = (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" height=\"20\" width=\"20\">\n    <path\n      fillRule=\"evenodd\"\n      d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z\"\n      clipRule=\"evenodd\"\n    />\n  </svg>\n);\n\nconst WarningIcon = (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" height=\"20\" width=\"20\">\n    <path\n      fillRule=\"evenodd\"\n      d=\"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n      clipRule=\"evenodd\"\n    />\n  </svg>\n);\n\nconst InfoIcon = (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" height=\"20\" width=\"20\">\n    <path\n      fillRule=\"evenodd\"\n      d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z\"\n      clipRule=\"evenodd\"\n    />\n  </svg>\n);\n\nconst ErrorIcon = (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" height=\"20\" width=\"20\">\n    <path\n      fillRule=\"evenodd\"\n      d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z\"\n      clipRule=\"evenodd\"\n    />\n  </svg>\n);\n\nexport const CloseIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"12\"\n    height=\"12\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"1.5\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n  >\n    <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n    <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n  </svg>\n);\n"
  },
  {
    "path": "src/hooks.tsx",
    "content": "import React from 'react';\n\nexport const useIsDocumentHidden = () => {\n  const [isDocumentHidden, setIsDocumentHidden] = React.useState(document.hidden);\n\n  React.useEffect(() => {\n    const callback = () => {\n      setIsDocumentHidden(document.hidden);\n    };\n    document.addEventListener('visibilitychange', callback);\n    return () => document.removeEventListener('visibilitychange', callback);\n  }, []);\n\n  return isDocumentHidden;\n};\n"
  },
  {
    "path": "src/index.tsx",
    "content": "'use client';\n\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport { CloseIcon, getAsset, Loader } from './assets';\nimport { useIsDocumentHidden } from './hooks';\nimport { toast, ToastState } from './state';\nimport './styles.css';\nimport {\n  isAction,\n  SwipeDirection,\n  type ExternalToast,\n  type HeightT,\n  type ToasterProps,\n  type ToastProps,\n  type ToastT,\n  type ToastToDismiss,\n} from './types';\n\n// Visible toasts amount\nconst VISIBLE_TOASTS_AMOUNT = 3;\n\n// Viewport padding\nconst VIEWPORT_OFFSET = '24px';\n\n// Mobile viewport padding\nconst MOBILE_VIEWPORT_OFFSET = '16px';\n\n// Default lifetime of a toasts (in ms)\nconst TOAST_LIFETIME = 4000;\n\n// Default toast width\nconst TOAST_WIDTH = 356;\n\n// Default gap between toasts\nconst GAP = 14;\n\n// Threshold to dismiss a toast\nconst SWIPE_THRESHOLD = 45;\n\n// Equal to exit animation duration\nconst TIME_BEFORE_UNMOUNT = 200;\n\nfunction cn(...classes: (string | undefined)[]) {\n  return classes.filter(Boolean).join(' ');\n}\n\nfunction getDefaultSwipeDirections(position: string): Array<SwipeDirection> {\n  const [y, x] = position.split('-');\n  const directions: Array<SwipeDirection> = [];\n\n  if (y) {\n    directions.push(y as SwipeDirection);\n  }\n\n  if (x) {\n    directions.push(x as SwipeDirection);\n  }\n\n  return directions;\n}\n\nconst Toast = (props: ToastProps) => {\n  const {\n    invert: ToasterInvert,\n    toast,\n    unstyled,\n    interacting,\n    setHeights,\n    visibleToasts,\n    heights,\n    index,\n    toasts,\n    expanded,\n    removeToast,\n    defaultRichColors,\n    closeButton: closeButtonFromToaster,\n    style,\n    cancelButtonStyle,\n    actionButtonStyle,\n    className = '',\n    descriptionClassName = '',\n    duration: durationFromToaster,\n    position,\n    gap,\n    expandByDefault,\n    classNames,\n    icons,\n    closeButtonAriaLabel = 'Close toast',\n  } = props;\n  const [swipeDirection, setSwipeDirection] = React.useState<'x' | 'y' | null>(null);\n  const [swipeOutDirection, setSwipeOutDirection] = React.useState<'left' | 'right' | 'up' | 'down' | null>(null);\n  const [mounted, setMounted] = React.useState(false);\n  const [removed, setRemoved] = React.useState(false);\n  const [swiping, setSwiping] = React.useState(false);\n  const [swipeOut, setSwipeOut] = React.useState(false);\n  const [isSwiped, setIsSwiped] = React.useState(false);\n  const [offsetBeforeRemove, setOffsetBeforeRemove] = React.useState(0);\n  const [initialHeight, setInitialHeight] = React.useState(0);\n  const remainingTime = React.useRef(toast.duration || durationFromToaster || TOAST_LIFETIME);\n  const dragStartTime = React.useRef<Date | null>(null);\n  const toastRef = React.useRef<HTMLLIElement>(null);\n  const isFront = index === 0;\n  const isVisible = index + 1 <= visibleToasts;\n  const toastType = toast.type;\n  const dismissible = toast.dismissible !== false;\n  const toastClassname = toast.className || '';\n  const toastDescriptionClassname = toast.descriptionClassName || '';\n  // Height index is used to calculate the offset as it gets updated before the toast array, which means we can calculate the new layout faster.\n  const heightIndex = React.useMemo(\n    () => heights.findIndex((height) => height.toastId === toast.id) || 0,\n    [heights, toast.id],\n  );\n  const closeButton = React.useMemo(\n    () => toast.closeButton ?? closeButtonFromToaster,\n    [toast.closeButton, closeButtonFromToaster],\n  );\n  const duration = React.useMemo(\n    () => toast.duration || durationFromToaster || TOAST_LIFETIME,\n    [toast.duration, durationFromToaster],\n  );\n  const closeTimerStartTimeRef = React.useRef(0);\n  const offset = React.useRef(0);\n  const lastCloseTimerStartTimeRef = React.useRef(0);\n  const pointerStartRef = React.useRef<{ x: number; y: number } | null>(null);\n  const [y, x] = position.split('-');\n  const toastsHeightBefore = React.useMemo(() => {\n    return heights.reduce((prev, curr, reducerIndex) => {\n      // Calculate offset up until current toast\n      if (reducerIndex >= heightIndex) {\n        return prev;\n      }\n\n      return prev + curr.height;\n    }, 0);\n  }, [heights, heightIndex]);\n  const isDocumentHidden = useIsDocumentHidden();\n\n  const invert = toast.invert || ToasterInvert;\n  const disabled = toastType === 'loading';\n\n  offset.current = React.useMemo(() => heightIndex * gap + toastsHeightBefore, [heightIndex, toastsHeightBefore]);\n\n  React.useEffect(() => {\n    remainingTime.current = duration;\n  }, [duration]);\n\n  React.useEffect(() => {\n    // Trigger enter animation without using CSS animation\n    setMounted(true);\n  }, []);\n\n  React.useEffect(() => {\n    const toastNode = toastRef.current;\n    if (toastNode) {\n      const height = toastNode.getBoundingClientRect().height;\n      // Add toast height to heights array after the toast is mounted\n      setInitialHeight(height);\n      setHeights((h) => [{ toastId: toast.id, height, position: toast.position }, ...h]);\n      return () => setHeights((h) => h.filter((height) => height.toastId !== toast.id));\n    }\n  }, [setHeights, toast.id]);\n\n  React.useLayoutEffect(() => {\n    // Keep height up to date with the content in case it updates\n    if (!mounted) return;\n    const toastNode = toastRef.current;\n    const originalHeight = toastNode.style.height;\n    toastNode.style.height = 'auto';\n    const newHeight = toastNode.getBoundingClientRect().height;\n    toastNode.style.height = originalHeight;\n\n    setInitialHeight(newHeight);\n\n    setHeights((heights) => {\n      const alreadyExists = heights.find((height) => height.toastId === toast.id);\n      if (!alreadyExists) {\n        return [{ toastId: toast.id, height: newHeight, position: toast.position }, ...heights];\n      } else {\n        return heights.map((height) => (height.toastId === toast.id ? { ...height, height: newHeight } : height));\n      }\n    });\n  }, [mounted, toast.title, toast.description, setHeights, toast.id, toast.jsx, toast.action, toast.cancel]);\n\n  const deleteToast = React.useCallback(() => {\n    // Save the offset for the exit swipe animation\n    setRemoved(true);\n    setOffsetBeforeRemove(offset.current);\n    setHeights((h) => h.filter((height) => height.toastId !== toast.id));\n\n    setTimeout(() => {\n      removeToast(toast);\n    }, TIME_BEFORE_UNMOUNT);\n  }, [toast, removeToast, setHeights, offset]);\n\n  React.useEffect(() => {\n    if ((toast.promise && toastType === 'loading') || toast.duration === Infinity || toast.type === 'loading') return;\n    let timeoutId: NodeJS.Timeout;\n\n    // Pause the timer on each hover\n    const pauseTimer = () => {\n      if (lastCloseTimerStartTimeRef.current < closeTimerStartTimeRef.current) {\n        // Get the elapsed time since the timer started\n        const elapsedTime = new Date().getTime() - closeTimerStartTimeRef.current;\n\n        remainingTime.current = remainingTime.current - elapsedTime;\n      }\n\n      lastCloseTimerStartTimeRef.current = new Date().getTime();\n    };\n\n    const startTimer = () => {\n      // setTimeout(, Infinity) behaves as if the delay is 0.\n      // As a result, the toast would be closed immediately, giving the appearance that it was never rendered.\n      // See: https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#an-infinite-timeout\n      if (remainingTime.current === Infinity) return;\n\n      closeTimerStartTimeRef.current = new Date().getTime();\n\n      // Let the toast know it has started\n      timeoutId = setTimeout(() => {\n        toast.onAutoClose?.(toast);\n        deleteToast();\n      }, remainingTime.current);\n    };\n\n    if (expanded || interacting || isDocumentHidden) {\n      pauseTimer();\n    } else {\n      startTimer();\n    }\n\n    return () => clearTimeout(timeoutId);\n  }, [expanded, interacting, toast, toastType, isDocumentHidden, deleteToast]);\n\n  React.useEffect(() => {\n    if (toast.delete) {\n      deleteToast();\n      toast.onDismiss?.(toast);\n    }\n  }, [deleteToast, toast.delete]);\n\n  function getLoadingIcon() {\n    if (icons?.loading) {\n      return (\n        <div\n          className={cn(classNames?.loader, toast?.classNames?.loader, 'sonner-loader')}\n          data-visible={toastType === 'loading'}\n        >\n          {icons.loading}\n        </div>\n      );\n    }\n\n    return <Loader className={cn(classNames?.loader, toast?.classNames?.loader)} visible={toastType === 'loading'} />;\n  }\n\n  const icon = toast.icon || icons?.[toastType] || getAsset(toastType);\n\n  return (\n    <li\n      tabIndex={0}\n      ref={toastRef}\n      className={cn(\n        className,\n        toastClassname,\n        classNames?.toast,\n        toast?.classNames?.toast,\n        classNames?.default,\n        classNames?.[toastType],\n        toast?.classNames?.[toastType],\n      )}\n      data-sonner-toast=\"\"\n      data-rich-colors={toast.richColors ?? defaultRichColors}\n      data-styled={!Boolean(toast.jsx || toast.unstyled || unstyled)}\n      data-mounted={mounted}\n      data-promise={Boolean(toast.promise)}\n      data-swiped={isSwiped}\n      data-removed={removed}\n      data-visible={isVisible}\n      data-y-position={y}\n      data-x-position={x}\n      data-index={index}\n      data-front={isFront}\n      data-swiping={swiping}\n      data-dismissible={dismissible}\n      data-type={toastType}\n      data-invert={invert}\n      data-swipe-out={swipeOut}\n      data-swipe-direction={swipeOutDirection}\n      data-expanded={Boolean(expanded || (expandByDefault && mounted))}\n      data-testid={toast.testId}\n      style={\n        {\n          '--index': index,\n          '--toasts-before': index,\n          '--z-index': toasts.length - index,\n          '--offset': `${removed ? offsetBeforeRemove : offset.current}px`,\n          '--initial-height': expandByDefault ? 'auto' : `${initialHeight}px`,\n          ...style,\n          ...toast.style,\n        } as React.CSSProperties\n      }\n      onDragEnd={() => {\n        setSwiping(false);\n        setSwipeDirection(null);\n        pointerStartRef.current = null;\n      }}\n      onPointerDown={(event) => {\n        if (event.button === 2) return; // Return early on right click\n        if (disabled || !dismissible) return;\n        dragStartTime.current = new Date();\n        setOffsetBeforeRemove(offset.current);\n        // Ensure we maintain correct pointer capture even when going outside of the toast (e.g. when swiping)\n        (event.target as HTMLElement).setPointerCapture(event.pointerId);\n        if ((event.target as HTMLElement).tagName === 'BUTTON') return;\n        setSwiping(true);\n        pointerStartRef.current = { x: event.clientX, y: event.clientY };\n      }}\n      onPointerUp={() => {\n        if (swipeOut || !dismissible) return;\n\n        pointerStartRef.current = null;\n        const swipeAmountX = Number(\n          toastRef.current?.style.getPropertyValue('--swipe-amount-x').replace('px', '') || 0,\n        );\n        const swipeAmountY = Number(\n          toastRef.current?.style.getPropertyValue('--swipe-amount-y').replace('px', '') || 0,\n        );\n        const timeTaken = new Date().getTime() - dragStartTime.current?.getTime();\n\n        const swipeAmount = swipeDirection === 'x' ? swipeAmountX : swipeAmountY;\n        const velocity = Math.abs(swipeAmount) / timeTaken;\n\n        if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {\n          setOffsetBeforeRemove(offset.current);\n\n          toast.onDismiss?.(toast);\n\n          if (swipeDirection === 'x') {\n            setSwipeOutDirection(swipeAmountX > 0 ? 'right' : 'left');\n          } else {\n            setSwipeOutDirection(swipeAmountY > 0 ? 'down' : 'up');\n          }\n\n          deleteToast();\n          setSwipeOut(true);\n\n          return;\n        } else {\n          toastRef.current?.style.setProperty('--swipe-amount-x', `0px`);\n          toastRef.current?.style.setProperty('--swipe-amount-y', `0px`);\n        }\n        setIsSwiped(false);\n        setSwiping(false);\n        setSwipeDirection(null);\n      }}\n      onPointerMove={(event) => {\n        if (!pointerStartRef.current || !dismissible) return;\n\n        const isHighlighted = window.getSelection()?.toString().length > 0;\n        if (isHighlighted) return;\n\n        const yDelta = event.clientY - pointerStartRef.current.y;\n        const xDelta = event.clientX - pointerStartRef.current.x;\n\n        const swipeDirections = props.swipeDirections ?? getDefaultSwipeDirections(position);\n\n        // Determine swipe direction if not already locked\n        if (!swipeDirection && (Math.abs(xDelta) > 1 || Math.abs(yDelta) > 1)) {\n          setSwipeDirection(Math.abs(xDelta) > Math.abs(yDelta) ? 'x' : 'y');\n        }\n\n        let swipeAmount = { x: 0, y: 0 };\n\n        const getDampening = (delta: number) => {\n          const factor = Math.abs(delta) / 20;\n\n          return 1 / (1.5 + factor);\n        };\n\n        // Only apply swipe in the locked direction\n        if (swipeDirection === 'y') {\n          // Handle vertical swipes\n          if (swipeDirections.includes('top') || swipeDirections.includes('bottom')) {\n            if ((swipeDirections.includes('top') && yDelta < 0) || (swipeDirections.includes('bottom') && yDelta > 0)) {\n              swipeAmount.y = yDelta;\n            } else {\n              // Smoothly transition to dampened movement\n              const dampenedDelta = yDelta * getDampening(yDelta);\n              // Ensure we don't jump when transitioning to dampened movement\n              swipeAmount.y = Math.abs(dampenedDelta) < Math.abs(yDelta) ? dampenedDelta : yDelta;\n            }\n          }\n        } else if (swipeDirection === 'x') {\n          // Handle horizontal swipes\n          if (swipeDirections.includes('left') || swipeDirections.includes('right')) {\n            if ((swipeDirections.includes('left') && xDelta < 0) || (swipeDirections.includes('right') && xDelta > 0)) {\n              swipeAmount.x = xDelta;\n            } else {\n              // Smoothly transition to dampened movement\n              const dampenedDelta = xDelta * getDampening(xDelta);\n              // Ensure we don't jump when transitioning to dampened movement\n              swipeAmount.x = Math.abs(dampenedDelta) < Math.abs(xDelta) ? dampenedDelta : xDelta;\n            }\n          }\n        }\n\n        if (Math.abs(swipeAmount.x) > 0 || Math.abs(swipeAmount.y) > 0) {\n          setIsSwiped(true);\n        }\n\n        // Apply transform using both x and y values\n        toastRef.current?.style.setProperty('--swipe-amount-x', `${swipeAmount.x}px`);\n        toastRef.current?.style.setProperty('--swipe-amount-y', `${swipeAmount.y}px`);\n      }}\n    >\n      {closeButton && !toast.jsx && toastType !== 'loading' ? (\n        <button\n          aria-label={closeButtonAriaLabel}\n          data-disabled={disabled}\n          data-close-button\n          onClick={\n            disabled || !dismissible\n              ? () => {}\n              : () => {\n                  deleteToast();\n                  toast.onDismiss?.(toast);\n                }\n          }\n          className={cn(classNames?.closeButton, toast?.classNames?.closeButton)}\n        >\n          {icons?.close ?? CloseIcon}\n        </button>\n      ) : null}\n      {/* TODO: This can be cleaner */}\n      {(toastType || toast.icon || toast.promise) &&\n      toast.icon !== null &&\n      (icons?.[toastType] !== null || toast.icon) ? (\n        <div data-icon=\"\" className={cn(classNames?.icon, toast?.classNames?.icon)}>\n          {toast.promise || (toast.type === 'loading' && !toast.icon) ? toast.icon || getLoadingIcon() : null}\n          {toast.type !== 'loading' ? icon : null}\n        </div>\n      ) : null}\n\n      <div data-content=\"\" className={cn(classNames?.content, toast?.classNames?.content)}>\n        <div data-title=\"\" className={cn(classNames?.title, toast?.classNames?.title)}>\n          {toast.jsx ? toast.jsx : typeof toast.title === 'function' ? toast.title() : toast.title}\n        </div>\n        {toast.description ? (\n          <div\n            data-description=\"\"\n            className={cn(\n              descriptionClassName,\n              toastDescriptionClassname,\n              classNames?.description,\n              toast?.classNames?.description,\n            )}\n          >\n            {typeof toast.description === 'function' ? toast.description() : toast.description}\n          </div>\n        ) : null}\n      </div>\n      {React.isValidElement(toast.cancel) ? (\n        toast.cancel\n      ) : toast.cancel && isAction(toast.cancel) ? (\n        <button\n          data-button\n          data-cancel\n          style={toast.cancelButtonStyle || cancelButtonStyle}\n          onClick={(event) => {\n            // We need to check twice because typescript\n            if (!isAction(toast.cancel)) return;\n            if (!dismissible) return;\n            toast.cancel.onClick?.(event);\n            deleteToast();\n          }}\n          className={cn(classNames?.cancelButton, toast?.classNames?.cancelButton)}\n        >\n          {toast.cancel.label}\n        </button>\n      ) : null}\n      {React.isValidElement(toast.action) ? (\n        toast.action\n      ) : toast.action && isAction(toast.action) ? (\n        <button\n          data-button\n          data-action\n          style={toast.actionButtonStyle || actionButtonStyle}\n          onClick={(event) => {\n            // We need to check twice because typescript\n            if (!isAction(toast.action)) return;\n            toast.action.onClick?.(event);\n            if (event.defaultPrevented) return;\n            deleteToast();\n          }}\n          className={cn(classNames?.actionButton, toast?.classNames?.actionButton)}\n        >\n          {toast.action.label}\n        </button>\n      ) : null}\n    </li>\n  );\n};\n\nfunction getDocumentDirection(): ToasterProps['dir'] {\n  if (typeof window === 'undefined') return 'ltr';\n  if (typeof document === 'undefined') return 'ltr'; // For Fresh purpose\n\n  const dirAttribute = document.documentElement.getAttribute('dir');\n\n  if (dirAttribute === 'auto' || !dirAttribute) {\n    return window.getComputedStyle(document.documentElement).direction as ToasterProps['dir'];\n  }\n\n  return dirAttribute as ToasterProps['dir'];\n}\n\nfunction assignOffset(defaultOffset: ToasterProps['offset'], mobileOffset: ToasterProps['mobileOffset']) {\n  const styles = {} as React.CSSProperties;\n\n  [defaultOffset, mobileOffset].forEach((offset, index) => {\n    const isMobile = index === 1;\n    const prefix = isMobile ? '--mobile-offset' : '--offset';\n    const defaultValue = isMobile ? MOBILE_VIEWPORT_OFFSET : VIEWPORT_OFFSET;\n\n    function assignAll(offset: string | number) {\n      ['top', 'right', 'bottom', 'left'].forEach((key) => {\n        styles[`${prefix}-${key}`] = typeof offset === 'number' ? `${offset}px` : offset;\n      });\n    }\n\n    if (typeof offset === 'number' || typeof offset === 'string') {\n      assignAll(offset);\n    } else if (typeof offset === 'object') {\n      ['top', 'right', 'bottom', 'left'].forEach((key) => {\n        if (offset[key] === undefined) {\n          styles[`${prefix}-${key}`] = defaultValue;\n        } else {\n          styles[`${prefix}-${key}`] = typeof offset[key] === 'number' ? `${offset[key]}px` : offset[key];\n        }\n      });\n    } else {\n      assignAll(defaultValue);\n    }\n  });\n\n  return styles;\n}\n\nfunction useSonner() {\n  const [activeToasts, setActiveToasts] = React.useState<ToastT[]>([]);\n\n  React.useEffect(() => {\n    return ToastState.subscribe((toast) => {\n      if ((toast as ToastToDismiss).dismiss) {\n        setTimeout(() => {\n          ReactDOM.flushSync(() => {\n            setActiveToasts((toasts) => toasts.filter((t) => t.id !== toast.id));\n          });\n        });\n        return;\n      }\n\n      // Prevent batching, temp solution.\n      setTimeout(() => {\n        ReactDOM.flushSync(() => {\n          setActiveToasts((toasts) => {\n            const indexOfExistingToast = toasts.findIndex((t) => t.id === toast.id);\n\n            // Update the toast if it already exists\n            if (indexOfExistingToast !== -1) {\n              return [\n                ...toasts.slice(0, indexOfExistingToast),\n                { ...toasts[indexOfExistingToast], ...toast },\n                ...toasts.slice(indexOfExistingToast + 1),\n              ];\n            }\n\n            return [toast, ...toasts];\n          });\n        });\n      });\n    });\n  }, []);\n\n  return {\n    toasts: activeToasts,\n  };\n}\n\nconst Toaster = React.forwardRef<HTMLElement, ToasterProps>(function Toaster(props, ref) {\n  const {\n    id,\n    invert,\n    position = 'bottom-right',\n    hotkey = ['altKey', 'KeyT'],\n    expand,\n    closeButton,\n    className,\n    offset,\n    mobileOffset,\n    theme = 'light',\n    richColors,\n    duration,\n    style,\n    visibleToasts = VISIBLE_TOASTS_AMOUNT,\n    toastOptions,\n    dir = getDocumentDirection(),\n    gap = GAP,\n    icons,\n    customAriaLabel,\n    containerAriaLabel = 'Notifications',\n  } = props;\n  const [toasts, setToasts] = React.useState<ToastT[]>([]);\n  const filteredToasts = React.useMemo(() => {\n    if (id) {\n      return toasts.filter((toast) => toast.toasterId === id);\n    }\n    return toasts.filter((toast) => !toast.toasterId);\n  }, [toasts, id]);\n  const possiblePositions = React.useMemo(() => {\n    return Array.from(\n      new Set([position].concat(filteredToasts.filter((toast) => toast.position).map((toast) => toast.position))),\n    );\n  }, [filteredToasts, position]);\n  const [heights, setHeights] = React.useState<HeightT[]>([]);\n  const [expanded, setExpanded] = React.useState(false);\n  const [interacting, setInteracting] = React.useState(false);\n  const [actualTheme, setActualTheme] = React.useState(\n    theme !== 'system'\n      ? theme\n      : typeof window !== 'undefined'\n      ? window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches\n        ? 'dark'\n        : 'light'\n      : 'light',\n  );\n\n  const listRef = React.useRef<HTMLOListElement>(null);\n  const hotkeyLabel = hotkey.join('+').replace(/Key/g, '').replace(/Digit/g, '');\n  const lastFocusedElementRef = React.useRef<HTMLElement>(null);\n  const isFocusWithinRef = React.useRef(false);\n\n  const removeToast = React.useCallback((toastToRemove: ToastT) => {\n    setToasts((toasts) => {\n      if (!toasts.find((toast) => toast.id === toastToRemove.id)?.delete) {\n        ToastState.dismiss(toastToRemove.id);\n      }\n\n      return toasts.filter(({ id }) => id !== toastToRemove.id);\n    });\n  }, []);\n\n  React.useEffect(() => {\n    return ToastState.subscribe((toast) => {\n      if ((toast as ToastToDismiss).dismiss) {\n        // Prevent batching of other state updates\n        requestAnimationFrame(() => {\n          setToasts((toasts) => toasts.map((t) => (t.id === toast.id ? { ...t, delete: true } : t)));\n        });\n        return;\n      }\n\n      // Prevent batching, temp solution.\n      setTimeout(() => {\n        ReactDOM.flushSync(() => {\n          setToasts((toasts) => {\n            const indexOfExistingToast = toasts.findIndex((t) => t.id === toast.id);\n\n            // Update the toast if it already exists\n            if (indexOfExistingToast !== -1) {\n              return [\n                ...toasts.slice(0, indexOfExistingToast),\n                { ...toasts[indexOfExistingToast], ...toast },\n                ...toasts.slice(indexOfExistingToast + 1),\n              ];\n            }\n\n            return [toast, ...toasts];\n          });\n        });\n      });\n    });\n  }, [toasts]);\n\n  React.useEffect(() => {\n    if (theme !== 'system') {\n      setActualTheme(theme);\n      return;\n    }\n\n    if (theme === 'system') {\n      // check if current preference is dark\n      if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {\n        // it's currently dark\n        setActualTheme('dark');\n      } else {\n        // it's not dark\n        setActualTheme('light');\n      }\n    }\n\n    if (typeof window === 'undefined') return;\n    const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n\n    try {\n      // Chrome & Firefox\n      darkMediaQuery.addEventListener('change', ({ matches }) => {\n        if (matches) {\n          setActualTheme('dark');\n        } else {\n          setActualTheme('light');\n        }\n      });\n    } catch (error) {\n      // Safari < 14\n      darkMediaQuery.addListener(({ matches }) => {\n        try {\n          if (matches) {\n            setActualTheme('dark');\n          } else {\n            setActualTheme('light');\n          }\n        } catch (e) {\n          console.error(e);\n        }\n      });\n    }\n  }, [theme]);\n\n  React.useEffect(() => {\n    // Ensure expanded is always false when no toasts are present / only one left\n    if (toasts.length <= 1) {\n      setExpanded(false);\n    }\n  }, [toasts]);\n\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      const isHotkeyPressed = hotkey.length > 0 && hotkey.every((key) => (event as any)[key] || event.code === key);\n\n      if (isHotkeyPressed) {\n        setExpanded(true);\n        listRef.current?.focus();\n      }\n\n      if (\n        event.code === 'Escape' &&\n        (document.activeElement === listRef.current || listRef.current?.contains(document.activeElement))\n      ) {\n        setExpanded(false);\n      }\n    };\n    document.addEventListener('keydown', handleKeyDown);\n\n    return () => document.removeEventListener('keydown', handleKeyDown);\n  }, [hotkey]);\n\n  React.useEffect(() => {\n    if (listRef.current) {\n      return () => {\n        if (lastFocusedElementRef.current) {\n          lastFocusedElementRef.current.focus({ preventScroll: true });\n          lastFocusedElementRef.current = null;\n          isFocusWithinRef.current = false;\n        }\n      };\n    }\n  }, [listRef.current]);\n\n  return (\n    // Remove item from normal navigation flow, only available via hotkey\n    <section\n      ref={ref}\n      aria-label={customAriaLabel ?? `${containerAriaLabel} ${hotkeyLabel}`}\n      tabIndex={-1}\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      aria-atomic=\"false\"\n      suppressHydrationWarning\n      data-react-aria-top-layer\n    >\n      {possiblePositions.map((position, index) => {\n        const [y, x] = position.split('-');\n\n        if (!filteredToasts.length) return null;\n\n        return (\n          <ol\n            key={position}\n            dir={dir === 'auto' ? getDocumentDirection() : dir}\n            tabIndex={-1}\n            ref={listRef}\n            className={className}\n            data-sonner-toaster\n            data-sonner-theme={actualTheme}\n            data-y-position={y}\n            data-x-position={x}\n            style={\n              {\n                '--front-toast-height': `${heights[0]?.height || 0}px`,\n                '--width': `${TOAST_WIDTH}px`,\n                '--gap': `${gap}px`,\n                ...style,\n                ...assignOffset(offset, mobileOffset),\n              } as React.CSSProperties\n            }\n            onBlur={(event) => {\n              if (isFocusWithinRef.current && !event.currentTarget.contains(event.relatedTarget)) {\n                isFocusWithinRef.current = false;\n                if (lastFocusedElementRef.current) {\n                  lastFocusedElementRef.current.focus({ preventScroll: true });\n                  lastFocusedElementRef.current = null;\n                }\n              }\n            }}\n            onFocus={(event) => {\n              const isNotDismissible =\n                event.target instanceof HTMLElement && event.target.dataset.dismissible === 'false';\n\n              if (isNotDismissible) return;\n\n              if (!isFocusWithinRef.current) {\n                isFocusWithinRef.current = true;\n                lastFocusedElementRef.current = event.relatedTarget as HTMLElement;\n              }\n            }}\n            onMouseEnter={() => setExpanded(true)}\n            onMouseMove={() => setExpanded(true)}\n            onMouseLeave={() => {\n              // Avoid setting expanded to false when interacting with a toast, e.g. swiping\n              if (!interacting) {\n                setExpanded(false);\n              }\n            }}\n            onDragEnd={() => setExpanded(false)}\n            onPointerDown={(event) => {\n              const isNotDismissible =\n                event.target instanceof HTMLElement && event.target.dataset.dismissible === 'false';\n\n              if (isNotDismissible) return;\n              setInteracting(true);\n            }}\n            onPointerUp={() => setInteracting(false)}\n          >\n            {filteredToasts\n              .filter((toast) => (!toast.position && index === 0) || toast.position === position)\n              .map((toast, index) => (\n                <Toast\n                  key={toast.id}\n                  icons={icons}\n                  index={index}\n                  toast={toast}\n                  defaultRichColors={richColors}\n                  duration={toastOptions?.duration ?? duration}\n                  className={toastOptions?.className}\n                  descriptionClassName={toastOptions?.descriptionClassName}\n                  invert={invert}\n                  visibleToasts={visibleToasts}\n                  closeButton={toastOptions?.closeButton ?? closeButton}\n                  interacting={interacting}\n                  position={position}\n                  style={toastOptions?.style}\n                  unstyled={toastOptions?.unstyled}\n                  classNames={toastOptions?.classNames}\n                  cancelButtonStyle={toastOptions?.cancelButtonStyle}\n                  actionButtonStyle={toastOptions?.actionButtonStyle}\n                  closeButtonAriaLabel={toastOptions?.closeButtonAriaLabel}\n                  removeToast={removeToast}\n                  toasts={filteredToasts.filter((t) => t.position == toast.position)}\n                  heights={heights.filter((h) => h.position == toast.position)}\n                  setHeights={setHeights}\n                  expandByDefault={expand}\n                  gap={gap}\n                  expanded={expanded}\n                  swipeDirections={props.swipeDirections}\n                />\n              ))}\n          </ol>\n        );\n      })}\n    </section>\n  );\n});\n\nexport { toast, Toaster, type ExternalToast, type ToastT, type ToasterProps, useSonner };\nexport { type ToastClassnames, type ToastToDismiss, type Action } from './types';\n"
  },
  {
    "path": "src/state.ts",
    "content": "import type {\n  ExternalToast,\n  PromiseData,\n  PromiseIExtendedResult,\n  PromiseT,\n  ToastT,\n  ToastToDismiss,\n  ToastTypes,\n} from './types';\n\nimport React from 'react';\n\nlet toastsCounter = 1;\n\ntype titleT = (() => React.ReactNode) | React.ReactNode;\n\nclass Observer {\n  subscribers: Array<(toast: ExternalToast | ToastToDismiss) => void>;\n  toasts: Array<ToastT | ToastToDismiss>;\n  dismissedToasts: Set<string | number>;\n\n  constructor() {\n    this.subscribers = [];\n    this.toasts = [];\n    this.dismissedToasts = new Set();\n  }\n\n  // We use arrow functions to maintain the correct `this` reference\n  subscribe = (subscriber: (toast: ToastT | ToastToDismiss) => void) => {\n    this.subscribers.push(subscriber);\n\n    return () => {\n      const index = this.subscribers.indexOf(subscriber);\n      this.subscribers.splice(index, 1);\n    };\n  };\n\n  publish = (data: ToastT) => {\n    this.subscribers.forEach((subscriber) => subscriber(data));\n  };\n\n  addToast = (data: ToastT) => {\n    this.publish(data);\n    this.toasts = [...this.toasts, data];\n  };\n\n  create = (\n    data: ExternalToast & {\n      message?: titleT;\n      type?: ToastTypes;\n      promise?: PromiseT;\n      jsx?: React.ReactElement;\n    },\n  ) => {\n    const { message, ...rest } = data;\n    const id = typeof data?.id === 'number' || data.id?.length > 0 ? data.id : toastsCounter++;\n    const alreadyExists = this.toasts.find((toast) => {\n      return toast.id === id;\n    });\n    const dismissible = data.dismissible === undefined ? true : data.dismissible;\n\n    if (this.dismissedToasts.has(id)) {\n      this.dismissedToasts.delete(id);\n    }\n\n    if (alreadyExists) {\n      this.toasts = this.toasts.map((toast) => {\n        if (toast.id === id) {\n          this.publish({ ...toast, ...data, id, title: message });\n          return {\n            ...toast,\n            ...data,\n            id,\n            dismissible,\n            title: message,\n          };\n        }\n\n        return toast;\n      });\n    } else {\n      this.addToast({ title: message, ...rest, dismissible, id });\n    }\n\n    return id;\n  };\n\n  dismiss = (id?: number | string) => {\n    if (id) {\n      this.dismissedToasts.add(id);\n      requestAnimationFrame(() => this.subscribers.forEach((subscriber) => subscriber({ id, dismiss: true })));\n    } else {\n      this.toasts.forEach((toast) => {\n        this.subscribers.forEach((subscriber) => subscriber({ id: toast.id, dismiss: true }));\n      });\n    }\n\n    return id;\n  };\n\n  message = (message: titleT | React.ReactNode, data?: ExternalToast) => {\n    return this.create({ ...data, message });\n  };\n\n  error = (message: titleT | React.ReactNode, data?: ExternalToast) => {\n    return this.create({ ...data, message, type: 'error' });\n  };\n\n  success = (message: titleT | React.ReactNode, data?: ExternalToast) => {\n    return this.create({ ...data, type: 'success', message });\n  };\n\n  info = (message: titleT | React.ReactNode, data?: ExternalToast) => {\n    return this.create({ ...data, type: 'info', message });\n  };\n\n  warning = (message: titleT | React.ReactNode, data?: ExternalToast) => {\n    return this.create({ ...data, type: 'warning', message });\n  };\n\n  loading = (message: titleT | React.ReactNode, data?: ExternalToast) => {\n    return this.create({ ...data, type: 'loading', message });\n  };\n\n  promise = <ToastData>(promise: PromiseT<ToastData>, data?: PromiseData<ToastData>) => {\n    if (!data) {\n      // Nothing to show\n      return;\n    }\n\n    let id: string | number | undefined = undefined;\n    if (data.loading !== undefined) {\n      id = this.create({\n        ...data,\n        promise,\n        type: 'loading',\n        message: data.loading,\n        description: typeof data.description !== 'function' ? data.description : undefined,\n      });\n    }\n\n    const p = Promise.resolve(promise instanceof Function ? promise() : promise);\n\n    let shouldDismiss = id !== undefined;\n    let result: ['resolve', ToastData] | ['reject', unknown];\n\n    const originalPromise = p\n      .then(async (response) => {\n        result = ['resolve', response];\n        const isReactElementResponse = React.isValidElement(response);\n        if (isReactElementResponse) {\n          shouldDismiss = false;\n          this.create({ id, type: 'default', message: response });\n        } else if (isHttpResponse(response) && !response.ok) {\n          shouldDismiss = false;\n\n          const promiseData =\n            typeof data.error === 'function' ? await data.error(`HTTP error! status: ${response.status}`) : data.error;\n\n          const description =\n            typeof data.description === 'function'\n              ? await data.description(`HTTP error! status: ${response.status}`)\n              : data.description;\n\n          const isExtendedResult = typeof promiseData === 'object' && !React.isValidElement(promiseData);\n\n          const toastSettings: PromiseIExtendedResult = isExtendedResult\n            ? (promiseData as PromiseIExtendedResult)\n            : { message: promiseData };\n\n          this.create({ id, type: 'error', description, ...toastSettings });\n        } else if (response instanceof Error) {\n          shouldDismiss = false;\n\n          const promiseData = typeof data.error === 'function' ? await data.error(response) : data.error;\n\n          const description =\n            typeof data.description === 'function' ? await data.description(response) : data.description;\n\n          const isExtendedResult = typeof promiseData === 'object' && !React.isValidElement(promiseData);\n\n          const toastSettings: PromiseIExtendedResult = isExtendedResult\n            ? (promiseData as PromiseIExtendedResult)\n            : { message: promiseData };\n\n          this.create({ id, type: 'error', description, ...toastSettings });\n        } else if (data.success !== undefined) {\n          shouldDismiss = false;\n          const promiseData = typeof data.success === 'function' ? await data.success(response) : data.success;\n\n          const description =\n            typeof data.description === 'function' ? await data.description(response) : data.description;\n\n          const isExtendedResult = typeof promiseData === 'object' && !React.isValidElement(promiseData);\n\n          const toastSettings: PromiseIExtendedResult = isExtendedResult\n            ? (promiseData as PromiseIExtendedResult)\n            : { message: promiseData };\n\n          this.create({ id, type: 'success', description, ...toastSettings });\n        }\n      })\n      .catch(async (error) => {\n        result = ['reject', error];\n        if (data.error !== undefined) {\n          shouldDismiss = false;\n          const promiseData = typeof data.error === 'function' ? await data.error(error) : data.error;\n\n          const description = typeof data.description === 'function' ? await data.description(error) : data.description;\n\n          const isExtendedResult = typeof promiseData === 'object' && !React.isValidElement(promiseData);\n\n          const toastSettings: PromiseIExtendedResult = isExtendedResult\n            ? (promiseData as PromiseIExtendedResult)\n            : { message: promiseData };\n\n          this.create({ id, type: 'error', description, ...toastSettings });\n        }\n      })\n      .finally(() => {\n        if (shouldDismiss) {\n          // Toast is still in load state (and will be indefinitely — dismiss it)\n          this.dismiss(id);\n          id = undefined;\n        }\n\n        data.finally?.();\n      });\n\n    const unwrap = () =>\n      new Promise<ToastData>((resolve, reject) =>\n        originalPromise.then(() => (result[0] === 'reject' ? reject(result[1]) : resolve(result[1]))).catch(reject),\n      );\n\n    if (typeof id !== 'string' && typeof id !== 'number') {\n      // cannot Object.assign on undefined\n      return { unwrap };\n    } else {\n      return Object.assign(id, { unwrap });\n    }\n  };\n\n  custom = (jsx: (id: number | string) => React.ReactElement, data?: ExternalToast) => {\n    const id = data?.id || toastsCounter++;\n    this.create({ jsx: jsx(id), ...data, id });\n    return id;\n  };\n\n  getActiveToasts = () => {\n    return this.toasts.filter((toast) => !this.dismissedToasts.has(toast.id));\n  };\n}\n\nexport const ToastState = new Observer();\n\n// bind this to the toast function\nconst toastFunction = (message: titleT, data?: ExternalToast) => {\n  const id = data?.id || toastsCounter++;\n\n  ToastState.addToast({\n    title: message,\n    ...data,\n    id,\n  });\n  return id;\n};\n\nconst isHttpResponse = (data: any): data is Response => {\n  return (\n    data &&\n    typeof data === 'object' &&\n    'ok' in data &&\n    typeof data.ok === 'boolean' &&\n    'status' in data &&\n    typeof data.status === 'number'\n  );\n};\n\nconst basicToast = toastFunction;\n\nconst getHistory = () => ToastState.toasts;\nconst getToasts = () => ToastState.getActiveToasts();\n\n// We use `Object.assign` to maintain the correct types as we would lose them otherwise\nexport const toast = Object.assign(\n  basicToast,\n  {\n    success: ToastState.success,\n    info: ToastState.info,\n    warning: ToastState.warning,\n    error: ToastState.error,\n    custom: ToastState.custom,\n    message: ToastState.message,\n    promise: ToastState.promise,\n    dismiss: ToastState.dismiss,\n    loading: ToastState.loading,\n  },\n  { getHistory, getToasts },\n);\n"
  },
  {
    "path": "src/styles.css",
    "content": "html[dir='ltr'],\n[data-sonner-toaster][dir='ltr'] {\n  --toast-icon-margin-start: -3px;\n  --toast-icon-margin-end: 4px;\n  --toast-svg-margin-start: -1px;\n  --toast-svg-margin-end: 0px;\n  --toast-button-margin-start: auto;\n  --toast-button-margin-end: 0;\n  --toast-close-button-start: 0;\n  --toast-close-button-end: unset;\n  --toast-close-button-transform: translate(-35%, -35%);\n}\n\nhtml[dir='rtl'],\n[data-sonner-toaster][dir='rtl'] {\n  --toast-icon-margin-start: 4px;\n  --toast-icon-margin-end: -3px;\n  --toast-svg-margin-start: 0px;\n  --toast-svg-margin-end: -1px;\n  --toast-button-margin-start: 0;\n  --toast-button-margin-end: auto;\n  --toast-close-button-start: unset;\n  --toast-close-button-end: 0;\n  --toast-close-button-transform: translate(35%, -35%);\n}\n\n[data-sonner-toaster] {\n  position: fixed;\n  width: var(--width);\n  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial,\n    Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;\n  --gray1: hsl(0, 0%, 99%);\n  --gray2: hsl(0, 0%, 97.3%);\n  --gray3: hsl(0, 0%, 95.1%);\n  --gray4: hsl(0, 0%, 93%);\n  --gray5: hsl(0, 0%, 90.9%);\n  --gray6: hsl(0, 0%, 88.7%);\n  --gray7: hsl(0, 0%, 85.8%);\n  --gray8: hsl(0, 0%, 78%);\n  --gray9: hsl(0, 0%, 56.1%);\n  --gray10: hsl(0, 0%, 52.3%);\n  --gray11: hsl(0, 0%, 43.5%);\n  --gray12: hsl(0, 0%, 9%);\n  --border-radius: 8px;\n  box-sizing: border-box;\n  padding: 0;\n  margin: 0;\n  list-style: none;\n  outline: none;\n  z-index: 999999999;\n  transition: transform 400ms ease;\n}\n\n@media (hover: none) and (pointer: coarse) {\n  [data-sonner-toaster][data-lifted='true'] {\n    transform: none;\n  }\n}\n\n[data-sonner-toaster][data-x-position='right'] {\n  right: var(--offset-right);\n}\n\n[data-sonner-toaster][data-x-position='left'] {\n  left: var(--offset-left);\n}\n\n[data-sonner-toaster][data-x-position='center'] {\n  left: 50%;\n  transform: translateX(-50%);\n}\n\n[data-sonner-toaster][data-y-position='top'] {\n  top: var(--offset-top);\n}\n\n[data-sonner-toaster][data-y-position='bottom'] {\n  bottom: var(--offset-bottom);\n}\n\n[data-sonner-toast] {\n  --y: translateY(100%);\n  --lift-amount: calc(var(--lift) * var(--gap));\n  z-index: var(--z-index);\n  position: absolute;\n  opacity: 0;\n  transform: var(--y);\n  touch-action: none;\n  transition: transform 400ms, opacity 400ms, height 400ms, box-shadow 200ms;\n  box-sizing: border-box;\n  outline: none;\n  overflow-wrap: anywhere;\n}\n\n[data-sonner-toast][data-styled='true'] {\n  padding: 16px;\n  background: var(--normal-bg);\n  border: 1px solid var(--normal-border);\n  color: var(--normal-text);\n  border-radius: var(--border-radius);\n  box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);\n  width: var(--width);\n  font-size: 13px;\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n[data-sonner-toast]:focus-visible {\n  box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(0, 0, 0, 0.2);\n}\n\n[data-sonner-toast][data-y-position='top'] {\n  top: 0;\n  --y: translateY(-100%);\n  --lift: 1;\n  --lift-amount: calc(1 * var(--gap));\n}\n\n[data-sonner-toast][data-y-position='bottom'] {\n  bottom: 0;\n  --y: translateY(100%);\n  --lift: -1;\n  --lift-amount: calc(var(--lift) * var(--gap));\n}\n\n[data-sonner-toast][data-styled='true'] [data-description] {\n  font-weight: 400;\n  line-height: 1.4;\n  color: #3f3f3f;\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-styled='true'] [data-description] {\n  color: inherit;\n}\n\n[data-sonner-toaster][data-sonner-theme='dark'] [data-description] {\n  color: hsl(0, 0%, 91%);\n}\n\n[data-sonner-toast][data-styled='true'] [data-title] {\n  font-weight: 500;\n  line-height: 1.5;\n  color: inherit;\n}\n\n[data-sonner-toast][data-styled='true'] [data-icon] {\n  display: flex;\n  height: 16px;\n  width: 16px;\n  position: relative;\n  justify-content: flex-start;\n  align-items: center;\n  flex-shrink: 0;\n  margin-left: var(--toast-icon-margin-start);\n  margin-right: var(--toast-icon-margin-end);\n}\n\n[data-sonner-toast][data-promise='true'] [data-icon] > svg {\n  opacity: 0;\n  transform: scale(0.8);\n  transform-origin: center;\n  animation: sonner-fade-in 300ms ease forwards;\n}\n\n[data-sonner-toast][data-styled='true'] [data-icon] > * {\n  flex-shrink: 0;\n}\n\n[data-sonner-toast][data-styled='true'] [data-icon] svg {\n  margin-left: var(--toast-svg-margin-start);\n  margin-right: var(--toast-svg-margin-end);\n}\n\n[data-sonner-toast][data-styled='true'] [data-content] {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n}\n\n[data-sonner-toast][data-styled='true'] [data-button] {\n  border-radius: 4px;\n  padding-left: 8px;\n  padding-right: 8px;\n  height: 24px;\n  font-size: 12px;\n  color: var(--normal-bg);\n  background: var(--normal-text);\n  margin-left: var(--toast-button-margin-start);\n  margin-right: var(--toast-button-margin-end);\n  border: none;\n  font-weight: 500;\n  cursor: pointer;\n  outline: none;\n  display: flex;\n  align-items: center;\n  flex-shrink: 0;\n  transition: opacity 400ms, box-shadow 200ms;\n}\n\n[data-sonner-toast][data-styled='true'] [data-button]:focus-visible {\n  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.4);\n}\n\n[data-sonner-toast][data-styled='true'] [data-button]:first-of-type {\n  margin-left: var(--toast-button-margin-start);\n  margin-right: var(--toast-button-margin-end);\n}\n\n[data-sonner-toast][data-styled='true'] [data-cancel] {\n  color: var(--normal-text);\n  background: rgba(0, 0, 0, 0.08);\n}\n\n[data-sonner-toaster][data-sonner-theme='dark'] [data-sonner-toast][data-styled='true'] [data-cancel] {\n  background: rgba(255, 255, 255, 0.3);\n}\n\n[data-sonner-toast][data-styled='true'] [data-close-button] {\n  position: absolute;\n  left: var(--toast-close-button-start);\n  right: var(--toast-close-button-end);\n  top: 0;\n  height: 20px;\n  width: 20px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  padding: 0;\n  color: var(--normal-text);\n  background: var(--normal-bg);\n  border: 1px solid var(--normal-border);\n  transform: var(--toast-close-button-transform);\n  border-radius: 50%;\n  cursor: pointer;\n  z-index: 1;\n  transition: opacity 100ms, background 200ms, border-color 200ms;\n}\n\n[data-sonner-toast][data-styled='true'] [data-close-button]:focus-visible {\n  box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(0, 0, 0, 0.2);\n}\n\n[data-sonner-toast][data-styled='true'] [data-disabled='true'] {\n  cursor: not-allowed;\n}\n\n[data-sonner-toast][data-styled='true']:hover [data-close-button]:hover {\n  background: var(--gray2);\n  border-color: var(--gray5);\n}\n\n[data-sonner-toast][data-swiping='true']::before {\n  content: '';\n  position: absolute;\n  left: -100%;\n  right: -100%;\n  height: 100%;\n  z-index: -1;\n}\n\n[data-sonner-toast][data-y-position='top'][data-swiping='true']::before {\n  bottom: 50%;\n  transform: scaleY(3) translateY(50%);\n}\n\n[data-sonner-toast][data-y-position='bottom'][data-swiping='true']::before {\n  top: 50%;\n  transform: scaleY(3) translateY(-50%);\n}\n\n[data-sonner-toast][data-swiping='false'][data-removed='true']::before {\n  content: '';\n  position: absolute;\n  inset: 0;\n  transform: scaleY(2);\n}\n\n[data-sonner-toast][data-expanded='true']::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  height: calc(var(--gap) + 1px);\n  bottom: 100%;\n  width: 100%;\n}\n\n[data-sonner-toast][data-mounted='true'] {\n  --y: translateY(0);\n  opacity: 1;\n}\n\n[data-sonner-toast][data-expanded='false'][data-front='false'] {\n  --scale: var(--toasts-before) * 0.05 + 1;\n  --y: translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));\n  height: var(--front-toast-height);\n}\n\n[data-sonner-toast] > * {\n  transition: opacity 400ms;\n}\n\n[data-sonner-toast][data-x-position='right'] {\n  right: 0;\n}\n\n[data-sonner-toast][data-x-position='left'] {\n  left: 0;\n}\n\n[data-sonner-toast][data-expanded='false'][data-front='false'][data-styled='true'] > * {\n  opacity: 0;\n}\n\n[data-sonner-toast][data-visible='false'] {\n  opacity: 0;\n  pointer-events: none;\n}\n\n[data-sonner-toast][data-mounted='true'][data-expanded='true'] {\n  --y: translateY(calc(var(--lift) * var(--offset)));\n  height: var(--initial-height);\n}\n\n[data-sonner-toast][data-removed='true'][data-front='true'][data-swipe-out='false'] {\n  --y: translateY(calc(var(--lift) * -100%));\n  opacity: 0;\n}\n\n[data-sonner-toast][data-removed='true'][data-front='false'][data-swipe-out='false'][data-expanded='true'] {\n  --y: translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));\n  opacity: 0;\n}\n\n[data-sonner-toast][data-removed='true'][data-front='false'][data-swipe-out='false'][data-expanded='false'] {\n  --y: translateY(40%);\n  opacity: 0;\n  transition: transform 500ms, opacity 200ms;\n}\n\n[data-sonner-toast][data-removed='true'][data-front='false']::before {\n  height: calc(var(--initial-height) + 20%);\n}\n\n[data-sonner-toast][data-swiping='true'] {\n  transform: var(--y) translateY(var(--swipe-amount-y, 0px)) translateX(var(--swipe-amount-x, 0px));\n  transition: none;\n}\n\n[data-sonner-toast][data-swiped='true'] {\n  -webkit-user-select: none; /* Safari 3+ */\n  user-select: none;\n}\n\n[data-sonner-toast][data-swipe-out='true'][data-y-position='bottom'],\n[data-sonner-toast][data-swipe-out='true'][data-y-position='top'] {\n  animation-duration: 200ms;\n  animation-timing-function: ease-out;\n  animation-fill-mode: forwards;\n}\n\n[data-sonner-toast][data-swipe-out='true'][data-swipe-direction='left'] {\n  animation-name: swipe-out-left;\n}\n\n[data-sonner-toast][data-swipe-out='true'][data-swipe-direction='right'] {\n  animation-name: swipe-out-right;\n}\n\n[data-sonner-toast][data-swipe-out='true'][data-swipe-direction='up'] {\n  animation-name: swipe-out-up;\n}\n\n[data-sonner-toast][data-swipe-out='true'][data-swipe-direction='down'] {\n  animation-name: swipe-out-down;\n}\n\n@keyframes swipe-out-left {\n  from {\n    transform: var(--y) translateX(var(--swipe-amount-x));\n    opacity: 1;\n  }\n\n  to {\n    transform: var(--y) translateX(calc(var(--swipe-amount-x) - 100%));\n    opacity: 0;\n  }\n}\n\n@keyframes swipe-out-right {\n  from {\n    transform: var(--y) translateX(var(--swipe-amount-x));\n    opacity: 1;\n  }\n\n  to {\n    transform: var(--y) translateX(calc(var(--swipe-amount-x) + 100%));\n    opacity: 0;\n  }\n}\n\n@keyframes swipe-out-up {\n  from {\n    transform: var(--y) translateY(var(--swipe-amount-y));\n    opacity: 1;\n  }\n\n  to {\n    transform: var(--y) translateY(calc(var(--swipe-amount-y) - 100%));\n    opacity: 0;\n  }\n}\n\n@keyframes swipe-out-down {\n  from {\n    transform: var(--y) translateY(var(--swipe-amount-y));\n    opacity: 1;\n  }\n\n  to {\n    transform: var(--y) translateY(calc(var(--swipe-amount-y) + 100%));\n    opacity: 0;\n  }\n}\n\n@media (max-width: 600px) {\n  [data-sonner-toaster] {\n    position: fixed;\n    right: var(--mobile-offset-right);\n    left: var(--mobile-offset-left);\n    width: 100%;\n  }\n\n  [data-sonner-toaster][dir='rtl'] {\n    left: calc(var(--mobile-offset-left) * -1);\n  }\n\n  [data-sonner-toaster] [data-sonner-toast] {\n    left: 0;\n    right: 0;\n    width: calc(100% - var(--mobile-offset-left) * 2);\n  }\n\n  [data-sonner-toaster][data-x-position='left'] {\n    left: var(--mobile-offset-left);\n  }\n\n  [data-sonner-toaster][data-y-position='bottom'] {\n    bottom: var(--mobile-offset-bottom);\n  }\n\n  [data-sonner-toaster][data-y-position='top'] {\n    top: var(--mobile-offset-top);\n  }\n\n  [data-sonner-toaster][data-x-position='center'] {\n    left: var(--mobile-offset-left);\n    right: var(--mobile-offset-right);\n    transform: none;\n  }\n}\n\n[data-sonner-toaster][data-sonner-theme='light'] {\n  --normal-bg: #fff;\n  --normal-border: var(--gray4);\n  --normal-text: var(--gray12);\n\n  --success-bg: hsl(143, 85%, 96%);\n  --success-border: hsl(145, 92%, 87%);\n  --success-text: hsl(140, 100%, 27%);\n\n  --info-bg: hsl(208, 100%, 97%);\n  --info-border: hsl(221, 91%, 93%);\n  --info-text: hsl(210, 92%, 45%);\n\n  --warning-bg: hsl(49, 100%, 97%);\n  --warning-border: hsl(49, 91%, 84%);\n  --warning-text: hsl(31, 92%, 45%);\n\n  --error-bg: hsl(359, 100%, 97%);\n  --error-border: hsl(359, 100%, 94%);\n  --error-text: hsl(360, 100%, 45%);\n}\n\n[data-sonner-toaster][data-sonner-theme='light'] [data-sonner-toast][data-invert='true'] {\n  --normal-bg: #000;\n  --normal-border: hsl(0, 0%, 20%);\n  --normal-text: var(--gray1);\n}\n\n[data-sonner-toaster][data-sonner-theme='dark'] [data-sonner-toast][data-invert='true'] {\n  --normal-bg: #fff;\n  --normal-border: var(--gray3);\n  --normal-text: var(--gray12);\n}\n\n[data-sonner-toaster][data-sonner-theme='dark'] {\n  --normal-bg: #000;\n  --normal-bg-hover: hsl(0, 0%, 12%);\n  --normal-border: hsl(0, 0%, 20%);\n  --normal-border-hover: hsl(0, 0%, 25%);\n  --normal-text: var(--gray1);\n\n  --success-bg: hsl(150, 100%, 6%);\n  --success-border: hsl(147, 100%, 12%);\n  --success-text: hsl(150, 86%, 65%);\n\n  --info-bg: hsl(215, 100%, 6%);\n  --info-border: hsl(223, 43%, 17%);\n  --info-text: hsl(216, 87%, 65%);\n\n  --warning-bg: hsl(64, 100%, 6%);\n  --warning-border: hsl(60, 100%, 9%);\n  --warning-text: hsl(46, 87%, 65%);\n\n  --error-bg: hsl(358, 76%, 10%);\n  --error-border: hsl(357, 89%, 16%);\n  --error-text: hsl(358, 100%, 81%);\n}\n\n[data-sonner-toaster][data-sonner-theme='dark'] [data-sonner-toast] [data-close-button] {\n  background: var(--normal-bg);\n  border-color: var(--normal-border);\n  color: var(--normal-text);\n}\n\n[data-sonner-toaster][data-sonner-theme='dark'] [data-sonner-toast] [data-close-button]:hover {\n  background: var(--normal-bg-hover);\n  border-color: var(--normal-border-hover);\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-type='success'] {\n  background: var(--success-bg);\n  border-color: var(--success-border);\n  color: var(--success-text);\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-type='success'] [data-close-button] {\n  background: var(--success-bg);\n  border-color: var(--success-border);\n  color: var(--success-text);\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-type='info'] {\n  background: var(--info-bg);\n  border-color: var(--info-border);\n  color: var(--info-text);\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-type='info'] [data-close-button] {\n  background: var(--info-bg);\n  border-color: var(--info-border);\n  color: var(--info-text);\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-type='warning'] {\n  background: var(--warning-bg);\n  border-color: var(--warning-border);\n  color: var(--warning-text);\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-type='warning'] [data-close-button] {\n  background: var(--warning-bg);\n  border-color: var(--warning-border);\n  color: var(--warning-text);\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-type='error'] {\n  background: var(--error-bg);\n  border-color: var(--error-border);\n  color: var(--error-text);\n}\n\n[data-rich-colors='true'][data-sonner-toast][data-type='error'] [data-close-button] {\n  background: var(--error-bg);\n  border-color: var(--error-border);\n  color: var(--error-text);\n}\n\n.sonner-loading-wrapper {\n  --size: 16px;\n  height: var(--size);\n  width: var(--size);\n  position: absolute;\n  inset: 0;\n  z-index: 10;\n}\n\n.sonner-loading-wrapper[data-visible='false'] {\n  transform-origin: center;\n  animation: sonner-fade-out 0.2s ease forwards;\n}\n\n.sonner-spinner {\n  position: relative;\n  top: 50%;\n  left: 50%;\n  height: var(--size);\n  width: var(--size);\n}\n\n.sonner-loading-bar {\n  animation: sonner-spin 1.2s linear infinite;\n  background: var(--gray11);\n  border-radius: 6px;\n  height: 8%;\n  left: -10%;\n  position: absolute;\n  top: -3.9%;\n  width: 24%;\n}\n\n.sonner-loading-bar:nth-child(1) {\n  animation-delay: -1.2s;\n  transform: rotate(0.0001deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(2) {\n  animation-delay: -1.1s;\n  transform: rotate(30deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(3) {\n  animation-delay: -1s;\n  transform: rotate(60deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(4) {\n  animation-delay: -0.9s;\n  transform: rotate(90deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(5) {\n  animation-delay: -0.8s;\n  transform: rotate(120deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(6) {\n  animation-delay: -0.7s;\n  transform: rotate(150deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(7) {\n  animation-delay: -0.6s;\n  transform: rotate(180deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(8) {\n  animation-delay: -0.5s;\n  transform: rotate(210deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(9) {\n  animation-delay: -0.4s;\n  transform: rotate(240deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(10) {\n  animation-delay: -0.3s;\n  transform: rotate(270deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(11) {\n  animation-delay: -0.2s;\n  transform: rotate(300deg) translate(146%);\n}\n\n.sonner-loading-bar:nth-child(12) {\n  animation-delay: -0.1s;\n  transform: rotate(330deg) translate(146%);\n}\n\n@keyframes sonner-fade-in {\n  0% {\n    opacity: 0;\n    transform: scale(0.8);\n  }\n  100% {\n    opacity: 1;\n    transform: scale(1);\n  }\n}\n\n@keyframes sonner-fade-out {\n  0% {\n    opacity: 1;\n    transform: scale(1);\n  }\n  100% {\n    opacity: 0;\n    transform: scale(0.8);\n  }\n}\n\n@keyframes sonner-spin {\n  0% {\n    opacity: 1;\n  }\n  100% {\n    opacity: 0.15;\n  }\n}\n\n@media (prefers-reduced-motion) {\n  [data-sonner-toast],\n  [data-sonner-toast] > *,\n  .sonner-loading-bar {\n    transition: none !important;\n    animation: none !important;\n  }\n}\n\n.sonner-loader {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  transform-origin: center;\n  transition: opacity 200ms, transform 200ms;\n}\n\n.sonner-loader[data-visible='false'] {\n  opacity: 0;\n  transform: scale(0.8) translate(-50%, -50%);\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "import React from 'react';\n\nexport type ToastTypes = 'normal' | 'action' | 'success' | 'info' | 'warning' | 'error' | 'loading' | 'default';\n\nexport type PromiseT<Data = any> = Promise<Data> | (() => Promise<Data>);\n\nexport interface PromiseIExtendedResult extends ExternalToast {\n  message: React.ReactNode;\n}\n\nexport type PromiseTExtendedResult<Data = any> =\n  | PromiseIExtendedResult\n  | ((data: Data) => PromiseIExtendedResult | Promise<PromiseIExtendedResult>);\n\nexport type PromiseTResult<Data = any> =\n  | string\n  | React.ReactNode\n  | ((data: Data) => React.ReactNode | string | Promise<React.ReactNode | string>);\n\nexport type PromiseExternalToast = Omit<ExternalToast, 'description'>;\n\nexport type PromiseData<ToastData = any> = PromiseExternalToast & {\n  loading?: string | React.ReactNode;\n  success?: PromiseTResult<ToastData> | PromiseTExtendedResult<ToastData>;\n  error?: PromiseTResult | PromiseTExtendedResult;\n  description?: PromiseTResult;\n  finally?: () => void | Promise<void>;\n};\n\nexport interface ToastClassnames {\n  toast?: string;\n  title?: string;\n  description?: string;\n  loader?: string;\n  closeButton?: string;\n  cancelButton?: string;\n  actionButton?: string;\n  success?: string;\n  error?: string;\n  info?: string;\n  warning?: string;\n  loading?: string;\n  default?: string;\n  content?: string;\n  icon?: string;\n}\n\nexport interface ToastIcons {\n  success?: React.ReactNode;\n  info?: React.ReactNode;\n  warning?: React.ReactNode;\n  error?: React.ReactNode;\n  loading?: React.ReactNode;\n  close?: React.ReactNode;\n}\n\nexport interface Action {\n  label: React.ReactNode;\n  onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;\n  actionButtonStyle?: React.CSSProperties;\n}\n\nexport interface ToastT {\n  id: number | string;\n  toasterId?: string;\n  title?: (() => React.ReactNode) | React.ReactNode;\n  type?: ToastTypes;\n  icon?: React.ReactNode;\n  jsx?: React.ReactNode;\n  richColors?: boolean;\n  invert?: boolean;\n  closeButton?: boolean;\n  dismissible?: boolean;\n  description?: (() => React.ReactNode) | React.ReactNode;\n  duration?: number;\n  delete?: boolean;\n  action?: Action | React.ReactNode;\n  cancel?: Action | React.ReactNode;\n  onDismiss?: (toast: ToastT) => void;\n  onAutoClose?: (toast: ToastT) => void;\n  promise?: PromiseT;\n  cancelButtonStyle?: React.CSSProperties;\n  actionButtonStyle?: React.CSSProperties;\n  style?: React.CSSProperties;\n  unstyled?: boolean;\n  className?: string;\n  classNames?: ToastClassnames;\n  descriptionClassName?: string;\n  position?: Position;\n  testId?: string;\n}\n\nexport function isAction(action: Action | React.ReactNode): action is Action {\n  return (action as Action).label !== undefined;\n}\n\nexport type Position = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center';\nexport interface HeightT {\n  height: number;\n  toastId: number | string;\n  position: Position;\n}\n\ninterface ToastOptions {\n  className?: string;\n  closeButton?: boolean;\n  descriptionClassName?: string;\n  style?: React.CSSProperties;\n  cancelButtonStyle?: React.CSSProperties;\n  actionButtonStyle?: React.CSSProperties;\n  duration?: number;\n  unstyled?: boolean;\n  classNames?: ToastClassnames;\n  closeButtonAriaLabel?: string;\n  toasterId?: string;\n}\n\ntype Offset =\n  | {\n      top?: string | number;\n      right?: string | number;\n      bottom?: string | number;\n      left?: string | number;\n    }\n  | string\n  | number;\n\nexport interface ToasterProps {\n  id?: string;\n  invert?: boolean;\n  theme?: 'light' | 'dark' | 'system';\n  position?: Position;\n  hotkey?: string[];\n  richColors?: boolean;\n  expand?: boolean;\n  duration?: number;\n  gap?: number;\n  visibleToasts?: number;\n  closeButton?: boolean;\n  toastOptions?: ToastOptions;\n  className?: string;\n  style?: React.CSSProperties;\n  offset?: Offset;\n  mobileOffset?: Offset;\n  dir?: 'rtl' | 'ltr' | 'auto';\n  swipeDirections?: SwipeDirection[];\n  icons?: ToastIcons;\n  customAriaLabel?: string;\n  containerAriaLabel?: string;\n}\n\nexport type SwipeDirection = 'top' | 'right' | 'bottom' | 'left';\n\nexport interface ToastProps {\n  toast: ToastT;\n  toasts: ToastT[];\n  index: number;\n  swipeDirections?: SwipeDirection[];\n  expanded: boolean;\n  invert: boolean;\n  heights: HeightT[];\n  setHeights: React.Dispatch<React.SetStateAction<HeightT[]>>;\n  removeToast: (toast: ToastT) => void;\n  gap?: number;\n  position: Position;\n  visibleToasts: number;\n  expandByDefault: boolean;\n  closeButton: boolean;\n  interacting: boolean;\n  style?: React.CSSProperties;\n  cancelButtonStyle?: React.CSSProperties;\n  actionButtonStyle?: React.CSSProperties;\n  duration?: number;\n  className?: string;\n  unstyled?: boolean;\n  descriptionClassName?: string;\n  loadingIcon?: React.ReactNode;\n  classNames?: ToastClassnames;\n  icons?: ToastIcons;\n  closeButtonAriaLabel?: string;\n  defaultRichColors?: boolean;\n}\n\nexport enum SwipeStateTypes {\n  SwipedOut = 'SwipedOut',\n  SwipedBack = 'SwipedBack',\n  NotSwiped = 'NotSwiped',\n}\n\nexport type Theme = 'light' | 'dark';\n\nexport interface ToastToDismiss {\n  id: number | string;\n  dismiss: boolean;\n}\n\nexport type ExternalToast = Omit<ToastT, 'id' | 'type' | 'title' | 'jsx' | 'delete' | 'promise'> & {\n  id?: number | string;\n  toasterId?: string;\n};\n"
  },
  {
    "path": "test/.eslintrc.json",
    "content": "{\n  \"extends\": \"next/core-web-vitals\"\n}\n"
  },
  {
    "path": "test/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n/test-results/\n/playwright-report/\n/playwright/.cache/\n"
  },
  {
    "path": "test/.npmrc",
    "content": "package-manager-strict=false\n"
  },
  {
    "path": "test/.vscode/settings.json",
    "content": "{\n  \"typescript.tsdk\": \"../node_modules/.pnpm/typescript@4.9.5/node_modules/typescript/lib\",\n  \"typescript.enablePromptUseWorkspaceTsdk\": true\n}\n"
  },
  {
    "path": "test/README.md",
    "content": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.\n\n[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.\n\nThe `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.\n\nThis project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.\n"
  },
  {
    "path": "test/next.config.js",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nmodule.exports = nextConfig;\n"
  },
  {
    "path": "test/package.json",
    "content": "{\n  \"name\": \"test\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"@types/node\": \"18.15.0\",\n    \"@types/react\": \"18.0.28\",\n    \"@types/react-dom\": \"18.0.11\",\n    \"ai\": \"^3.4.9\",\n    \"eslint\": \"8.35.0\",\n    \"eslint-config-next\": \"13.2.4\",\n    \"next\": \"14.2.15\",\n    \"react\": \"18.3.1\",\n    \"react-dom\": \"18.3.1\",\n    \"sonner\": \"workspace:*\",\n    \"typescript\": \"4.9.5\"\n  }\n}\n"
  },
  {
    "path": "test/src/app/action.tsx",
    "content": "'use server';\nimport { createStreamableUI } from 'ai/rsc';\n\nexport async function action() {\n  'use server';\n  let progress = 0;\n  const ui = createStreamableUI('loading 0%');\n  const interval = setInterval(() => {\n    progress += 10;\n    ui.update('loading ' + progress + '%');\n    if (progress >= 100) {\n      clearInterval(interval);\n      ui.update('load complete');\n    }\n  }, 100);\n  return ui.value;\n}\n"
  },
  {
    "path": "test/src/app/layout.tsx",
    "content": "export const metadata = {\n  title: 'Create Next App',\n  description: 'Generated by create next app',\n};\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <body>{children}</body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "test/src/app/page.tsx",
    "content": "'use client';\n\nimport React from 'react';\nimport { Toaster, toast } from 'sonner';\nimport { action } from '@/app/action';\n\nconst promise = () => new Promise((resolve) => setTimeout(resolve, 2000));\n\nexport default function Home({ searchParams }: any) {\n  const [showAutoClose, setShowAutoClose] = React.useState(false);\n  const [showDismiss, setShowDismiss] = React.useState(false);\n  const [theme, setTheme] = React.useState(searchParams.theme || 'light');\n  const [isFinally, setIsFinally] = React.useState(false);\n  const [showAriaLabels, setShowAriaLabels] = React.useState(false);\n\n  return (\n    <>\n      <button data-testid=\"theme-button\" className=\"button\" onClick={() => setTheme('dark')}>\n        Change theme\n      </button>\n      <button data-testid=\"default-button\" className=\"button\" onClick={() => toast('My Toast')}>\n        Render Toast\n      </button>\n      <button data-testid=\"default-button-top\" className=\"button\" onClick={() => toast('My Toast')}>\n        Render Toast Top\n      </button>\n      <button data-testid=\"success\" className=\"button\" onClick={() => toast.success('My Success Toast')}>\n        Render Success Toast\n      </button>\n      <button data-testid=\"error\" className=\"button\" onClick={() => toast.error('My Error Toast')}>\n        Render Error Toast\n      </button>\n      <button\n        data-testid=\"action\"\n        className=\"button\"\n        onClick={() =>\n          toast('My Message', {\n            action: {\n              label: 'Action',\n              onClick: () => console.log('Action'),\n            },\n          })\n        }\n      >\n        Render Action Toast\n      </button>\n      <button\n        data-testid=\"action-prevent\"\n        className=\"button\"\n        onClick={() =>\n          toast('My Message', {\n            action: {\n              label: 'Action',\n              onClick: (event) => {\n                event.preventDefault();\n                console.log('Action');\n              },\n            },\n          })\n        }\n      >\n        Render Action Toast\n      </button>\n      <button\n        data-testid=\"promise\"\n        data-finally={isFinally ? '1' : '0'}\n        className=\"button\"\n        onClick={() =>\n          toast.promise(promise, {\n            loading: 'Loading...',\n            success: 'Loaded',\n            error: 'Error',\n            finally: () => setIsFinally(true),\n          })\n        }\n      >\n        Render Promise Toast\n      </button>\n      <button\n        data-testid=\"rsf-promise\"\n        data-finally={isFinally ? '1' : '0'}\n        className=\"button\"\n        onClick={() =>\n          toast.promise(action(), {\n            loading: 'Loading...',\n            success: 'Loaded',\n            error: 'Error',\n            finally: () => setIsFinally(true),\n          })\n        }\n      >\n        Render React Server Function Toast\n      </button>\n      <button\n        data-testid=\"custom\"\n        className=\"button\"\n        onClick={() =>\n          toast.custom((t) => (\n            <div>\n              <h1>jsx</h1>\n              <button data-testid=\"dismiss-button\" onClick={() => toast.dismiss(t)}>\n                Dismiss\n              </button>\n            </div>\n          ))\n        }\n      >\n        Render Custom Toast\n      </button>\n      <button\n        data-testid=\"custom-cancel-button-toast\"\n        className=\"button\"\n        onClick={() =>\n          toast('My Custom Cancel Button', {\n            cancel: {\n              label: 'Cancel',\n              onClick: () => console.log('Cancel'),\n            },\n          })\n        }\n      >\n        Render Custom Cancel Button\n      </button>\n      <button\n        data-testid=\"custom-with-empty-id\"\n        className=\"button\"\n        onClick={() =>\n          toast.custom(\n            (t) => (\n              <div>\n                <h1>jsx</h1>\n                <button data-testid=\"dismiss-button\" onClick={() => toast.dismiss(t)}>\n                  Dismiss\n                </button>\n              </div>\n            ),\n            {\n              id: undefined,\n            },\n          )\n        }\n      >\n        Render Custom Toast with empty id\n      </button>\n      <button data-testid=\"infinity-toast\" className=\"button\" onClick={() => toast('My Toast', { duration: Infinity })}>\n        Render Infinity Toast\n      </button>\n      <button\n        data-testid=\"auto-close-toast-callback\"\n        className=\"button\"\n        onClick={() =>\n          toast('My Toast', {\n            onAutoClose: () => setShowAutoClose(true),\n          })\n        }\n      >\n        Render Toast With onAutoClose callback\n      </button>\n      <button\n        data-testid=\"dismiss-toast-callback\"\n        className=\"button\"\n        onClick={() =>\n          toast('My Toast', {\n            onDismiss: () => {\n              setShowDismiss(true);\n            },\n          })\n        }\n      >\n        Dismiss toast callback\n      </button>\n      <button\n        data-testid=\"non-dismissible-toast\"\n        className=\"button\"\n        onClick={() =>\n          toast('My Toast', {\n            dismissible: false,\n          })\n        }\n      >\n        Non-dismissible Toast\n      </button>\n      <button\n        data-testid=\"update-toast\"\n        className=\"button\"\n        onClick={() => {\n          const toastId = toast('My Unupdated Toast', {\n            duration: 10000,\n          });\n          toast('My Updated Toast', {\n            id: toastId,\n            duration: 10000,\n          });\n        }}\n      >\n        Updated Toast\n      </button>\n      <button\n        data-testid=\"update-toast-duration\"\n        className=\"button\"\n        onClick={() => {\n          const toastId = toast('My Unupdated Toast, Updated After 3 Seconds', {\n            duration: 10000,\n          });\n          setTimeout(() => {\n            toast('My Updated Toast, Close After 1 Second', {\n              id: toastId,\n              duration: 1000,\n            });\n          }, 3000);\n        }}\n      >\n        Updated Toast Duration\n      </button>\n      <button\n        data-testid=\"string-description\"\n        className=\"button\"\n        onClick={() => toast('Custom Description', { description: 'string description' })}\n      >\n        String Description\n      </button>\n      <button\n        data-testid=\"react-node-description\"\n        className=\"button\"\n        onClick={() => toast('Custom Description', { description: <div>This is my custom ReactNode description</div> })}\n      >\n        ReactNode Description\n      </button>\n      <button\n        data-testid=\"close-button\"\n        className=\"button\"\n        onClick={() => toast('Toast with close button', { closeButton: true })}\n      >\n        Render close button\n      </button>\n      <button\n        data-testid=\"extended-promise\"\n        className=\"button\"\n        onClick={() =>\n          toast.promise(\n            new Promise((resolve) => {\n              setTimeout(() => {\n                resolve({ name: 'Sonner' });\n              }, 2000);\n            }),\n            {\n              loading: 'Loading...',\n              success: (data: any) => ({\n                message: `${data.name} toast has been added`,\n                description: 'Custom description for the Success state',\n              }),\n              error: {\n                message: 'An error occurred',\n                description: undefined,\n                action: {\n                  label: 'Retry',\n                  onClick: () => {\n                    console.log('retrying');\n                  },\n                },\n              },\n              description: 'Global description',\n            },\n          )\n        }\n      >\n        Extended Promise Toast\n      </button>\n\n      <button\n        data-testid=\"extended-promise-error\"\n        className=\"button\"\n        onClick={() =>\n          toast.promise(\n            new Promise((_, reject) => {\n              setTimeout(() => {\n                reject(new Error('Simulated error'));\n              }, 2000);\n            }),\n            {\n              loading: 'Loading...',\n              success: (data: any) => ({\n                message: `${data.name} toast has been added`,\n                description: 'Custom description for the Success state',\n              }),\n              error: {\n                message: 'An error occurred',\n                description: undefined,\n                action: {\n                  label: 'Retry',\n                  onClick: (event) => {\n                    event.preventDefault();\n                    console.log('retrying');\n                  },\n                },\n              },\n              description: 'Global description',\n            },\n          )\n        }\n      >\n        Extended Promise Error Toast\n      </button>\n      <button\n        data-testid=\"error-promise\"\n        className=\"button\"\n        onClick={() => {\n          const whatWillHappen = async () => {\n            throw new Error('Not implemented');\n          };\n\n          toast.promise(whatWillHappen, {\n            loading: 'Saving project...',\n            success: (result: any) => {\n              if (result?.ok) {\n                return 'Project saved';\n              } else {\n                return `${result?.error}`;\n              }\n            },\n            error: (e) => `Error Raise: ${e}`,\n          });\n        }}\n      >\n        Error Promise Toast\n      </button>\n      <button\n        className=\"button\"\n        onClick={() => {\n          setShowAriaLabels(true);\n          toast('Toast with custom ARIA labels', { closeButton: true, onAutoClose: () => setShowAriaLabels(false) });\n        }}\n      >\n        With custom ARIA labels\n      </button>\n      <button\n        data-testid=\"toast-secondary\"\n        className=\"button\"\n        onClick={() => toast('Secondary Toaster Toast', { toasterId: 'secondary' })}\n      >\n        Render Toast in Secondary Toaster\n      </button>\n      <button data-testid=\"toast-global\" className=\"button\" onClick={() => toast('Global Toaster Toast')}>\n        Render Toast in Global Toaster\n      </button>\n      <button\n        data-testid=\"testid-toast-button\"\n        className=\"button\"\n        onClick={() => toast('Toast with test ID', { testId: 'my-test-toast' })}\n      >\n        Toast with testId\n      </button>\n      <button\n        data-testid=\"testid-promise-toast-button\"\n        className=\"button\"\n        onClick={() =>\n          toast.promise(promise, {\n            loading: 'Loading...',\n            success: 'Loaded',\n            error: 'Error',\n            testId: 'promise-test-toast',\n          })\n        }\n      >\n        Promise Toast with testId\n      </button>\n      {showAutoClose ? <div data-testid=\"auto-close-el\" /> : null}\n      {showDismiss ? <div data-testid=\"dismiss-el\" /> : null}\n      <Toaster\n        offset={32}\n        position={searchParams.position || 'bottom-right'}\n        toastOptions={{\n          actionButtonStyle: { backgroundColor: 'rgb(219, 239, 255)' },\n          cancelButtonStyle: { backgroundColor: 'rgb(254, 226, 226)' },\n          closeButtonAriaLabel: showAriaLabels ? 'Yeet the notice' : undefined,\n        }}\n        theme={theme}\n        dir={searchParams.dir || 'auto'}\n        containerAriaLabel={showAriaLabels ? 'Notices' : undefined}\n        icons={{\n          close:\n            searchParams.customCloseIcon === '' ? (\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"12\"\n                height=\"12\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"3\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              >\n                <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n                <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n              </svg>\n            ) : undefined,\n        }}\n      />\n      <Toaster\n        id=\"secondary\"\n        position=\"top-left\"\n        toastOptions={{\n          className: 'secondary-toaster',\n        }}\n      />\n    </>\n  );\n}\n\nHome.theme = 'light';\n"
  },
  {
    "path": "test/tests/basic.spec.ts",
    "content": "import { expect, test } from '@playwright/test';\nimport { toast } from 'sonner';\n\ntest.beforeEach(async ({ page }) => {\n  await page.goto('/');\n});\n\ntest.describe('Basic functionality', () => {\n  test('toast is rendered and disappears after the default timeout', async ({ page }) => {\n    await page.getByTestId('default-button').click();\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(1);\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(0);\n  });\n\n  test('various toast types are rendered correctly', async ({ page }) => {\n    await page.getByTestId('success').click();\n    await expect(page.getByText('My Success Toast', { exact: true })).toHaveCount(1);\n\n    await page.getByTestId('error').click();\n    await expect(page.getByText('My Error Toast', { exact: true })).toHaveCount(1);\n\n    await page.getByTestId('action').click();\n    await expect(page.locator('[data-button]')).toHaveCount(1);\n  });\n\n  test('show correct toast content based on promise state', async ({ page }) => {\n    await page.getByTestId('promise').click();\n    await expect(page.getByText('Loading...')).toHaveCount(1);\n    await expect(page.getByText('Loaded')).toHaveCount(1);\n  });\n\n  test('handle toast promise rejections', async ({ page }) => {\n    const rejectedPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Promise rejected')), 100));\n    try {\n      toast.promise(rejectedPromise, {});\n    } catch {\n      throw new Error('Promise should not have rejected without unwrap');\n    }\n\n    await expect(toast.promise(rejectedPromise, {}).unwrap()).rejects.toThrow('Promise rejected');\n  });\n\n  test('promise toast with extended configuration', async ({ page }) => {\n    await page.getByTestId('extended-promise').click();\n\n    // Check loading state\n    await expect(page.getByText('Loading...')).toHaveCount(1);\n\n    // Check success state with custom message and description\n    await expect(page.getByText('Sonner toast has been added')).toHaveCount(1);\n    await expect(page.getByText('Custom description for the Success state')).toHaveCount(1);\n\n    // Verify global description is not shown (overridden by success description)\n    await expect(page.getByText('Global description')).toHaveCount(0);\n  });\n\n  test('promise toast with extended error configuration', async ({ page }) => {\n    await page.getByTestId('extended-promise-error').click();\n\n    // Check loading state\n    await expect(page.getByText('Loading...')).toHaveCount(1);\n\n    // Check error state\n    await expect(page.getByText('An error occurred')).toHaveCount(1);\n\n    // Verify action button is present\n    const actionButton = page.getByText('Retry');\n    await expect(actionButton).toHaveCount(1);\n\n    // Click retry button and verify it doesn't close the toast (due to preventDefault)\n    await actionButton.click();\n    await expect(page.getByText('An error occurred')).toHaveCount(1);\n  });\n\n  test('promise toast with Error object rejection', async ({ page }) => {\n    await page.getByTestId('error-promise').click();\n\n    // Check error state shows the error message correctly\n    await expect(page.getByText('Error Raise: Error: Not implemented')).toHaveCount(1);\n  });\n\n  test('render custom jsx in toast', async ({ page }) => {\n    await page.getByTestId('custom').click();\n    await expect(page.getByText('jsx')).toHaveCount(1);\n  });\n\n  test('toast is removed after swiping down', async ({ page }) => {\n    await page.getByTestId('default-button').click();\n    await page.hover('[data-sonner-toast]');\n    await page.mouse.down();\n    await page.mouse.move(0, 800);\n    await page.mouse.up();\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(0);\n  });\n\n  test('dismissible toast is not removed when dragged', async ({ page }) => {\n    await page.getByTestId('non-dismissible-toast').click();\n    const toast = page.locator('[data-sonner-toast]');\n    const dragBoundingBox = await toast.boundingBox();\n\n    if (!dragBoundingBox) return;\n    await page.mouse.move(dragBoundingBox.x + dragBoundingBox.width / 2, dragBoundingBox.y);\n\n    await page.mouse.down();\n    await page.mouse.move(0, dragBoundingBox.y + 300);\n\n    await page.mouse.up();\n    await expect(page.getByTestId('non-dismissible-toast')).toHaveCount(1);\n  });\n\n  test('toast is removed after swiping up', async ({ page }) => {\n    await page.goto('/?position=top-left');\n    await page.getByTestId('default-button').click();\n    await page.hover('[data-sonner-toast]');\n    await page.mouse.down();\n    await page.mouse.move(0, -800);\n    await page.mouse.up();\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(0);\n  });\n\n  test('toast is not removed when hovered', async ({ page }) => {\n    await page.getByTestId('default-button').click();\n\n    // Wait for toast to be visible first\n    await expect(page.locator('[data-sonner-toast]')).toBeVisible();\n\n    // Hover the toast\n    await page.hover('[data-sonner-toast]');\n\n    // Wait a bit to ensure hover is registered\n    await page.waitForTimeout(100);\n\n    // Create a longer timeout to verify toast persists\n    await page.waitForTimeout(5000);\n\n    // Verify toast is still visible\n    await expect(page.locator('[data-sonner-toast]')).toBeVisible();\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(1);\n  });\n\n  test('toast is not removed if duration is set to infinity', async ({ page }) => {\n    await page.getByTestId('infinity-toast').click();\n\n    await expect(page.locator('[data-sonner-toast]')).toBeVisible();\n\n    const toast = page.locator('[data-sonner-toast]');\n    await toast.hover({ force: true });\n\n    await page.waitForTimeout(100);\n\n    await page.waitForTimeout(5000);\n\n    await expect(toast).toBeVisible();\n    await expect(toast).toHaveCount(1);\n  });\n\n  test('toast is not removed when event prevented in action', async ({ page }) => {\n    await page.getByTestId('action-prevent').click();\n    await page.locator('[data-button]').click();\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(1);\n  });\n\n  test(\"toast's auto close callback gets executed correctly\", async ({ page }) => {\n    await page.getByTestId('auto-close-toast-callback').click();\n    await expect(page.getByTestId('auto-close-el')).toHaveCount(1);\n  });\n\n  test(\"toast's dismiss callback gets executed correctly\", async ({ page }) => {\n    await page.getByTestId('dismiss-toast-callback').click();\n    const toast = page.locator('[data-sonner-toast]');\n\n    await toast.waitFor({ state: 'visible' });\n\n    const box = await toast.boundingBox();\n    if (!box) return;\n\n    const startX = box.x + box.width / 2;\n    const startY = box.y + box.height / 2;\n\n    await page.mouse.move(startX, startY);\n    await page.mouse.down();\n\n    // Small initial movement to trigger drag\n    await page.mouse.move(startX, startY + 20, { steps: 5 });\n\n    // Main swipe movement\n    await page.mouse.move(startX, startY + 300, { steps: 10 });\n    await page.mouse.up();\n\n    await expect(page.getByTestId('dismiss-el')).toHaveCount(1);\n  });\n\n  test(\"toaster's theme should be light\", async ({ page }) => {\n    await page.getByTestId('infinity-toast').click();\n    await expect(page.locator('[data-sonner-toaster]')).toHaveAttribute('data-sonner-theme', 'light');\n  });\n\n  test(\"toaster's theme should be dark\", async ({ page }) => {\n    await page.goto('/?theme=dark');\n    await page.getByTestId('infinity-toast').click();\n    await expect(page.locator('[data-sonner-toaster]')).toHaveAttribute('data-sonner-theme', 'dark');\n  });\n\n  test(\"toaster's theme should be changed\", async ({ page }) => {\n    await page.getByTestId('infinity-toast').click();\n    await expect(page.locator('[data-sonner-toaster]')).toHaveAttribute('data-sonner-theme', 'light');\n    await page.getByTestId('theme-button').click();\n    await expect(page.locator('[data-sonner-toaster]')).toHaveAttribute('data-sonner-theme', 'dark');\n  });\n\n  test('return focus to the previous focused element', async ({ page }) => {\n    await page.getByTestId('custom').focus();\n    await page.keyboard.press('Enter');\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(1);\n    await page.getByTestId('dismiss-button').focus();\n    await page.keyboard.press('Enter');\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(0);\n    await expect(page.getByTestId('custom')).toBeFocused();\n  });\n\n  test(\"toaster's dir prop is reflected correctly\", async ({ page }) => {\n    await page.goto('/?dir=rtl');\n    await page.getByTestId('default-button').click();\n    await expect(page.locator('[data-sonner-toaster]')).toHaveAttribute('dir', 'rtl');\n  });\n\n  test(\"toaster respects the HTML's dir attribute\", async ({ page }) => {\n    await page.evaluate(() => {\n      document.documentElement.setAttribute('dir', 'rtl');\n    });\n    await page.getByTestId('default-button').click();\n    await expect(page.locator('[data-sonner-toaster]')).toHaveAttribute('dir', 'rtl');\n  });\n\n  test(\"toaster respects its own dir attribute over HTML's\", async ({ page }) => {\n    await page.goto('/?dir=ltr');\n    await page.evaluate(() => {\n      document.documentElement.setAttribute('dir', 'rtl');\n    });\n    await page.getByTestId('default-button').click();\n    await expect(page.locator('[data-sonner-toaster]')).toHaveAttribute('dir', 'ltr');\n  });\n\n  test('show correct toast content when updating', async ({ page }) => {\n    await page.getByTestId('update-toast').click();\n    await expect(page.getByText('My Unupdated Toast')).toHaveCount(0);\n    await expect(page.getByText('My Updated Toast')).toHaveCount(1);\n  });\n\n  test('should update toast content and duration after 3 seconds', async ({ page }) => {\n    await page.getByTestId('update-toast-duration').click();\n\n    const initialToast = page.getByText('My Unupdated Toast, Updated After 3 Seconds');\n    await expect(initialToast).toBeVisible();\n\n    await page.waitForTimeout(3000);\n    const updatedToast = page.getByText('My Updated Toast, Close After 1 Second');\n    await expect(updatedToast).toBeVisible();\n\n    await expect(initialToast).not.toBeVisible();\n\n    await page.waitForTimeout(1200);\n    await expect(updatedToast).not.toBeVisible();\n  });\n\n  test('cancel button is rendered with custom styles', async ({ page }) => {\n    await page.getByTestId('custom-cancel-button-toast').click();\n    const button = await page.locator('[data-cancel]');\n\n    await expect(button).toHaveCSS('background-color', 'rgb(254, 226, 226)');\n  });\n\n  test('cancel button dismisses the custom toast with empty id', async ({ page }) => {\n    await page.getByTestId('custom-with-empty-id').click();\n\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(1);\n    await page.locator('[data-dismiss]').click();\n    await expect(page.locator('[data-sonner-toast]')).toHaveCount(0);\n  });\n\n  test('action button is rendered with custom styles', async ({ page }) => {\n    await page.getByTestId('action').click();\n    const button = await page.locator('[data-button]');\n\n    await expect(button).toHaveCSS('background-color', 'rgb(219, 239, 255)');\n  });\n\n  test('string description is rendered', async ({ page }) => {\n    await page.getByTestId('string-description').click();\n    await expect(page.getByText('string description')).toHaveCount(1);\n  });\n\n  test('ReactNode description is rendered', async ({ page }) => {\n    await page.getByTestId('react-node-description').click();\n    await expect(page.getByText('This is my custom ReactNode description')).toHaveCount(1);\n  });\n\n  test('aria labels are custom', async ({ page }) => {\n    await page.getByRole('button', { name: 'With custom ARIA labels' }).click();\n    await expect(page.getByText('Toast with custom ARIA labels')).toHaveCount(1);\n    await expect(page.getByLabel('Notices')).toHaveCount(1);\n    await expect(page.getByLabel('Yeet the notice', { exact: true })).toHaveCount(1);\n  });\n\n  test('toast with toasterId only appears in the correct Toaster', async ({ page }) => {\n    await page.getByTestId('toast-secondary').click();\n    const secondaryToaster = page.locator('[data-sonner-toaster][data-x-position=\"left\"][data-y-position=\"top\"]');\n    await expect(secondaryToaster.getByText('Secondary Toaster Toast')).toHaveCount(1);\n    const globalToaster = page.locator('[data-sonner-toaster][data-x-position=\"right\"][data-y-position=\"bottom\"]');\n    await expect(globalToaster.getByText('Secondary Toaster Toast')).toHaveCount(0);\n  });\n\n  test('toast without toasterId only appears in the global Toaster', async ({ page }) => {\n    await page.getByTestId('toast-global').click();\n    const globalToaster = page.locator('[data-sonner-toaster][data-x-position=\"right\"][data-y-position=\"bottom\"]');\n    await expect(globalToaster.getByText('Global Toaster Toast')).toHaveCount(1);\n    const secondaryToaster = page.locator('[data-sonner-toaster][data-x-position=\"left\"][data-y-position=\"top\"]');\n    await expect(secondaryToaster.getByText('Global Toaster Toast')).toHaveCount(0);\n  });\n\n  test('toast with testId renders data-testid attribute correctly', async ({ page }) => {\n    await page.getByTestId('testid-toast-button').click();\n    await expect(page.getByTestId('my-test-toast')).toBeVisible();\n    await expect(page.getByTestId('my-test-toast')).toHaveText('Toast with test ID');\n  });\n\n  test('toast without testId does not have data-testid attribute', async ({ page }) => {\n    await page.getByTestId('default-button').click();\n    const toast = page.locator('[data-sonner-toast]');\n    await expect(toast).toBeVisible();\n    await expect(toast).not.toHaveAttribute('data-testid');\n  });\n\n  test('promise toast with testId maintains testId through state changes', async ({ page }) => {\n    await page.getByTestId('testid-promise-toast-button').click();\n    await expect(page.getByTestId('promise-test-toast')).toBeVisible();\n    await expect(page.getByTestId('promise-test-toast')).toHaveText('Loading...');\n    await expect(page.getByTestId('promise-test-toast')).toHaveText('Loaded');\n  });\n});\n"
  },
  {
    "path": "test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\", \"../playwright.config.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"jsx\": \"react\",\n    \"target\": \"ES2018\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"lib\": [\"es2015\", \"dom\"]\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"extends\": [\"//\"],\n  \"pipeline\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\", \".next/**\"]\n    },\n    \"dev\": {\n      \"cache\": false\n    }\n  }\n}\n"
  },
  {
    "path": "website/.eslintrc.json",
    "content": "{\n  \"extends\": \"next/core-web-vitals\"\n}\n"
  },
  {
    "path": "website/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "website/.vscode/settings.json",
    "content": "{\n  \"typescript.tsdk\": \"../node_modules/.pnpm/typescript@4.9.5/node_modules/typescript/lib\",\n  \"typescript.enablePromptUseWorkspaceTsdk\": true\n}\n"
  },
  {
    "path": "website/README.md",
    "content": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.\n\n[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.\n\nThe `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.\n\nThis project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.\n"
  },
  {
    "path": "website/next.config.js",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  experimental: {\n    appDir: true,\n  },\n};\n\nconst withNextra = require('nextra')({\n  title: 'Sonner',\n  theme: 'nextra-theme-docs',\n  themeConfig: './theme.config.jsx',\n  defaultShowCopyCode: true,\n});\n\nmodule.exports = withNextra(nextConfig);\n"
  },
  {
    "path": "website/package.json",
    "content": "{\n  \"name\": \"website\",\n  \"version\": \"0.1.0\",\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"@types/node\": \"18.11.18\",\n    \"@types/react\": \"18.2.0\",\n    \"@types/react-dom\": \"18.0.10\",\n    \"@vercel/analytics\": \"^1.1.0\",\n    \"clsx\": \"^2.0.0\",\n    \"copy-to-clipboard\": \"^3.3.3\",\n    \"eslint-config-next\": \"^13.2.3\",\n    \"framer-motion\": \"^9.0.1\",\n    \"next\": \"13.4.19\",\n    \"next-mdx-remote\": \"^4.3.0\",\n    \"nextra\": \"^2.12.3\",\n    \"nextra-theme-docs\": \"^2.12.3\",\n    \"prism-react-renderer\": \"^1.3.5\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"18.2.0\",\n    \"react-use-measure\": \"^2.1.1\",\n    \"sonner\": \"workspace:*\",\n    \"typescript\": \"4.9.5\"\n  },\n  \"devDependencies\": {\n    \"autoprefixer\": \"^10.4.15\",\n    \"postcss\": \"^8.4.29\",\n    \"tailwindcss\": \"^3.3.3\"\n  }\n}\n"
  },
  {
    "path": "website/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "website/src/components/CodeBlock/code-block.module.css",
    "content": ".root {\n  padding: 16px;\n  margin: 0;\n  background: var(--gray1);\n  border-radius: 0;\n  position: relative;\n  line-height: 17px;\n  white-space: pre-wrap;\n  background: linear-gradient(to top, var(--gray2), var(--gray1) 16px);\n}\n\n.wrapper {\n  overflow: hidden;\n  margin: 0;\n  position: relative;\n  border-radius: 6px;\n  margin-top: 16px;\n  border: 1px solid var(--gray3);\n  padding: 0 !important;\n}\n\n.copyButton {\n  position: absolute;\n  top: 12px;\n  right: 12px;\n  z-index: 1;\n  width: 26px;\n  height: 26px;\n  border: 1px solid var(--gray4);\n  border-radius: 6px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: var(--gray0);\n  cursor: pointer;\n  opacity: 0;\n  color: var(--gray12);\n  transition: background 200ms, box-shadow 200ms, opacity 200ms;\n}\n\n.copyButton:focus-visible {\n  opacity: 1;\n}\n\n.copyButton:hover {\n  background: var(--gray1);\n}\n\n.copyButton:focus-visible {\n  box-shadow: 0 0 0 1px var(--gray4);\n}\n\n.copyButton > div {\n  display: flex;\n}\n\n.outerWrapper {\n  position: relative;\n}\n\n.outerWrapper:hover .copyButton {\n  opacity: 1;\n}\n"
  },
  {
    "path": "website/src/components/CodeBlock/index.tsx",
    "content": "import React from 'react';\nimport Highlight, { defaultProps } from 'prism-react-renderer';\nimport useMeasure from 'react-use-measure';\nimport copy from 'copy-to-clipboard';\nimport { AnimatePresence, motion, MotionConfig } from 'framer-motion';\n\nimport styles from './code-block.module.css';\n\nconst variants = {\n  visible: { opacity: 1, scale: 1 },\n  hidden: { opacity: 0, scale: 0.5 },\n};\n\nconst theme = {\n  plain: {\n    color: 'var(--gray12)',\n    fontSize: 12,\n    fontFamily: 'var(--font-mono)',\n  },\n  styles: [\n    {\n      types: ['comment'],\n      style: {\n        color: 'var(--gray9)',\n      },\n    },\n    {\n      types: ['atrule', 'keyword', 'attr-name', 'selector', 'string'],\n      style: {\n        color: 'var(--gray11)',\n      },\n    },\n    {\n      types: ['punctuation', 'operator'],\n      style: {\n        color: 'var(--gray9)',\n      },\n    },\n    {\n      types: ['class-name', 'function', 'tag'],\n      style: {\n        color: 'var(--gray12)',\n      },\n    },\n  ],\n};\n\nexport const CodeBlock = ({ children, initialHeight = 0 }: { children: string; initialHeight?: number }) => {\n  const [ref, bounds] = useMeasure();\n  const [copying, setCopying] = React.useState<number>(0);\n\n  const onCopy = React.useCallback(() => {\n    copy(children);\n    setCopying((c) => c + 1);\n    setTimeout(() => {\n      setCopying((c) => c - 1);\n    }, 2000);\n  }, [children]);\n\n  return (\n    <div className={styles.outerWrapper}>\n      <button className={styles.copyButton} onClick={onCopy} aria-label=\"Copy code\">\n        <MotionConfig transition={{ duration: 0.15 }}>\n          <AnimatePresence initial={false} mode=\"wait\">\n            {copying ? (\n              <motion.div animate=\"visible\" exit=\"hidden\" initial=\"hidden\" key=\"check\" variants={variants}>\n                <svg\n                  viewBox=\"0 0 24 24\"\n                  width=\"14\"\n                  height=\"14\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  fill=\"none\"\n                  shapeRendering=\"geometricPrecision\"\n                >\n                  <path d=\"M20 6L9 17l-5-5\"></path>\n                </svg>\n              </motion.div>\n            ) : (\n              <motion.div animate=\"visible\" exit=\"hidden\" initial=\"hidden\" key=\"copy\" variants={variants}>\n                <svg\n                  viewBox=\"0 0 24 24\"\n                  width=\"14\"\n                  height=\"14\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  fill=\"none\"\n                  shapeRendering=\"geometricPrecision\"\n                >\n                  <path d=\"M8 17.929H6c-1.105 0-2-.912-2-2.036V5.036C4 3.91 4.895 3 6 3h8c1.105 0 2 .911 2 2.036v1.866m-6 .17h8c1.105 0 2 .91 2 2.035v10.857C20 21.09 19.105 22 18 22h-8c-1.105 0-2-.911-2-2.036V9.107c0-1.124.895-2.036 2-2.036z\"></path>\n                </svg>\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </MotionConfig>\n      </button>\n      {/* @ts-ignore */}\n      <Highlight {...defaultProps} theme={theme} code={children} language=\"jsx\">\n        {({ className, tokens, getLineProps, getTokenProps }) => (\n          <motion.pre\n            className={styles.wrapper}\n            animate={{ height: bounds.height || initialHeight }}\n            transition={{ type: 'easeOut', duration: 0.2 }}\n          >\n            <div className={`${className} ${styles.root}`} ref={ref}>\n              <div />\n              {tokens.map((line, i) => {\n                const { key: lineKey, ...rest } = getLineProps({ line, key: i });\n                return (\n                  <div key={lineKey} {...rest}>\n                    {line.map((token, key) => {\n                      const { key: tokenKey, ...rest } = getTokenProps({ token, key });\n                      return <span key={tokenKey} {...rest} />;\n                    })}\n                  </div>\n                );\n              })}\n            </div>\n          </motion.pre>\n        )}\n      </Highlight>\n    </div>\n  );\n};\n"
  },
  {
    "path": "website/src/components/ExpandModes/index.tsx",
    "content": "import { toast } from 'sonner';\nimport { CodeBlock } from '../CodeBlock';\n\nexport const ExpandModes = ({\n  expand,\n  setExpand,\n}: {\n  expand: boolean;\n  setExpand: React.Dispatch<React.SetStateAction<boolean>>;\n}) => {\n  return (\n    <div>\n      <h2>Expand</h2>\n      <p>\n        You can change the amount of toasts visible through the <code>visibleToasts</code> prop.\n      </p>\n      <div className=\"buttons\">\n        <button\n          data-active={expand}\n          className=\"button\"\n          onClick={() => {\n            toast('Event has been created', {\n              description: 'Monday, January 3rd at 6:00pm',\n            });\n            setExpand(true);\n          }}\n        >\n          Expand\n        </button>\n        <button\n          data-active={!expand}\n          className=\"button\"\n          onClick={() => {\n            toast('Event has been created', {\n              description: 'Monday, January 3rd at 6:00pm',\n            });\n            setExpand(false);\n          }}\n        >\n          Default\n        </button>\n      </div>\n      <CodeBlock>{`<Toaster expand={${expand}} />`}</CodeBlock>\n    </div>\n  );\n};\n"
  },
  {
    "path": "website/src/components/Footer/footer.module.css",
    "content": ".wrapper {\n  padding: 32px 0;\n  border-top: 1px solid var(--gray3);\n  background: var(--gray1);\n  margin-top: 164px;\n}\n\n.p {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin: 0;\n  font-size: 14px;\n}\n\n.p img {\n  border-radius: 50%;\n}\n\n.p a {\n  font-weight: 600;\n  color: inherit;\n  text-decoration: none;\n}\n\n.p a:hover {\n  text-decoration: underline;\n}\n\n@media (max-width: 600px) {\n  .wrapper {\n    margin-top: 128px;\n    padding: 16px 0;\n  }\n}\n"
  },
  {
    "path": "website/src/components/Footer/index.tsx",
    "content": "import Image from 'next/image';\nimport emil from 'public/emil.jpeg';\nimport styles from './footer.module.css';\n\nexport const Footer = () => {\n  return (\n    <footer className={styles.wrapper}>\n      <div className=\"container\">\n        <p className={styles.p}>\n          <Image alt=\"Emil's profile picture\" src={emil} height={24} width={24} />\n          <span>\n            Made by{' '}\n            <a href=\"https://twitter.com/emilkowalski_\" target=\"_blank\">\n              Emil.\n            </a>\n          </span>\n        </p>\n      </div>\n    </footer>\n  );\n};\n"
  },
  {
    "path": "website/src/components/Head/index.tsx",
    "content": "import NextHead from 'next/head';\n\nconst ogImage = 'https://sonner.emilkowal.ski/og.png';\n\nconst Head = () => (\n  <NextHead>\n    {/* Title */}\n    <title>Sonner</title>\n    <meta name=\"og:title\" content=\"Sonner\" />\n\n    {/* Description */}\n    <meta name=\"description\" content=\"An opinionated toast component for React.\" />\n    <meta name=\"og:description\" content=\"An opinionated toast component for React.\" />\n\n    {/* Image */}\n    <meta name=\"twitter:image\" content={ogImage} />\n    <meta name=\"og:image\" content={ogImage} />\n\n    {/* URL */}\n    <meta name=\"og:url\" content=\"https://sonner.emilkowal.ski/\" />\n\n    {/* General */}\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta httpEquiv=\"Content-Language\" content=\"en\" />\n    <meta name=\"twitter:card\" content=\"summary_large_image\" />\n    <meta name=\"twitter:site\" content=\"@emilkowalski_\" />\n    <meta name=\"author\" content=\"Emil Kowalski\" />\n\n    {/* Favicons */}\n    <meta name=\"msapplication-TileColor\" content=\"#ffffff\" />\n    <meta name=\"theme-color\" content=\"#ffffff\" />\n    <link rel=\"shortcut icon\" href=\"favicon.ico\" />\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"apple-touch-icon.png\" />\n  </NextHead>\n);\n\nexport default Head;\n"
  },
  {
    "path": "website/src/components/Hero/hero.module.css",
    "content": ".wrapper {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n  align-items: center;\n}\n\n.toastWrapper {\n  display: flex;\n  flex-direction: column;\n  margin: 0 auto;\n  height: 100px;\n  width: 400px;\n  position: relative;\n  mask-image: linear-gradient(to top, transparent 0%, black 35%);\n  opacity: 1;\n}\n\n.toast {\n  width: 356px;\n  height: 40px;\n  background: var(--gray0);\n  box-shadow: 0 4px 12px #0000001a;\n  border: 1px solid var(--gray3);\n  border-radius: 6px;\n  position: absolute;\n  bottom: 0;\n  left: 50%;\n  transform: translateX(-50%);\n}\n\n.toast:nth-child(1) {\n  transform: translateY(-60%) translateX(-50%) scale(0.9);\n}\n\n.toast:nth-child(2) {\n  transform: translateY(-30%) translateX(-50%) scale(0.95);\n}\n\n.buttons {\n  display: flex;\n  gap: 8px;\n}\n\n.button {\n  height: 40px;\n  border-radius: 6px;\n  border: none;\n  background: linear-gradient(156deg, rgba(255, 255, 255, 1) 0%, rgba(240, 240, 240, 1) 100%);\n  padding: 0 30px;\n  font-weight: 600;\n  flex-shrink: 0;\n  font-family: inherit;\n  box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.06), 0px 1px 0px 0px rgba(0, 0, 0, 0.08),\n    0px 2px 2px 0px rgba(0, 0, 0, 0.04), 0px 3px 3px 0px rgba(0, 0, 0, 0.02), 0px 4px 4px 0px rgba(0, 0, 0, 0.01);\n  position: relative;\n  overflow: hidden;\n  cursor: pointer;\n  text-decoration: none;\n  color: hsl(0, 0%, 9%);\n  font-size: 13px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  transition: box-shadow 200ms, background 200ms;\n  width: 152px;\n}\n\n.button[data-primary] {\n  box-shadow: 0px 0px 0px 1px var(--gray12);\n  background: var(--gray12);\n  color: var(--gray1);\n}\n.button:focus-visible {\n  outline: none;\n  box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.06), 0px 1px 0px 0px rgba(0, 0, 0, 0.08),\n    0px 2px 2px 0px rgba(0, 0, 0, 0.04), 0px 3px 3px 0px rgba(0, 0, 0, 0.02), 0px 4px 4px 0px rgba(0, 0, 0, 0.01),\n    0 0 0 2px rgba(0, 0, 0, 0.15);\n}\n\n.button:after {\n  content: '';\n  position: absolute;\n  top: 100%;\n  background: blue;\n  left: 0;\n  width: 100%;\n  height: 35%;\n  background: linear-gradient(\n    to top,\n    hsl(0, 0%, 91%) 0%,\n    hsla(0, 0%, 91%, 0.987) 8.1%,\n    hsla(0, 0%, 91%, 0.951) 15.5%,\n    hsla(0, 0%, 91%, 0.896) 22.5%,\n    hsla(0, 0%, 91%, 0.825) 29%,\n    hsla(0, 0%, 91%, 0.741) 35.3%,\n    hsla(0, 0%, 91%, 0.648) 41.2%,\n    hsla(0, 0%, 91%, 0.55) 47.1%,\n    hsla(0, 0%, 91%, 0.45) 52.9%,\n    hsla(0, 0%, 91%, 0.352) 58.8%,\n    hsla(0, 0%, 91%, 0.259) 64.7%,\n    hsla(0, 0%, 91%, 0.175) 71%,\n    hsla(0, 0%, 91%, 0.104) 77.5%,\n    hsla(0, 0%, 91%, 0.049) 84.5%,\n    hsla(0, 0%, 91%, 0.013) 91.9%,\n    hsla(0, 0%, 91%, 0) 100%\n  );\n  opacity: 0.6;\n  transition: transform 200ms;\n}\n\n.button:hover:not([data-primary]):after {\n  transform: translateY(-100%);\n}\n\n.button[data-primary]:hover {\n  background: var(--hover);\n}\n\n.heading {\n  font-size: 48px;\n  font-weight: 700;\n  margin: -20px 0 12px;\n}\n\n.wrapper p {\n  margin-bottom: 12px;\n}\n\n@media (max-width: 600px) {\n  .toastWrapper {\n    width: 100%;\n  }\n}\n\n.link {\n  color: var(--gray11) !important;\n  font-size: 14px;\n  text-decoration: underline;\n}\n"
  },
  {
    "path": "website/src/components/Hero/index.tsx",
    "content": "import { toast } from 'sonner';\n\nimport styles from './hero.module.css';\nimport Link from 'next/link';\n\nexport const Hero = () => {\n  return (\n    <div className={styles.wrapper}>\n      <div className={styles.toastWrapper}>\n        <div className={styles.toast} />\n        <div className={styles.toast} />\n        <div className={styles.toast} />\n      </div>\n      <h1 className={styles.heading}>Sonner</h1>\n      <p style={{ marginTop: 0, fontSize: 18, textAlign: 'center' }}>An opinionated toast component for React.</p>\n      <div className={styles.buttons}>\n        <button\n          data-primary=\"\"\n          onClick={() => {\n            toast('Sonner', {\n              description: 'An opinionated toast component for React.',\n            });\n          }}\n          className={styles.button}\n        >\n          Render a toast\n        </button>\n        <a className={styles.button} href=\"https://github.com/emilkowalski/sonner\" target=\"_blank\">\n          GitHub\n        </a>\n      </div>\n      <Link href=\"/getting-started\" className={styles.link}>\n        Documentation\n      </Link>\n    </div>\n  );\n};\n"
  },
  {
    "path": "website/src/components/How/How.tsx",
    "content": "import React from 'react';\n\nexport const How = () => {\n  return (\n    <>\n      <div>\n        <h2>Want to learn how to make components like this one?</h2>\n        <p>\n          I created an animations course in which I share everything I know about motion on the web. You can check it\n          out{' '}\n          <a href=\"https://animations.dev/\" target=\"_blank\" style={{ textDecoration: 'underline' }}>\n            here\n          </a>\n          .\n        </p>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "website/src/components/Installation/index.tsx",
    "content": "'use client';\n\nimport React from 'react';\nimport copy from 'copy-to-clipboard';\nimport { motion, AnimatePresence, MotionConfig } from 'framer-motion';\n\nimport styles from './installation.module.css';\n\nconst variants = {\n  visible: { opacity: 1, scale: 1 },\n  hidden: { opacity: 0, scale: 0.5 },\n};\n\nexport const Installation = () => {\n  const [copying, setCopying] = React.useState(0);\n\n  const onCopy = React.useCallback(() => {\n    copy('npm install sonner');\n    setCopying((c) => c + 1);\n    setTimeout(() => {\n      setCopying((c) => c - 1);\n    }, 2000);\n  }, []);\n\n  return (\n    <div>\n      <h2>Installation</h2>\n      <code className={styles.code} onClick={onCopy}>\n        npm install sonner{' '}\n        <button aria-label=\"Copy code\" className={styles.copy}>\n          <MotionConfig transition={{ duration: 0.15 }}>\n            <AnimatePresence initial={false} mode=\"wait\">\n              {copying ? (\n                <motion.div animate=\"visible\" exit=\"hidden\" initial=\"hidden\" key=\"check\" variants={variants}>\n                  <svg\n                    viewBox=\"0 0 24 24\"\n                    width=\"14\"\n                    height=\"14\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"1.5\"\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    fill=\"none\"\n                    shapeRendering=\"geometricPrecision\"\n                  >\n                    <path d=\"M20 6L9 17l-5-5\"></path>\n                  </svg>\n                </motion.div>\n              ) : (\n                <motion.div animate=\"visible\" exit=\"hidden\" initial=\"hidden\" key=\"copy\" variants={variants}>\n                  <svg\n                    viewBox=\"0 0 24 24\"\n                    width=\"14\"\n                    height=\"14\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"1.5\"\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    fill=\"none\"\n                    shapeRendering=\"geometricPrecision\"\n                  >\n                    <path d=\"M8 17.929H6c-1.105 0-2-.912-2-2.036V5.036C4 3.91 4.895 3 6 3h8c1.105 0 2 .911 2 2.036v1.866m-6 .17h8c1.105 0 2 .91 2 2.035v10.857C20 21.09 19.105 22 18 22h-8c-1.105 0-2-.911-2-2.036V9.107c0-1.124.895-2.036 2-2.036z\"></path>\n                  </svg>\n                </motion.div>\n              )}\n            </AnimatePresence>\n          </MotionConfig>\n        </button>\n      </code>\n    </div>\n  );\n};\n"
  },
  {
    "path": "website/src/components/Installation/installation.module.css",
    "content": ".code {\n  padding: 0 62px 0 12px;\n  border-radius: 6px;\n  background: linear-gradient(to top, var(--gray2), var(--gray1) 8px);\n  font-family: var(--font-mono);\n  font-size: 14px;\n  position: relative;\n  cursor: copy;\n  height: 40px;\n  border: 1px solid var(--gray3);\n  display: flex;\n  align-items: center;\n  color: var(--gray12);\n}\n\n.copy {\n  position: absolute;\n  right: 6px;\n  top: 50%;\n  transform: translateY(-50%);\n  cursor: pointer;\n  border-radius: 50%;\n  border: none;\n  border: 1px solid var(--gray4);\n  background: #fff;\n  color: var(--gray12);\n  border-radius: 5px;\n  width: 26px;\n  height: 26px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n.copy div {\n  display: flex;\n}\n"
  },
  {
    "path": "website/src/components/Other/Other.tsx",
    "content": "import React from 'react';\nimport { useMemo } from 'react';\nimport { toast } from 'sonner';\nimport { CodeBlock } from '../CodeBlock';\nimport styles from './other.module.css';\n\nexport const Other = ({\n  setRichColors,\n  setCloseButton,\n}: {\n  setRichColors: React.Dispatch<React.SetStateAction<boolean>>;\n  setCloseButton: React.Dispatch<React.SetStateAction<boolean>>;\n}) => {\n  const allTypes = useMemo(\n    () => [\n      {\n        name: 'Rich Colors Success',\n        snippet: `toast.success('Event has been created')`,\n        action: () => {\n          toast.success('Event has been created');\n          setRichColors(true);\n        },\n      },\n      {\n        name: 'Rich Colors Error',\n        snippet: `toast.error('Event has not been created')`,\n        action: () => {\n          toast.error('Event has not been created');\n          setRichColors(true);\n        },\n      },\n      {\n        name: 'Rich Colors Info',\n        snippet: `toast.info('Be at the area 10 minutes before the event time')`,\n        action: () => {\n          toast.info('Be at the area 10 minutes before the event time');\n          setRichColors(true);\n        },\n      },\n      {\n        name: 'Rich Colors Warning',\n        snippet: `toast.warning('Event start time cannot be earlier than 8am')`,\n        action: () => {\n          toast.warning('Event start time cannot be earlier than 8am');\n          setRichColors(true);\n        },\n      },\n      {\n        name: 'Close Button',\n        snippet: `toast('Event has been created', {\n  description: 'Monday, January 3rd at 6:00pm',\n})`,\n        action: () => {\n          toast('Event has been created', {\n            description: 'Monday, January 3rd at 6:00pm',\n          });\n          setCloseButton((t) => !t);\n        },\n      },\n      {\n        name: 'Headless',\n        snippet: `toast.custom((t) => (\n  <div>\n    <h1>Custom toast</h1>\n     <button onClick={() => toast.dismiss(t)}>Dismiss</button>\n  </div>\n));`,\n        action: () => {\n          toast.custom(\n            (t) => (\n              <div className={styles.headless}>\n                <p className={styles.headlessTitle}>Event Created</p>\n                <p className={styles.headlessDescription}>Today at 4:00pm - &quot;Louvre Museum&quot;</p>\n                <button className={styles.headlessClose} onClick={() => toast.dismiss(t)}>\n                  <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n                    <path d=\"M2.96967 2.96967C3.26256 2.67678 3.73744 2.67678 4.03033 2.96967L8 6.939L11.9697 2.96967C12.2626 2.67678 12.7374 2.67678 13.0303 2.96967C13.3232 3.26256 13.3232 3.73744 13.0303 4.03033L9.061 8L13.0303 11.9697C13.2966 12.2359 13.3208 12.6526 13.1029 12.9462L13.0303 13.0303C12.7374 13.3232 12.2626 13.3232 11.9697 13.0303L8 9.061L4.03033 13.0303C3.73744 13.3232 3.26256 13.3232 2.96967 13.0303C2.67678 12.7374 2.67678 12.2626 2.96967 11.9697L6.939 8L2.96967 4.03033C2.7034 3.76406 2.6792 3.3474 2.89705 3.05379L2.96967 2.96967Z\"></path>\n                  </svg>\n                </button>\n              </div>\n            ),\n            { duration: 999999 },\n          );\n          setCloseButton((t) => !t);\n        },\n      },\n    ],\n    [setRichColors],\n  );\n\n  const [activeType, setActiveType] = React.useState(allTypes[0]);\n\n  const richColorsActive = activeType?.name?.includes('Rich');\n  const closeButtonActive = activeType?.name?.includes('Close');\n\n  return (\n    <div>\n      <h2>Other</h2>\n      <div className=\"buttons\">\n        {allTypes.map((type) => (\n          <button\n            className=\"button\"\n            onClick={() => {\n              type.action();\n              setActiveType(type);\n            }}\n            key={type.name}\n          >\n            {type.name}\n          </button>\n        ))}\n      </div>\n      <CodeBlock>\n        {`${activeType.snippet || ''}\n\n// ...\n\n<Toaster ${richColorsActive ? 'richColors ' : ''} ${closeButtonActive ? 'closeButton ' : ''}/>`}\n      </CodeBlock>\n    </div>\n  );\n};\n"
  },
  {
    "path": "website/src/components/Other/other.module.css",
    "content": "ol[dir='ltr'] .headlessClose {\n  --headless-close-start: unset;\n  --headless-close-end: 6px;\n}\n\nol[dir='rtl'] .headlessClose {\n  --headless-close-start: 6px;\n  --headless-close-end: unset;\n}\n\n.headless {\n  padding: 16px;\n  width: 356px;\n  box-sizing: border-box;\n  border-radius: 8px;\n  background: var(--gray1);\n  border: 1px solid var(--gray4);\n  position: relative;\n}\n\n.headless .headlessDescription {\n  margin: 0;\n  color: var(--gray10);\n  font-size: 14px;\n  line-height: 1;\n}\n\n.headless .headlessTitle {\n  font-size: 14px;\n  margin: 0 0 8px;\n  color: var(--gray12);\n  font-weight: 500;\n  line-height: 1;\n}\n\n.headlessClose {\n  position: absolute;\n  cursor: pointer;\n  top: 6px;\n  height: 24px;\n  width: 24px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  left: var(--headless-close-start);\n  right: var(--headless-close-end);\n  color: var(--gray10);\n  padding: 0;\n  background: transparent;\n  border: none;\n  transition: color 200ms;\n}\n\n.headlessClose:hover {\n  color: var(--gray12);\n}\n"
  },
  {
    "path": "website/src/components/Position/index.tsx",
    "content": "import { toast, useSonner } from 'sonner';\nimport { CodeBlock } from '../CodeBlock';\nimport React from 'react';\n\nconst positions = ['top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'] as const;\n\nexport type Position = (typeof positions)[number];\n\nexport const Position = ({\n  position: activePosition,\n  setPosition,\n}: {\n  position: Position;\n  setPosition: React.Dispatch<React.SetStateAction<Position>>;\n}) => {\n  const { toasts } = useSonner();\n\n  function removeAllToasts() {\n    toasts.forEach((t) => toast.dismiss(t.id));\n  }\n\n  return (\n    <div>\n      <h2>Position</h2>\n      <p>Swipe direction changes depending on the position.</p>\n      <div className=\"buttons\">\n        {positions.map((position) => (\n          <button\n            data-active={activePosition === position}\n            className=\"button\"\n            onClick={() => {\n              if (activePosition !== position) {\n                setPosition(position);\n                removeAllToasts();\n              }\n\n              toast('Event has been created', {\n                description: 'Monday, January 3rd at 6:00pm',\n              });\n            }}\n            key={position}\n          >\n            {position}\n          </button>\n        ))}\n      </div>\n      <CodeBlock>{`<Toaster position=\"${activePosition}\" />`}</CodeBlock>\n    </div>\n  );\n};\n"
  },
  {
    "path": "website/src/components/Types/Types.tsx",
    "content": "import React from 'react';\nimport { toast } from 'sonner';\nimport { CodeBlock } from '../CodeBlock';\n\nconst promiseCode = '`${data.name} toast has been added`';\n\nexport const Types = () => {\n  const [activeType, setActiveType] = React.useState(allTypes[0]);\n\n  return (\n    <div>\n      <h2>Types</h2>\n      <p>You can customize the type of toast you want to render, and pass an options object as the second argument.</p>\n      <div className=\"buttons\">\n        {allTypes.map((type) => (\n          <button\n            className=\"button\"\n            data-active={activeType.name === type.name}\n            onClick={() => {\n              type.action();\n              setActiveType(type);\n            }}\n            key={type.name}\n          >\n            {type.name}\n          </button>\n        ))}\n      </div>\n      <CodeBlock>{`${activeType.snippet}`}</CodeBlock>\n    </div>\n  );\n};\n\nconst allTypes = [\n  {\n    name: 'Default',\n    snippet: `toast('Event has been created')`,\n    action: () => toast('Event has been created'),\n  },\n  {\n    name: 'Description',\n    snippet: `toast.message('Event has been created', {\n  description: 'Monday, January 3rd at 6:00pm',\n})`,\n    action: () =>\n      toast('Event has been created', {\n        description: 'Monday, January 3rd at 6:00pm',\n      }),\n  },\n  {\n    name: 'Success',\n    snippet: `toast.success('Event has been created')`,\n    action: () => toast.success('Event has been created'),\n  },\n  {\n    name: 'Info',\n    snippet: `toast.info('Be at the area 10 minutes before the event time')`,\n    action: () => toast.info('Be at the area 10 minutes before the event time'),\n  },\n  {\n    name: 'Warning',\n    snippet: `toast.warning('Event start time cannot be earlier than 8am')`,\n    action: () => toast.warning('Event start time cannot be earlier than 8am'),\n  },\n  {\n    name: 'Error',\n    snippet: `toast.error('Event has not been created')`,\n    action: () => toast.error('Event has not been created'),\n  },\n  {\n    name: 'Action',\n    snippet: `toast('Event has been created', {\n  action: {\n    label: 'Undo',\n    onClick: () => console.log('Undo')\n  },\n})`,\n    action: () =>\n      toast.message('Event has been created', {\n        action: {\n          label: 'Undo',\n          onClick: () => console.log('Undo'),\n        },\n      }),\n  },\n  {\n    name: 'Promise',\n    snippet: `const promise = () => new Promise((resolve) => setTimeout(() => resolve({ name: 'Sonner' }), 2000));\n\ntoast.promise(promise, {\n  loading: 'Loading...',\n  success: (data) => {\n    return ${promiseCode};\n  },\n  error: 'Error',\n});`,\n    action: () =>\n      toast.promise<{ name: string }>(\n        () =>\n          new Promise((resolve) => {\n            setTimeout(() => {\n              resolve({ name: 'Sonner' });\n            }, 2000);\n          }),\n        {\n          loading: 'Loading...',\n          success: (data) => {\n            return `${data.name} toast has been added`;\n          },\n          error: 'Error',\n        },\n      ),\n  },\n  {\n    name: 'Custom',\n    snippet: `toast(<div>A custom toast with default styling</div>)`,\n    action: () => toast(<div>A custom toast with default styling</div>, { duration: 1000000 }),\n  },\n];\n"
  },
  {
    "path": "website/src/components/Usage/index.tsx",
    "content": "import { CodeBlock } from '../CodeBlock';\n\nexport const Usage = () => {\n  return (\n    <div>\n      <h2>Usage</h2>\n      <p>Render the toaster in the root of your app.</p>\n      <CodeBlock initialHeight={270}>{`import { Toaster, toast } from 'sonner'\n\n// ...\n\nfunction App() {\n  return (\n    <div>\n      <Toaster />\n      <button onClick={() => toast('My first toast')}>\n        Give me a toast\n      </button>\n    </div>\n  )\n}`}</CodeBlock>\n    </div>\n  );\n};\n"
  },
  {
    "path": "website/src/globals.css",
    "content": ":root,\n.light {\n  --gray0: #fff;\n  --gray1: hsl(0, 0%, 99%);\n  --gray2: hsl(0, 0%, 97.3%);\n  --gray3: hsl(0, 0%, 95.1%);\n  --gray4: hsl(0, 0%, 93%);\n  --gray5: hsl(0, 0%, 90.9%);\n  --gray6: hsl(0, 0%, 88.7%);\n  --gray7: hsl(0, 0%, 85.8%);\n  --gray8: hsl(0, 0%, 78%);\n  --gray9: hsl(0, 0%, 56.1%);\n  --gray10: hsl(0, 0%, 52.3%);\n  --gray11: hsl(0, 0%, 43.5%);\n  --gray12: hsl(0, 0%, 9%);\n  --hover: rgb(40, 40, 40);\n  --border-radius: 6px;\n  --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial,\n    Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;\n  --font-mono: 'SF Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;\n}\n\n.dark {\n  --gray0: #000;\n  --gray1: hsl(0, 0%, 9.5%);\n  --gray2: hsl(0, 0%, 10.5%);\n  --gray3: hsl(0, 0%, 15.8%);\n  --gray4: hsl(0, 0%, 18.9%);\n  --gray5: hsl(0, 0%, 21.7%);\n  --gray6: hsl(0, 0%, 24.7%);\n  --gray7: hsl(0, 0%, 29.1%);\n  --gray8: hsl(0, 0%, 37.5%);\n  --gray9: hsl(0, 0%, 43%);\n  --gray10: hsl(0, 0%, 50.7%);\n  --gray11: hsl(0, 0%, 69.5%);\n  --gray12: hsl(0, 0%, 93.5%);\n}\n\n::selection {\n  background: var(--gray7);\n}\n\n.container {\n  max-width: 642px;\n  margin: 0 auto;\n  padding-left: max(var(--side-padding), env(safe-area-inset-left));\n  padding-right: max(var(--side-padding), env(safe-area-inset-right));\n}\n\n.wrapper {\n  --side-padding: 16px;\n  background: var(--gray0);\n  margin: 0;\n  padding: 0;\n  padding-top: 100px;\n  font-family: var(--font-sans);\n  -webkit-font-smoothing: antialiased;\n}\n\n/* Disable double-tap zoom */\n* {\n  touch-action: manipulation;\n}\n\nh1,\np {\n  color: var(--gray12);\n}\n\nh2 {\n  font-size: 16px;\n  color: var(--gray12);\n  font-weight: 500;\n}\n\nh2 + p {\n  margin-top: -4px;\n}\n\np {\n  font-size: 16px;\n}\n\na {\n  color: inherit;\n  text-decoration-color: var(--gray10);\n  text-underline-position: from-font;\n}\n\ncode {\n  font-size: 13px;\n  line-height: 28px;\n  padding: 2px 3.6px;\n  border: 1px solid var(--gray3);\n  background: var(--gray4);\n  font-family: var(--font-mono);\n  border-radius: 6px;\n}\n\n.content {\n  display: flex;\n  flex-direction: column;\n  gap: 48px;\n  margin-top: 96px;\n}\n\n.buttons {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 8px;\n  overflow: auto;\n  margin: 0 calc(-1 * var(--side-padding));\n  padding: 4px var(--side-padding);\n  position: relative;\n}\n\n.button {\n  padding: 8px 12px;\n  margin: 0;\n  background: var(--gray1);\n  border: 1px solid var(--gray3);\n  white-space: nowrap;\n  border-radius: 6px;\n  font-size: 13px;\n  font-weight: 500;\n  font-family: var(--font-sans);\n  cursor: pointer;\n  color: var(--gray12);\n  transition: border-color 200ms, background 200ms, box-shadow 200ms;\n}\n\n.button:hover {\n  background: var(--gray2);\n  border-color: var(--gray4);\n}\n\n.button[data-active='true'] {\n  background: var(--gray3);\n  border-color: var(--gray7);\n}\n\n.button:focus-visible {\n  outline: none;\n  box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.06), 0px 1px 0px 0px rgba(0, 0, 0, 0.08),\n    0px 2px 2px 0px rgba(0, 0, 0, 0.04), 0px 3px 3px 0px rgba(0, 0, 0, 0.02), 0px 4px 4px 0px rgba(0, 0, 0, 0.01),\n    0 0 0 2px rgba(0, 0, 0, 0.15);\n}\n\n@media (max-width: 600px) {\n  .buttons {\n    mask-image: linear-gradient(to right, transparent, black 16px, black calc(100% - 16px), transparent);\n  }\n}\n\n.wrapper h1,\n.wrapper p {\n  color: var(--gray12);\n  line-height: 25px;\n}\nm .wrapper h2 {\n  font-size: 16px;\n  color: var(--gray12);\n  font-weight: 500;\n}\n\n.wrapper h2 + p {\n  margin-top: -4px;\n}\n\n.wrapper h2 {\n  margin: 12px 0;\n}\n\n.wrapper p {\n  font-size: 16px;\n  margin-bottom: 16px;\n}\n\n.wrapper a {\n  text-decoration-color: var(--gray10);\n  text-underline-position: from-font;\n}\n\n.wrapper .content {\n  display: flex;\n  flex-direction: column;\n  gap: 48px;\n  margin-top: 96px;\n}\n\n.wrapper footer {\n  padding: 0;\n}\n\n.wrapper footer .container {\n  padding: 32px 16px !important;\n}\n\n.wrapper footer p {\n  margin: 0;\n  font-size: 14px;\n}\n\nfooter {\n  background: var(--gray1) !important;\n}\n\nhr {\n  background: var(--gray3) !important;\n}\n\n.nx-border-primary-500 {\n  border-color: var(--gray12) !important;\n}\n\n.nx-bg-primary-500\\/10 {\n  background: var(--gray3) !important;\n}\n"
  },
  {
    "path": "website/src/pages/_app.tsx",
    "content": "import type { ReactElement } from 'react';\nimport type { AppProps } from 'next/app';\nimport { Analytics } from '@vercel/analytics/react';\nimport '../style.css';\nimport '../globals.css';\n\nexport default function Nextra({ Component, pageProps }: AppProps): ReactElement {\n  return (\n    <>\n      {/* @ts-ignore */}\n      <Component {...pageProps} />\n      <Analytics />\n    </>\n  );\n}\n"
  },
  {
    "path": "website/src/pages/_meta.json",
    "content": "{\n  \"getting-started\": {\n    \"title\": \"Getting Started\",\n    \"href\": \"/getting-started\"\n  },\n  \"-- API\": {\n    \"type\": \"separator\",\n    \"title\": \"API\"\n  },\n  \"toast\": {\n    \"title\": \"toast()\",\n    \"href\": \"/toast\"\n  },\n  \"toaster\": {\n    \"title\": \"Toaster\",\n    \"href\": \"/toaster\"\n  },\n  \"-- More\": {\n    \"type\": \"separator\",\n    \"title\": \"Guides\"\n  },\n  \"styling\": {\n    \"title\": \"Styling\",\n    \"href\": \"/styling\"\n  }\n}\n"
  },
  {
    "path": "website/src/pages/getting-started.mdx",
    "content": "import { Tab, Tabs, Cards, Card, Steps } from 'nextra-theme-docs';\nimport { toast } from 'sonner';\n\n# Getting Started\n\nSonner is an opinionated toast component for React. You can read more about why and how it was built [here](https://emilkowal.ski/ui/building-a-toast-component).\n\n<Steps>\n### Install\n\n<Tabs items={['pnpm', 'npm', 'yarn', 'bun']}>\n  <Tab>\n    ```bash\n    pnpm i sonner\n    ```\n\n  </Tab>\n  <Tab>\n    ```bash\n    npm i sonner\n    ```\n  </Tab>\n  <Tab>\n    ```bash\n    yarn add sonner\n    ```\n  </Tab>\n    <Tab>\n    ```bash\n    bun add sonner\n    ```\n  </Tab>\n</Tabs>\n\n### Add Toaster to your app\n\nIt can be placed anywhere, even in server components such as `layout.tsx`.\n\n```tsx\nimport { Toaster } from 'sonner';\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <body>\n        {children}\n        <Toaster />\n      </body>\n    </html>\n  );\n}\n```\n\n### Render a toast\n\n```tsx\nimport { toast } from 'sonner';\n\nfunction MyToast() {\n  return <button onClick={() => toast('This is a sonner toast')}>Render my toast</button>;\n}\n```\n\n</Steps>\n"
  },
  {
    "path": "website/src/pages/index.tsx",
    "content": "import React, { StrictMode } from 'react';\nimport { Toaster } from 'sonner';\nimport { Installation } from '@/src/components/Installation';\nimport { Hero } from '@/src/components/Hero';\nimport { Types } from '@/src/components/Types/Types';\nimport { ExpandModes } from '@/src/components/ExpandModes';\nimport { Position } from '@/src/components/Position';\nimport { Usage } from '@/src/components/Usage';\nimport { Other } from '@/src/components/Other/Other';\nimport Head from '../components/Head';\nimport { How } from '../components/How/How';\nimport { Footer } from '../components/Footer';\n\nexport default function Home() {\n  const [expand, setExpand] = React.useState(false);\n  const [position, setPosition] = React.useState<Position>('bottom-right');\n  const [richColors, setRichColors] = React.useState(false);\n  const [closeButton, setCloseButton] = React.useState(false);\n\n  return (\n    <div className=\"wrapper light\">\n      <Head />\n      <Toaster\n        theme=\"light\"\n        richColors={richColors}\n        closeButton={closeButton}\n        expand={expand}\n        position={position}\n        duration={Infinity}\n      />\n      <main className=\"container\">\n        <Hero />\n        <div className=\"content\">\n          <Installation />\n          <Usage />\n          <Types />\n          <Position position={position} setPosition={setPosition} />\n          <ExpandModes expand={expand} setExpand={setExpand} />\n          <Other setCloseButton={setCloseButton} setRichColors={setRichColors} />\n          <How />\n        </div>\n      </main>\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "website/src/pages/styling.mdx",
    "content": "# Styling\n\nStyling can be done globally via `toastOptions`, this way every toast will have the same styling.\n\n```jsx\n<Toaster\n  toastOptions={{\n    style: {\n      background: 'red',\n    },\n    className: 'class',\n  }}\n/>\n```\n\nYou can also use the same props when calling `toast` to style a specific toast.\n\n```jsx\ntoast('Hello World', {\n  style: {\n    background: 'red',\n  },\n  className: 'class',\n});\n```\n\n## Tailwind CSS\n\nThe preferred way to style the toasts with tailwind is by using the `unstyled` prop. That will give you an unstyled toast which you can then style with tailwind.\n\n```jsx\n<Toaster\n  toastOptions={{\n    unstyled: true,\n    classNames: {\n      toast: 'bg-blue-400',\n      title: 'text-red-400',\n      description: 'text-red-400',\n      actionButton: 'bg-zinc-400',\n      cancelButton: 'bg-orange-400',\n      closeButton: 'bg-lime-400',\n    },\n  }}\n/>\n```\n\nYou can do the same when calling `toast()`.\n\n```jsx\ntoast('Hello World', {\n  unstyled: true,\n  classNames: {\n    toast: 'bg-blue-400',\n    title: 'text-red-400 text-2xl',\n    description: 'text-red-400',\n    actionButton: 'bg-zinc-400',\n    cancelButton: 'bg-orange-400',\n    closeButton: 'bg-lime-400',\n  },\n});\n```\n\nStyling per toast type is also possible.\n\n```jsx\n<Toaster\n  toastOptions={{\n    unstyled: true,\n    classNames: {\n      error: 'bg-red-400',\n      success: 'text-green-400',\n      warning: 'text-yellow-400',\n      info: 'bg-blue-400',\n    },\n  }}\n/>\n```\n\n## Changing Icons\n\nYou can change the default icons using the `icons` prop:\n\n```jsx\n<Toaster\n  icons={{\n    success: <SuccessIcon />,\n    info: <InfoIcon />,\n    warning: <WarningIcon />,\n    error: <ErrorIcon />,\n    loading: <LoadingIcon />,\n  }}\n/>\n```\n\nYou can also set an icon for each toast:\n\n```jsx\ntoast('Hello World', {\n  icon: <Icon />,\n});\n```\n"
  },
  {
    "path": "website/src/pages/toast.mdx",
    "content": "import { toast } from 'sonner';\n\n# Toast()\n\nUse it to render a toast. You can call it from anywhere, even outside of React.\n\n## Rendering the toast\n\nYou can call it with just a string.\n\n```jsx\nimport { toast } from 'sonner';\n\ntoast('Hello World!');\n```\n\nOr provide an object as the second argument with more options. They will overwrite the options passed to [`<Toaster />`](/toaster) if you have provided any.\n\n```jsx\nimport { toast } from 'sonner';\n\ntoast('My toast', {\n  className: 'my-classname',\n  description: 'My description',\n  duration: 5000,\n  icon: <MyIcon />,\n});\n```\n\n### Render toast on page load\n\nTo render a toast on initial page load it is required that the function `toast()` is called inside of a `setTimeout` or `requestAnimationFrame`.\n\n```jsx\nsetTimeout(() => {\n  toast('My toast on a page load');\n});\n```\n\n## Creating toasts\n\n### Success\n\nRenders a checkmark icon in front of the message.\n\n```jsx\ntoast.success('My success toast');\n```\n\n### Error\n\nRenders an error icon in front of the message.\n\n```jsx\ntoast.error('My error toast');\n```\n\n### Action\n\nRenders a primary button, clicking it will close the toast and run the callback passed via `onClick`. You can prevent the toast from closing by calling `event.preventDefault()` in the `onClick` callback.\n\n```jsx\ntoast('My action toast', {\n  action: {\n    label: 'Action',\n    onClick: () => console.log('Action!'),\n  },\n});\n```\n\nYou can also render jsx as your action.\n\n```jsx\ntoast('My action toast', {\n  action: <Button onClick={() => console.log('Action!')}>Action</Button>,\n});\n```\n\n### Cancel\n\nRenders a secondary button, clicking it will close the toast and run the callback passed via `onClick`.\n\n```jsx\ntoast('My cancel toast', {\n  cancel: {\n    label: 'Cancel',\n    onClick: () => console.log('Cancel!'),\n  },\n});\n```\n\nYou can also render jsx in the cancel option.\n\n```jsx\ntoast('My cancel toast', {\n  cancel: <Button onClick={() => console.log('Cancel!')}>Cancel</Button>,\n});\n```\n\n### Promise\n\nStarts in a loading state and will update automatically after the promise resolves or fails.\nYou can pass a function to the success/error messages to incorporate the result/error of the promise.\n\n```jsx\ntoast.promise(myPromise, {\n  loading: 'Loading...',\n  success: (data) => {\n    return `${data.name} toast has been added`;\n  },\n  error: 'Error',\n});\n```\n\n### Loading\n\nRenders a toast with a loading spinner. Useful when you want to handle various states yourself instead of using a promise toast.\n\n```jsx\ntoast.loading('Loading data');\n```\n\n### Custom\n\nYou can pass jsx as the first argument instead of a string to render a custom toast while maintaining default styling.\n\n```jsx\ntoast(<div>A custom toast with default styling</div>, { duration: 5000 });\n```\n\n### Headless\n\nUse it to render an unstyled toast with custom jsx while maintaining the functionality. This function receives the `Toast` as an argument, giving you access to all properties.\n\n```jsx\ntoast.custom((t) => (\n  <div>\n    This is a custom component <button onClick={() => toast.dismiss(t)}>close</button>\n  </div>\n));\n```\n\n### Dynamic Position\n\nYou can change the position of the toast dynamically by passing a `position` prop to the toast\nfunction. It will not affect the positioning of other toasts.\n\n```jsx\n// Available positions:\n// top-left, top-center, top-right, bottom-left, bottom-center, bottom-right\ntoast('Hello World', {\n  position: 'top-center',\n});\n```\n\n## Other\n\n### Updating toasts\n\nYou can update a toast by using the `toast` function and passing it the id of the toast you want to update, the rest stays the same.\n\n```jsx\nconst toastId = toast('Sonner');\n\ntoast.success('Toast has been updated', {\n  id: toastId,\n});\n```\n\n### On Close Callback\n\nYou can pass `onDismiss` and `onAutoClose` callbacks to each toast. `onDismiss` gets fired when either the close button gets clicked or the toast is swiped. `onAutoClose` fires when the toast disappears automatically after it's timeout (`duration` prop).\n\n```jsx\ntoast('Event has been created', {\n  onDismiss: (t) => console.log(`Toast with id ${t.id} has been dismissed`),\n  onAutoClose: (t) => console.log(`Toast with id ${t.id} has been closed automatically`),\n});\n```\n\n### Persisting toasts\n\nIf you want a toast to stay on screen forever, you can set the `duration` to [`Infinity`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity).\n\n```js\ntoast('This toast will stay on screen forever', {\n  duration: Infinity,\n});\n```\n\n### Dismissing toasts programmatically\n\nTo remove a toast programmatically use `toast.dismiss(id)`. The `toast()` function return the id of the toast.\n\n```jsx\nconst toastId = toast('Event has been created');\n\ntoast.dismiss(toastId);\n```\n\nYou can also dismiss all toasts at once by calling `toast.dismiss()` without an id.\n\n```jsx\ntoast.dismiss();\n```\n\n### Rendering custom elements\n\nYou can render custom elements inside the toast like `<a />` or custom components by passing a function instead of a string. This work for both the title and description.\n\n```jsx\ntoast(\n  () => (\n    <>\n      View{' '}\n      <a href=\"https://google.com\" target=\"_blank\">\n        Animation on the Web\n      </a>\n    </>\n  ),\n  {\n    description: () => <button>This is a button element!</button>,\n  },\n);\n```\n\n### Targeting a specific Toaster\n\nYou can target a specific Toaster by passing a `toasterId` option:\n\n```jsx\n// This toast will only appear in the Toaster with id=\"canvas\"\ntoast('This will show in the canvas Toaster', { toasterId: 'canvas' });\n```\n\n## API Reference\n\n| Property          |                                              Description                                               |        Default |\n| :---------------- | :----------------------------------------------------------------------------------------------------: | -------------: |\n| description       |                           Toast's description, renders underneath the title.                           |            `-` |\n| closeButton       |                                          Adds a close button.                                          |        `false` |\n| invert            |                                Dark toast in light mode and vice versa.                                |        `false` |\n| duration          |            Time in milliseconds that should elapse before automatically closing the toast.             |         `4000` |\n| position          |                                         Position of the toast.                                         | `bottom-right` |\n| dismissible       |                     If `false`, it'll prevent the user from dismissing the toast.                      |         `true` |\n| icon              |                      Icon displayed in front of toast's text, aligned vertically.                      |            `-` |\n| action            |                      Renders a primary button, clicking it will close the toast.                       |            `-` |\n| cancel            |                     Renders a secondary button, clicking it will close the toast.                      |            `-` |\n| id                |                                        Custom id for the toast.                                        |            `-` |\n| onDismiss         |       The function gets called when either the close button is clicked, or the toast is swiped.        |            `-` |\n| onAutoClose       | Function that gets called when the toast disappears automatically after it's timeout (duration` prop). |            `-` |\n| unstyled          |                  Removes the default styling, which allows for easier customization.                   |        `false` |\n| actionButtonStyle |                                      Styles for the action button                                      |           `{}` |\n| cancelButtonStyle |                                      Styles for the cancel button                                      |           `{}` |\n"
  },
  {
    "path": "website/src/pages/toaster.mdx",
    "content": "# Toaster\n\nThis component renders all the toasts, you can place it anywhere in your app.\n\n## Customization\n\nYou can see examples of most of the scenarios described below on the [homepage](/).\n\n### Multiple Toasters\n\nYou can render multiple Toaster components with different ids and target toasts to each one:\n\n```jsx\n<Toaster id=\"global\" position=\"top-right\" />\n<Toaster id=\"canvas\" position=\"bottom-left\" />\n\n<button onClick={() => toast('Global toast', { toasterId: 'global' })}>\n  Show in Global Toaster\n</button>\n<button onClick={() => toast('Canvas toast', { toasterId: 'canvas' })}>\n  Show in Canvas Toaster\n</button>\n```\n\n### Expand\n\nWhen you hover on one of the toasts, they will expand. You can make that the default behavior by setting the `expand` prop to `true`, and customize it even further with the `visibleToasts` prop.\n\n```jsx\n// 9 toasts will be visible instead of the default, which is 3.\n<Toaster expand visibleToasts={9} />\n```\n\n### Position\n\nChanges the place where all toasts will be rendered.\n\n```jsx\n// Available positions:\n// top-left, top-center, top-right, bottom-left, bottom-center, bottom-right\n<Toaster position=\"top-center\" />\n```\n\n### Styling all toasts\n\nYou can customize all toasts at once with `toastOptions` prop. These options will act as the default for all toasts.\n\n```jsx\n<Toaster\n  toastOptions={{\n    style: { background: 'red' },\n    className: 'my-toast',\n  }}\n/>\n```\n\n### dir\n\nChanges the directionality of the toast's text.\n\n```jsx\n// rtl, ltr, auto\n<Toaster dir=\"rtl\" />\n```\n\n### Custom ARIA label\n\nYou can customize the default ARIA label for the notification container and the toast close button.\n\n```jsx\n// example in Finnish\n<Toaster containerAriaLabel=\"Ilmoitukset\" toastOptions={{ closeButtonAriaLabel: 'Sulje' }} />\n```\n\n## API Reference\n\n| Property              |                                                           Description                                                           |        Default |\n| :-------------------- | :-----------------------------------------------------------------------------------------------------------------------------: | -------------: |\n| theme                 |                                       Toast's theme, either `light`, `dark`, or `system`                                        |        `light` |\n| richColors            |                                           Makes error and success state more colorful                                           |        `false` |\n| expand                |                                               Toasts will be expanded by default                                                |        `false` |\n| visibleToasts         |                                                    Amount of visible toasts                                                     |            `3` |\n| position              |                                             Place where the toasts will be rendered                                             | `bottom-right` |\n| closeButton           |                                                Adds a close button to all toasts                                                |        `false` |\n| offset                |                                              Offset from the edges of the screen.                                               |         `32px` |\n| mobileOffset          |                    Offset from the left/right edges of the screen on screens with width smaller than 600px.                     |         `16px` |\n| dir                   |                                                 Directionality of toast's text                                                  |          `ltr` |\n| hotkey                |                                   Keyboard shortcut that will move focus to the toaster area.                                   |    `⌥/alt + T` |\n| invert                |                                            Dark toasts in light mode and vice versa.                                            |        `false` |\n| toastOptions          |               These will act as default options for all toasts. See [toast()](/toast) for all available options.                |         `4000` |\n| gap                   |                                                Gap between toasts when expanded                                                 |           `14` |\n| loadingIcon           |                                                Changes the default loading icon                                                 |            `-` |\n| pauseWhenPageIsHidden | Pauses toast timers when the page is hidden, e.g., when the tab is backgrounded, the browser is minimized, or the OS is locked. |        `false` |\n| icons                 |                                                    Changes the default icons                                                    |            `-` |\n"
  },
  {
    "path": "website/src/style.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n:root {\n  --gray0: #fff;\n  --gray1: hsl(0, 0%, 99%);\n  --gray2: hsl(0, 0%, 97.3%);\n  --gray3: hsl(0, 0%, 95.1%);\n  --gray4: hsl(0, 0%, 93%);\n  --gray5: hsl(0, 0%, 90.9%);\n  --gray6: hsl(0, 0%, 88.7%);\n  --gray7: hsl(0, 0%, 85.8%);\n  --gray8: hsl(0, 0%, 78%);\n  --gray9: hsl(0, 0%, 56.1%);\n  --gray10: hsl(0, 0%, 52.3%);\n  --gray11: hsl(0, 0%, 43.5%);\n  --gray12: hsl(0, 0%, 9%);\n  --hover: rgb(40, 40, 40);\n  --border-radius: 6px;\n  --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial,\n    Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;\n  --font-mono: 'SF Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;\n  --shiki-token-comment: var(--gray11) !important;\n}\n\n.dark {\n  --gray0: #000;\n  --gray1: hsl(0, 0%, 9.5%);\n  --gray2: hsl(0, 0%, 10.5%);\n  --gray3: hsl(0, 0%, 15.8%);\n  --gray4: hsl(0, 0%, 18.9%);\n  --gray5: hsl(0, 0%, 21.7%);\n  --gray6: hsl(0, 0%, 24.7%);\n  --gray7: hsl(0, 0%, 29.1%);\n  --gray8: hsl(0, 0%, 37.5%);\n  --gray9: hsl(0, 0%, 43%);\n  --gray10: hsl(0, 0%, 50.7%);\n  --gray11: hsl(0, 0%, 69.5%);\n  --gray12: hsl(0, 0%, 93.5%);\n}\n\nbody {\n  padding-top: 0;\n}\n\n.button {\n  padding: 8px 12px;\n  margin: 0;\n  background: var(--gray1);\n  border: 1px solid var(--gray3);\n  white-space: nowrap;\n  border-radius: 6px;\n  font-size: 13px;\n  font-weight: 500;\n  font-family: var(--font-sans);\n  cursor: pointer;\n  color: var(--gray12);\n  transition: border-color 200ms, background 200ms, box-shadow 200ms;\n  margin: 1.5rem 0 0;\n}\n\n.button p {\n  line-height: 1.5;\n}\n\n.button:hover {\n  background: var(--gray2);\n  border-color: var(--gray4);\n}\n\n.button[data-active='true'] {\n  background: var(--gray3);\n  border-color: var(--gray7);\n}\n\n.button:focus-visible {\n  outline: none;\n  box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.06), 0px 1px 0px 0px rgba(0, 0, 0, 0.08),\n    0px 2px 2px 0px rgba(0, 0, 0, 0.04), 0px 3px 3px 0px rgba(0, 0, 0, 0.02), 0px 4px 4px 0px rgba(0, 0, 0, 0.01),\n    0 0 0 2px rgba(0, 0, 0, 0.15);\n}\n\n@media (max-width: 600px) {\n  .buttons {\n    mask-image: linear-gradient(to right, transparent, black 16px, black calc(100% - 16px), transparent);\n  }\n}\n\naside li.active a {\n  background: var(--gray3) !important;\n  color: var(--gray12) !important;\n}\n\naside li:not(.active) a:hover {\n  background: var(--gray2) !important;\n}\n\npre {\n  background-color: var(--gray0) !important;\n  border: 1px solid var(--gray4);\n  margin-bottom: 2rem !important;\n}\n\nbutton[title='Copy code'] {\n  background: var(--gray2);\n  color: var(--gray10);\n}\n\nmain > p {\n  line-height: 1.5rem !important;\n  margin-top: 1rem !important;\n}\n\n.nx-text-primary-600 {\n  color: var(--gray12) !important;\n}\n\ndiv > a:hover {\n  color: var(--gray12) !important;\n}\n\np {\n  color: var(--gray12) !important;\n}\n\nfooter > div {\n  padding: 32px 24px !important;\n}\n"
  },
  {
    "path": "website/tailwind.config.js",
    "content": "module.exports = {\n  content: [\n    './app/**/*.{js,ts,jsx,tsx,mdx}',\n    './pages/**/*.{js,ts,jsx,tsx,mdx}',\n    './components/**/*.{js,ts,jsx,tsx,mdx}',\n\n    // Or if using `src` directory:\n    './src/**/*.{js,ts,jsx,tsx,mdx}',\n  ],\n  theme: {\n    extend: {},\n  },\n  plugins: [],\n};\n"
  },
  {
    "path": "website/theme.config.jsx",
    "content": "export default {\n  logo: <span style={{ fontWeight: 600 }}>Sonner</span>,\n  project: {\n    link: 'https://github.com/emilkowalski/sonner',\n  },\n  docsRepositoryBase: 'https://github.com/emilkowalski/sonner/tree/main/website',\n  useNextSeoProps() {\n    return {\n      titleTemplate: '%s – Sonner',\n    };\n  },\n  feedback: {\n    content: null,\n  },\n  footer: {\n    text: (\n      <span>\n        MIT {new Date().getFullYear()} ©{' '}\n        <a href=\"https://sonner.emilkowal.ski\" target=\"_blank\">\n          Sonner\n        </a>\n        .\n      </span>\n    ),\n  },\n  // ... other theme options\n};\n"
  },
  {
    "path": "website/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  }
]