feat: add notification store and NotificationStack component

This commit is contained in:
Kirill
2026-05-27 21:07:55 +05:00
parent f6414adf2f
commit e2d4423e2e
5 changed files with 216 additions and 0 deletions
@@ -0,0 +1,36 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { describe, it, expect, beforeEach } from 'vitest'
import { addNotification, dismissAll } from '../../model/notification'
import { NotificationStack } from './NotificationStack'
beforeEach(() => {
dismissAll()
})
describe('NotificationStack', () => {
it('renders nothing when empty', () => {
const { container } = render(<NotificationStack />)
expect(container.firstChild).toBeNull()
})
it('renders notification when added', async () => {
render(<NotificationStack />)
addNotification({ type: 'info', message: 'Hello' })
await waitFor(() => {
expect(screen.getByText('Hello')).toBeInTheDocument()
})
})
it('dismiss button works', async () => {
render(<NotificationStack />)
addNotification({ type: 'info', message: 'Dismiss me' })
await waitFor(() => {
expect(screen.getByText('Dismiss me')).toBeInTheDocument()
})
fireEvent.click(screen.getByTestId('CloseIcon'))
await waitFor(() => {
expect(screen.queryByText('Dismiss me')).not.toBeInTheDocument()
})
})
})
@@ -0,0 +1,47 @@
import { useUnit } from 'effector-react'
import { Snackbar, Alert, Stack, IconButton } from '@mui/material'
import CloseIcon from '@mui/icons-material/Close'
import { $notifications, dismissNotification } from '../../model/notification'
export function NotificationStack() {
const notifications = useUnit($notifications)
if (notifications.length === 0) return null
return (
<Stack
spacing={1}
sx={{
position: 'fixed',
bottom: 80,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 2000,
width: 'auto',
maxWidth: 400,
}}
>
{notifications.map((n) => (
<Snackbar
key={n.id}
open
autoHideDuration={n.autoHideDuration}
onClose={() => dismissNotification(n.id)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert
severity={n.type}
variant="filled"
action={
<IconButton size="small" color="inherit" onClick={() => dismissNotification(n.id)}>
<CloseIcon fontSize="small" />
</IconButton>
}
>
{n.message}
</Alert>
</Snackbar>
))}
</Stack>
)
}
@@ -0,0 +1 @@
export { NotificationStack } from './NotificationStack'