diff --git a/ordinis-admin-ui/package.json b/ordinis-admin-ui/package.json index f9f9e39..720e52a 100644 --- a/ordinis-admin-ui/package.json +++ b/ordinis-admin-ui/package.json @@ -43,6 +43,7 @@ "react-i18next": "^17.0.2", "react-leaflet": "^5.0.0", "react-oidc-context": "^3.3.1", + "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0" }, diff --git a/ordinis-admin-ui/pnpm-lock.yaml b/ordinis-admin-ui/pnpm-lock.yaml index 132e12b..6e7180d 100644 --- a/ordinis-admin-ui/pnpm-lock.yaml +++ b/ordinis-admin-ui/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: react-oidc-context: specifier: ^3.3.1 version: 3.3.1(oidc-client-ts@3.5.0)(react@19.2.5) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tailwind-merge: specifier: ^3.6.0 version: 3.6.0 @@ -2213,6 +2216,12 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4300,6 +4309,11 @@ snapshots: siginfo@2.0.0: {} + sonner@2.0.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + source-map-js@1.2.1: {} stackback@0.0.2: {} diff --git a/ordinis-admin-ui/src/main.tsx b/ordinis-admin-ui/src/main.tsx index 087854a..f34b219 100644 --- a/ordinis-admin-ui/src/main.tsx +++ b/ordinis-admin-ui/src/main.tsx @@ -2,7 +2,7 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { RouterProvider, createRouter } from '@tanstack/react-router' -import { NStartUiProvider, DatePickerProvider, DATE_PICKER_LOCALE_RU } from '@/ui' +import { NStartUiProvider, DatePickerProvider, DATE_PICKER_LOCALE_RU, Toaster } from '@/ui' import { AuthProvider } from 'react-oidc-context' import { routeTree } from './routeTree.gen' import { oidcAuthConfig } from '@/auth/oidcConfig' @@ -36,6 +36,7 @@ createRoot(document.getElementById('root')!).render( + diff --git a/ordinis-admin-ui/src/ui/components/toaster.tsx b/ordinis-admin-ui/src/ui/components/toaster.tsx new file mode 100644 index 0000000..12d2d3d --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/toaster.tsx @@ -0,0 +1,52 @@ +import { Toaster as SonnerToaster, toast } from 'sonner' +import { useTheme } from '@/stores/ThemeProvider' + +/** + * Toast notification provider — wraps sonner с our design tokens. + * + * Sonner = headless, кастомизируем через CSS variables. Параметризируется + * theme="light" | "dark" — берётся из ThemeProvider, авто-switch на смену темы. + * + * Использование: + *
+ * import { toast } from '@/ui/components/toaster'
+ * toast.success('Запись сохранена')
+ * toast.error('Ошибка валидации', { description: '...' })
+ * toast.promise(mutateAsync(), {loading, success, error})
+ * 
+ * + * Mount: один раз в main.tsx внутри ThemeProvider. + */ +export function Toaster() { + const { resolved } = useTheme() + + return ( + + ) +} + +// Re-export для удобства: `import { toast } from '@/ui/components/toaster'`. +export { toast } diff --git a/ordinis-admin-ui/src/ui/index.ts b/ordinis-admin-ui/src/ui/index.ts index 8bf979a..47906d2 100644 --- a/ordinis-admin-ui/src/ui/index.ts +++ b/ordinis-admin-ui/src/ui/index.ts @@ -123,3 +123,8 @@ export { DropdownMenuRadioGroup, } from './components/dropdown-menu' export { Switch as ShadcnSwitch } from './components/switch' + +// Toast notifications через sonner (rich, accessible, themable). +// Mount один раз в main.tsx; call toast.success/error/etc. +// в callbacks / effects. +export { Toaster, toast } from './components/toaster'