Skip to content

Nyloth9/moon-toast

Repository files navigation

solid-moon-toast

A SolidJS toast library

Check out the Demo

Features

  • Solidjs context api
    • Uses the context api to create its own scope
    • Exposes five methods: notify, dismiss, update, remove and custom
  • Toast features
    • Easily customizable
    • Pass string or JSX as toast body
    • Control the position
    • Update the toast and have the other toasts react to the height changes
    • Custom entrance and exit animations
    • On enter, update and exit callbacks
    • Automatic toast screen overflow prevention
    • Pause on hover / pause on tab change
  • Customizable Progress Bar
    • Customize the look and the position
    • Customize the progress animation
    • Play, pause and reset controls

Quick start

Installation:

npm i solid-moon-toast
# or
yarn add solid-moon-toast
# or
pnpm add solid-moon-toast

Usage:

// App.tsx
import { ToastProvider } from 'solid-moon-toast'
import "solid-moon-toast/dist/index.css";
import ToastsPage from './ToastsPage'

const App = () => {
  return (
    <ToastProvider>
      <ToastsPage />
    </ToastProvider>
  )
}

export default App
// ToastsPage.tsx
import { useToast } from "solid-moon-toast";

const ToastsPage = () => {
  const { notify } = useToast();

  return (
    <div>
      <button onClick={() => notify("🎉 Operation Successful!")}>
    </div>
  )
}

Documentation

Toast options

Toast options are divided into global and per toast options.

Global options:

These settings can be passed as props to the ToastProvider component.

  gutter={16} // distance between the toasts (global only)
  maxToasts={10} // set to 0 if you dont want to limit the number of toasts (global only)
  offsetX={16} // distance from the screen edge on the X axis (global only)
  offsetY={16} // distance from the screen edge on the Y axis (global only)
  positionX="right" // position on the X axis, accepts: left | center | right (global only)
  positionY="top" // position on the Y axis, accepts: top | bottom (global only)
  toasterStyle={{
   "background-color": "blue", // custom style for the toaster (global only)
  }}
  pauseOnTabSwitch={true} // pause the toast timer when switching browser tabs (global only)

Common options:

These settings can be applied either globally on the ToastProvider component (all except the toast type), or as individual toast options. Any setting passed to the individual toast will override the corresponding global setting.

  type="success" // type of the toast, accepts: 'success' | 'error' | 'loading'
  class={{ className: "my-toast-class", replaceDefault: true }} // custom toast class, choose to replace default or not
  style={{ "background-color": "blue" }} // custom style for the toast
  dismissButton={{
   className: "my-dismiss-class", // custom class for dismiss button
   show: true, // show dismiss button
   style: { color: "red" }, // custom style for dismiss button
   type: "floating", // floating or inline
  }}
  dissmisOnClick={false} // dismiss toast when clicking on the body
  duration="infinite" // duration in ms or infinite
  enterCallback={() => null} // pass a function that will be called on toast enter
  updateCallback={() => null} // pass a function that will be called on toast update
  exitCallback={() => null} // pass a function that will be called on toast exit
  onEnter="my-entrance-animation" // class that will be applied to the toast container on enter
  onIdle="my-idle-animation" // class that will be applied to the toast container when idle
  onExit="my-exit-animation" // class that will be applied to the toast container on exit
  enterDuration={500} // entrance duration; should be the same as the duration of the entrance animation
  exitDuration={500} // entrance duration; should be the same as the duration of the exit animation
  pauseOnHover={true} // pause the toast timer when hovering over it
  progressBar={{
   showDefault: true, // show the default progress bar
   className: "my-progress-bar-class", // custom class for progress bar
   style: { "background-color": "red" }, // custom style for progress bar
   animate: { // pass the arguments to the el.animate() method which will be called on the progress bar
      keyframes: [{ width: "0%" }, { width: "100%" }], // pass the keyframes, the options or both
      options: { // pass the keyframes, the options or both
        duration: 5000,
        fill: "forwards",
        easing: "linear",
      },
   },
  }}
  wrapperClass={{
   className: "my-wrapper-class",
   replaceDefault: false,
  }} // custom wrapper class, choose to replace default or not
  icon={<svg></svg>} // custom icon for the toast, accepts a JSX element
  iconWrapperClass={{
   className: "my-icon-wrapper-class", // custom icon wrapper class
   replaceDefault: false, // replace default icon wrapper
  }}
  iconWrapperStyle={{
   "background-color": "red", // custom style for the icon wrapper
  }}
  aria={{ "aria-live": "polite", role: "status" }} // accessibility props
  unstyled={false} // remove all default styles of the toast, doesn't affect the toast wrapper

Functions

The useToast function returns an object with five methods.

import {useToast} from "solid-moon-toast";

...

const {notify, update, dismiss, remove, custom} = useToast();

notify()

Creates a new toast. Accepts a string or jsx as the first argument, and options (common options) as the second argument.

Returns an id, a ref to the toast element, and a timer:
const { id, ref, timer } = notify('My first toast!', { duration: 5000 })

You can provide your own id:
const toastId = 'toast-1'

const { id, ref, timer } = notify('My first toast!', { id: toastId })

The timer allows for the control of the toast duration and the progress bar animation:
const { timer } = notify()

timer.pause()
timer.play()
timer.reset()

update()

Updates an existing toast. Accepts a string or jsx as the first argument, and options (common options) as the second argument. Passing an id as an option is required.

Returns an id, a ref to the toast element, and a timer:
const {id, ref, timer} = update("Updating toast...", id: "toast-1")

Example:
const { notify, update } = useToast()

const getData = async () => {
  const { id } = notify('Fetching data...', { type: 'loading' })

  const response = await fetch('http://example.com/api')
  const data = await response.json()

  if (!data) return update('Error fetching data.', { id, type: 'error' })

  update('Successfully fetched data!', { id, type: 'success' })
  return data
}

dismiss()

Dismisses a toast. Call with an id to dismiss a specific toast, or with no arguments to dismiss all toasts.

dismiss('toast-1')
dismiss() // dismisses all toasts

remove()

Removes a toast immediately, without triggering the exit animation. Call with an id to remove a specific toast, or with no arguments to remove all toasts.

remove('toast-1')
remove() // removes all toasts

custom()

Provide your own toast body. Provides id, timer, and duration as args. Returns id, ref and timer. Accepts limited options.

const {id, ref, timer: timerControls} = custom({id, duration, timer} => (
  <div>
    <button onClick={()=> timer.pause()}>Pause toast</button>
  </div>
), {
  onEnter: "slide-in-top"
});

// options:
  | 'duration'
  | 'dismissButton'
  | 'dissmisOnClick'
  | 'enterDuration'
  | 'exitDuration'
  | 'id'
  | 'onEnter'
  | 'onExit'
  | 'onIdle'
  | 'pauseOnHover'
  | 'progressBar'
  | 'wrapperClass'

Miscellaneous

Custom progress bar

You can use a custom progress bar by providing an element with a data-role="progress" property. After that, set the toast options to not show the default progress bar. The progress bar can be controlled with the timer prop.

Example:
const {timer} = notify(
  <>
    <div class="px-1">
      <div class="relative flex gap-3">
        <p class="font-medium">
          This is a custom toast.
        </p>
        <button onClick={() => timer.pause()}>Pause timer</button>
      </div>
    </div>
  
    <div
      data-role="progress"
      class="pointer-events-none absolute left-0 top-0 h-full w-full bg-blue-600/20"
    />
  </>,
  {
    duration: 10000,
    progressBar: { showDefault: false }, // hide the default progress bar
  }
);

To add a custom animation to the progress bar, pass keyframes and / or options to progressBar: {animate: {keyframes, options}}, which will be called by the Element: animate() method on the progress bar dom element.

notify(
  <>
    <div>
        <p>
          You can customize the progress bar.
        </p>
    </div>
    <div
      data-role="progress"
      class="pointer-events-none absolute left-0 top-0 h-full w-full bg-blue-600/20"
    />
  </>,
  {
    progressBar: {
      style: {
        background: 'radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%)',
      },
      animate: {
        keyframes: [{ transform: 'scaleX(0)' }, { transform: 'scaleX(1)' }],
      },
    },
  });

Computations created outside a createRoot error

This error occurs when passing a jsx component as the first argument to the create toast functions. You can fix it by wrapping the component with createRoot().

 notify(<MyComponent message="This is a test message" />); // will log "computations created outside a `createRoot` or `render` will never be disposed"

 notify(createRoot(()=> <MyComponent message="This is a test message" />)) // no error