Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions .storybook/preview.ts → .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import type { Preview } from '@storybook/react-vite'
import '../src/index.css'

import '@/index.css'

import { ToastProvider } from '../src/shared/integrations/Toast'

import { ThemeProvider } from '../src/shared/theme'

const preview: Preview = {
decorators: [
(Story) => (
<ThemeProvider>
<ToastProvider />

<Story />
</ThemeProvider>
),
],

parameters: {
controls: {
matchers: {
color: /(background|color)$/i,

date: /Date$/i,
},
},

a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: 'todo',
},
},
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ export default defineConfig([
])
```

<!-- 38 35 34 26 -->
<!-- 27 31 33 34 35 -->
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"react-dom": "^19.2.5",
"react-icons": "^5.5.0",
"react-router-dom": "^7.7.1",
"react-toastify": "^11.1.0",
"styled-components": "^6.2.0",
"tailwindcss": "^4.2.4",
"vite-plugin-remove-console": "^2.2.0",
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/hooks/useToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { toast } from '@/shared/integrations/Toast/toast.tsx'

export const useToast = () => {
return toast
}
2 changes: 2 additions & 0 deletions src/layouts/MainLayout/BaseLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AvatarMenu from '@/shared/composites/AvatarMenu/AvatarMenu'
import { ToastProvider } from '@/shared/integrations/Toast'
import { Tooltip } from '@/shared/primitives/Tooltip'
// import ThemeToggleSwitch from '@/ThemeToggleSwitch'
import React, { Suspense } from 'react'
Expand All @@ -8,6 +9,7 @@ const BaseLayout: React.FC = () => {
return (
<div className="bg-bg-primary flex min-h-screen flex-col">
{/* Main Content */}
<ToastProvider />
<main className="flex-1 overflow-y-auto px-4 sm:px-6 lg:px-8">
<div className="text-text-primary py-8 sm:py-12">
<Suspense fallback={null}>
Expand Down
11 changes: 8 additions & 3 deletions src/shared/composites/AvatarMenu/AvatarMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useEffect, useRef, useState } from 'react'

import { Avatar } from '@/shared/primitives/Avatar'
import { Switch } from '@/shared/primitives/Switch/Switch'
import {
Expand All @@ -15,6 +14,8 @@ import {
} from '@heroicons/react/24/outline'
import { useTheme } from '@/shared/theme'
import { Button } from '@/shared/primitives/Button'
import { useToast } from '@/hooks/useToast'

export interface AvatarMenuProps {
name: string

Expand All @@ -41,6 +42,7 @@ export function AvatarMenu({
const [open, setOpen] = useState(false)
const { themeName, setTheme } = useTheme()
const ref = useRef<HTMLDivElement>(null)
const toast = useToast()

useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
Expand Down Expand Up @@ -114,7 +116,11 @@ export function AvatarMenu({
className="ms-6"
checked={themeName === 'dark'}
onChange={(checked: boolean) => {
return setTheme(checked ? 'dark' : 'light')
const newTheme = checked ? 'dark' : 'light'

setTheme(newTheme)

toast.info(`Theme changed to ${newTheme}`)
}}
/>
}
Expand All @@ -141,7 +147,6 @@ export function AvatarMenu({
variant={'danger'}
leftIcon={<ArrowRightStartOnRectangleIcon className="h-5 w-5" />}
>
{/* <div><ArrowRightStartOnRectangleIcon className='w-5 h-5' /></div> */}
<div>Logout</div>
</Button>
</div>
Expand Down
116 changes: 116 additions & 0 deletions src/shared/integrations/Toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { Meta, StoryObj } from '@storybook/react-vite'

import { toast } from './toast'

import { Button } from '@/shared/primitives/Button'

const meta = {
title: 'Shared/Integrations/Toast',

tags: ['autodocs'],
} satisfies Meta

export default meta

type Story = StoryObj

export const Success: Story = {
render: () => (
<>
<Button
onClick={() => {
toast.success('Report resolved successfully')
}}
>
Show Success Toast
</Button>
</>
),
}

export const Error: Story = {
render: () => (
<>
<Button
variant="danger"
onClick={() => {
toast.error('Failed to moderate report')
}}
>
Show Error Toast
</Button>
</>
),
}

export const Warning: Story = {
render: () => (
<>
<Button
variant="warning"
onClick={() => {
toast.warning('Another moderator is reviewing this')
}}
>
Show Warning Toast
</Button>
</>
),
}

export const Info: Story = {
render: () => (
<>
<Button
variant="info"
onClick={() => {
toast.info('AI moderation started')
}}
>
Show Info Toast
</Button>
</>
),
}

export const WithAction: Story = {
render: () => (
<>
<Button
onClick={() => {
toast.success('Report dismissed', {
action: {
label: 'Undo',

onClick: () => {
console.log('Undo clicked')
},
},
})
}}
>
Show Action Toast
</Button>
</>
),
}

export const StackedQueue: Story = {
render: () => (
<>
<Button
onClick={() => {
toast.success('Report resolved')

toast.warning('Another moderator joined')

toast.info('AI screening started')

toast.error('Failed to sync changes')
}}
>
Show Stacked Toasts
</Button>
</>
),
}
22 changes: 22 additions & 0 deletions src/shared/integrations/Toast/ToastProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ToastContainer, Slide } from 'react-toastify'

import 'react-toastify/dist/ReactToastify.css'

import './toast.css'

export function ToastProvider() {
return (
<ToastContainer
position="bottom-left"
autoClose={2500}
transition={Slide}
theme=""
newestOnTop
closeOnClick
pauseOnHover
draggable
/>
)
}

export default ToastProvider
3 changes: 3 additions & 0 deletions src/shared/integrations/Toast/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { ToastProvider } from './ToastProvider'

export { toast } from './toast'
51 changes: 51 additions & 0 deletions src/shared/integrations/Toast/toast.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.Toastify__toast {
background: var(--color-bg-secondary);

color: var(--color-text-primary);

border: 1px solid var(--color-border-secondary);

border-radius: var(--radius-md);

box-shadow: var(--shadow-md);

min-height: 56px;

backdrop-filter: blur(8px);
}

.Toastify__toast-body {
color: var(--color-text-primary);
}

.Toastify__close-button {
color: var(--color-text-secondary);

opacity: 1;
}

/* Remove default colored backgrounds */
.Toastify__toast--success,
.Toastify__toast--error,
.Toastify__toast--warning,
.Toastify__toast--info {
background: var(--color-bg-secondary);
}

/* Progress bars ONLY use semantic colors */

.Toastify__progress-bar--success {
background: var(--color-text-success);
}

.Toastify__progress-bar--error {
background: var(--color-text-danger);
}

.Toastify__progress-bar--warning {
background: var(--color-text-warning);
}

.Toastify__progress-bar--info {
background: var(--color-text-info);
}
Loading
Loading