Gallery/AnimatePresence/Notifications stack

Notifications stack

Items spring in, fall out and the stack reflows.

list
  • Build deploy succeeded
notifications.tsx
import { AnimatePresence, motion } from 'motion-solidjs'
import { For, createSignal } from 'solid-js'

export const meta = {
  slug: 'notifications',
  title: 'Notifications stack',
  category: 'animate-presence',
  description: 'Items spring in, fall out and the stack reflows.',
  tag: 'list',
} as const

interface Note {
  id: number
  text: string
}

const messages = [
  'Build deploy succeeded',
  'You have a new follower',
  '3 unread messages',
  'PR opened on motion-solidjs',
  'CI passed on main',
]

let nextId = 1

export default function Notifications() {
  const [notes, setNotes] = createSignal<Note[]>([{ id: nextId++, text: messages[0] }])

  const add = () => {
    setNotes((n) => [...n, { id: nextId, text: messages[(nextId++ - 1) % messages.length] }])
  }
  const remove = (id: number) => setNotes((n) => n.filter((x) => x.id !== id))

  return (
    <div class="flex w-72 flex-col gap-3">
      <button
        onClick={add}
        class="self-start rounded-full bg-grad-rose px-4 py-1.5 text-xs font-semibold text-white"
      >
        + Add notification
      </button>
      <ul class="flex flex-col gap-2">
        <AnimatePresence>
          <For each={notes()}>
            {(note) => (
              <motion.li
                class="flex items-center justify-between rounded-2xl border border-border bg-card px-4 py-2.5 text-xs"
                initial={{ opacity: 0, x: -40, scale: 0.95 }}
                animate={{ opacity: 1, x: 0, scale: 1 }}
                exit={{ opacity: 0, x: 40, scale: 0.9 }}
                transition={{ type: 'spring', stiffness: 320, damping: 26 }}
                layout
              >
                <span>{note.text}</span>
                <button onClick={() => remove(note.id)} class="text-fg-dim hover:text-fg">
                  ×
                </button>
              </motion.li>
            )}
          </For>
        </AnimatePresence>
      </ul>
    </div>
  )
}