toast notification hook

This commit is contained in:
Oscar Beaumont 2022-10-07 07:36:22 +08:00
parent 75c4680b43
commit 162eef60ab
4 changed files with 158 additions and 7 deletions

View file

@ -24,6 +24,7 @@
"@radix-ui/react-progress": "^1.0.0",
"@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-tabs": "^1.0.0",
"@radix-ui/react-toast": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"@sd/assets": "workspace:*",
"@sd/client": "workspace:*",

View file

@ -1,10 +1,12 @@
import * as ToastPrimitive from '@radix-ui/react-toast';
import { useCurrentLibrary } from '@sd/client';
import clsx from 'clsx';
import { Suspense } from 'react';
import { Suspense, useEffect, useState } from 'react';
import { Outlet } from 'react-router-dom';
import { Sidebar } from './components/layout/Sidebar';
import { useOperatingSystem } from './hooks/useOperatingSystem';
import { useToasts } from './hooks/useToasts';
export function AppLayout() {
const { libraries } = useCurrentLibrary();
@ -34,6 +36,94 @@ export function AppLayout() {
<Outlet />
</Suspense>
</div>
<Toasts />
</div>
);
}
function Toasts() {
const { toasts, addToast, removeToast } = useToasts();
// useEffect(() => {
// setTimeout(() => {
// addToast({
// title: 'Spacedrop',
// subtitle: 'Someone tried to send you a file. Accept it?',
// actionButton: {
// text: 'Accept',
// onClick: () => {
// console.log('Bruh');
// }
// }
// });
// }, 2000);
// }, []);
return (
<div className="fixed flex right-0">
<ToastPrimitive.Provider>
<>
{toasts.map((toast) => (
<ToastPrimitive.Root
key={toast.id}
open={true}
onOpenChange={() => removeToast(toast)}
duration={toast.duration || 3000}
className={clsx(
'w-80 m-4 shadow-lg rounded-lg',
'bg-gray-800/20 backdrop-blur',
'radix-state-open:animate-toast-slide-in-bottom md:radix-state-open:animate-toast-slide-in-right',
'radix-state-closed:animate-toast-hide',
'radix-swipe-end:animate-toast-swipe-out',
'translate-x-radix-toast-swipe-move-x',
'radix-swipe-cancel:translate-x-0 radix-swipe-cancel:duration-200 radix-swipe-cancel:ease-[ease]',
'focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-opacity-75 border-white/10 border-2 shadow-2xl'
)}
>
<div className="flex">
<div className="w-0 flex-1 flex items-center pl-5 py-4">
<div className="w-full radix">
<ToastPrimitive.Title className="text-sm font-medium text-gray-900 dark:text-gray-100">
{toast.title}
</ToastPrimitive.Title>
{toast.subtitle && (
<ToastPrimitive.Description className="mt-1 text-sm text-gray-700 dark:text-gray-400">
{toast.subtitle}
</ToastPrimitive.Description>
)}
</div>
</div>
<div className="flex">
<div className="flex flex-col px-3 py-2 space-y-1">
<div className="h-0 flex-1 flex">
{toast.actionButton && (
<ToastPrimitive.Action
altText="view now"
className="w-full border border-transparent rounded-lg px-3 py-2 flex items-center justify-center text-sm font-medium text-primary dark:text-primary hover:bg-white/10 focus:z-10 focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-opacity-75"
onClick={(e) => {
e.preventDefault();
toast.actionButton?.onClick();
removeToast(toast);
}}
>
{toast.actionButton.text || 'Open'}
</ToastPrimitive.Action>
)}
</div>
<div className="h-0 flex-1 flex">
<ToastPrimitive.Close className="w-full border border-transparent rounded-lg px-3 py-2 flex items-center justify-center text-sm font-medium text-gray-700 dark:text-gray-100 hover:bg-white/10 focus:z-10 focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-opacity-75">
Dismiss
</ToastPrimitive.Close>
</div>
</div>
</div>
</div>
</ToastPrimitive.Root>
))}
<ToastPrimitive.Viewport />
</>
</ToastPrimitive.Provider>
</div>
);
}

View file

@ -0,0 +1,34 @@
import { proxy, useSnapshot } from 'valtio';
interface Toast {
id: string;
title: string;
subtitle?: string;
duration?: number;
actionButton?: {
text: string;
onClick: () => void;
};
}
const state = proxy({
toasts: [] as Toast[]
});
const randomId = () => Math.random().toString(36).slice(2);
export function useToasts() {
return {
toasts: useSnapshot(state).toasts,
addToast: (toast: Omit<Toast, 'id'>) => {
state.toasts.push({
id: randomId(),
...toast
});
},
removeToast: (toast: Toast | string) => {
const id = typeof toast === 'string' ? toast : toast.id;
state.toasts = state.toasts.filter((t) => t.id !== id);
}
};
}

View file

@ -337,6 +337,7 @@ importers:
'@radix-ui/react-progress': ^1.0.0
'@radix-ui/react-slider': ^1.0.0
'@radix-ui/react-tabs': ^1.0.0
'@radix-ui/react-toast': ^1.0.0
'@radix-ui/react-tooltip': ^1.0.0
'@sd/assets': workspace:*
'@sd/client': workspace:*
@ -411,6 +412,7 @@ importers:
'@radix-ui/react-progress': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-slider': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-tabs': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-toast': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-tooltip': 1.0.0_rj7ozvcq3uehdlnj3cbwzbi5ce
'@sd/assets': link:../assets
'@sd/client': link:../client
@ -3451,6 +3453,29 @@ packages:
react-dom: 18.2.0_react@18.2.0
dev: false
/@radix-ui/react-toast/1.0.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-mdoF6rahgushdev0OX+9a7JKoH0xZAZBo2Ktf/s779S7EnkZeL3/MFiRIV5LpRP5CtASmfdSD3FLnEvG1RHRtQ==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.19.0
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-collection': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-compose-refs': 1.0.0_react@18.2.0
'@radix-ui/react-context': 1.0.0_react@18.2.0
'@radix-ui/react-dismissable-layer': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-portal': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0
'@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0
'@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0
'@radix-ui/react-visually-hidden': 1.0.0_biqbaboplfbrettd7655fr4n2y
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/@radix-ui/react-tooltip/1.0.0_rj7ozvcq3uehdlnj3cbwzbi5ce:
resolution: {integrity: sha512-RB06pov+O4Npy10ei1C6fsyB9QoOjz7Ubo8Sl3qdKtLgkL9iI96925DYtH0bxx6MH6YB2FuzLU6B75qn3AQQQw==}
peerDependencies:
@ -6232,7 +6257,7 @@ packages:
'@babel/plugin-transform-react-jsx-source': 7.18.6_@babel+core@7.19.3
magic-string: 0.26.6
react-refresh: 0.14.0
vite: 3.1.4_sass@1.55.0
vite: 3.1.4
transitivePeerDependencies:
- supports-color
@ -6967,6 +6992,7 @@ packages:
normalize-range: 0.1.2
picocolors: 1.0.0
postcss-value-parser: 4.2.0
dev: false
/autoprefixer/10.4.12_postcss@8.4.17:
resolution: {integrity: sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==}
@ -7527,7 +7553,7 @@ packages:
hasBin: true
dependencies:
caniuse-lite: 1.0.30001416
electron-to-chromium: 1.4.274
electron-to-chromium: 1.4.275
node-releases: 2.0.6
update-browserslist-db: 1.0.10_browserslist@4.21.4
@ -9250,8 +9276,8 @@ packages:
jake: 10.8.5
dev: true
/electron-to-chromium/1.4.274:
resolution: {integrity: sha512-Fgn7JZQzq85I81FpKUNxVLAzoghy8JZJ4NIue+YfUYBbu1AkpgzFvNwzF/ZNZH9ElkmJD0TSWu1F2gTpw/zZlg==}
/electron-to-chromium/1.4.275:
resolution: {integrity: sha512-aJeQQ+Hl9Jyyzv4chBqYJwmVRY46N5i2BEX5Cuyk/5gFCUZ5F3i7Hnba6snZftWla7Gglwc5pIgcd+E7cW+rPg==}
/elliptic/6.5.4:
resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==}
@ -18098,7 +18124,7 @@ packages:
valtio: ^1.2.5
dependencies:
lodash: 4.17.21
valtio: 1.7.0
valtio: 1.7.0_react@18.2.0+vite@3.1.4
dev: false
/valtio/1.7.0:
@ -18262,7 +18288,7 @@ packages:
dependencies:
'@rollup/pluginutils': 4.2.1
'@svgr/core': 6.4.0
vite: 3.1.4_sass@1.55.0
vite: 3.1.4
transitivePeerDependencies:
- '@babel/core'
- supports-color