mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
feat(Toast): add Toast nearly identical to ChatGPT's (#1108)
This commit is contained in:
parent
ba5ab86037
commit
81a90d245b
10 changed files with 281 additions and 3 deletions
|
@ -37,6 +37,7 @@
|
|||
"@radix-ui/react-slider": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.3",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"@tanstack/react-query": "^4.28.0",
|
||||
"@zattoo/use-double-click": "1.2.0",
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { RecoilRoot } from 'recoil';
|
||||
import * as RadixToast from '@radix-ui/react-toast';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { QueryClient, QueryClientProvider, QueryCache } from '@tanstack/react-query';
|
||||
import { ScreenshotProvider, ThemeProvider, useApiErrorBoundary } from './hooks';
|
||||
import Toast from './components/ui/Toast';
|
||||
import { router } from './routes';
|
||||
|
||||
const App = () => {
|
||||
|
@ -22,8 +24,12 @@ const App = () => {
|
|||
<QueryClientProvider client={queryClient}>
|
||||
<RecoilRoot>
|
||||
<ThemeProvider>
|
||||
<RouterProvider router={router} />
|
||||
<ReactQueryDevtools initialIsOpen={false} position="top-right" />
|
||||
<RadixToast.Provider>
|
||||
<RouterProvider router={router} />
|
||||
<ReactQueryDevtools initialIsOpen={false} position="top-right" />
|
||||
<Toast />
|
||||
<RadixToast.Viewport className="pointer-events-none fixed inset-0 z-[60] mx-auto my-2 flex max-w-[560px] flex-col items-stretch justify-start md:pb-5" />
|
||||
</RadixToast.Provider>
|
||||
</ThemeProvider>
|
||||
</RecoilRoot>
|
||||
</QueryClientProvider>
|
||||
|
|
|
@ -21,6 +21,13 @@ export enum ESide {
|
|||
Left = 'left',
|
||||
}
|
||||
|
||||
export enum NotificationSeverity {
|
||||
INFO = 'info',
|
||||
SUCCESS = 'success',
|
||||
WARNING = 'warning',
|
||||
ERROR = 'error',
|
||||
}
|
||||
|
||||
export type TBaseSettingsProps = {
|
||||
conversation: TConversation | TPreset | null;
|
||||
className?: string;
|
||||
|
|
58
client/src/components/ui/Toast.tsx
Normal file
58
client/src/components/ui/Toast.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import * as RadixToast from '@radix-ui/react-toast';
|
||||
import { NotificationSeverity } from '~/common/types';
|
||||
import { useToast } from '~/hooks';
|
||||
|
||||
export default function Toast() {
|
||||
const { toast, onOpenChange } = useToast();
|
||||
const severityClassName = {
|
||||
[NotificationSeverity.INFO]: 'border-gray-500 bg-gray-500',
|
||||
[NotificationSeverity.SUCCESS]: 'border-green-500 bg-green-500',
|
||||
[NotificationSeverity.WARNING]: 'border-orange-500 bg-orange-500',
|
||||
[NotificationSeverity.ERROR]: 'border-red-500 bg-red-500',
|
||||
};
|
||||
|
||||
return (
|
||||
<RadixToast.Root
|
||||
open={toast.open}
|
||||
onOpenChange={onOpenChange}
|
||||
className="toast-root"
|
||||
style={{
|
||||
height: '74px',
|
||||
marginBottom: '0px',
|
||||
}}
|
||||
>
|
||||
<div className="w-full p-1 text-center md:w-auto md:text-justify">
|
||||
<div
|
||||
className={`alert-root pointer-events-auto inline-flex flex-row gap-2 rounded-md border px-3 py-2 text-white ${
|
||||
severityClassName[toast.severity]
|
||||
}`}
|
||||
role="alert"
|
||||
>
|
||||
{toast.showIcon && (
|
||||
<div className="mt-1 flex-shrink-0 flex-grow-0">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="icon-sm"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2" />
|
||||
<line x1="12" y1="8" x2="12" y2="12" />
|
||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<RadixToast.Description className="flex-1 justify-center gap-2">
|
||||
<div className="whitespace-pre-wrap text-left">{toast.message}</div>
|
||||
</RadixToast.Description>
|
||||
</div>
|
||||
</div>
|
||||
</RadixToast.Root>
|
||||
);
|
||||
}
|
|
@ -2,6 +2,7 @@ export * from './AuthContext';
|
|||
export * from './ThemeContext';
|
||||
export * from './ScreenshotContext';
|
||||
export * from './ApiErrorBoundaryContext';
|
||||
export { default as useToast } from './useToast';
|
||||
export { default as useTimeout } from './useTimeout';
|
||||
export { default as useUserKey } from './useUserKey';
|
||||
export { default as useDebounce } from './useDebounce';
|
||||
|
|
43
client/src/hooks/useToast.ts
Normal file
43
client/src/hooks/useToast.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { useRef, useEffect } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import store from '~/store';
|
||||
|
||||
export default function useToast(timeoutDuration = 100) {
|
||||
const [toast, setToast] = useRecoilState(store.toastState);
|
||||
const timerRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timerRef.current !== null) {
|
||||
clearTimeout(timerRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
type TShowToast = {
|
||||
message: string;
|
||||
severity?: NotificationSeverity;
|
||||
showIcon?: boolean;
|
||||
};
|
||||
|
||||
const showToast = ({
|
||||
message,
|
||||
severity = NotificationSeverity.SUCCESS,
|
||||
showIcon = true,
|
||||
}: TShowToast) => {
|
||||
setToast({ ...toast, open: false });
|
||||
if (timerRef.current !== null) {
|
||||
clearTimeout(timerRef.current);
|
||||
}
|
||||
timerRef.current = window.setTimeout(() => {
|
||||
setToast({ open: true, message, severity, showIcon });
|
||||
}, timeoutDuration);
|
||||
};
|
||||
|
||||
return {
|
||||
toast,
|
||||
onOpenChange: (open: boolean) => setToast({ ...toast, open }),
|
||||
showToast,
|
||||
};
|
||||
}
|
|
@ -4,6 +4,7 @@ import endpoints from './endpoints';
|
|||
import models from './models';
|
||||
import user from './user';
|
||||
import text from './text';
|
||||
import toast from './toast';
|
||||
import submission from './submission';
|
||||
import search from './search';
|
||||
import preset from './preset';
|
||||
|
@ -17,6 +18,7 @@ export default {
|
|||
...models,
|
||||
...user,
|
||||
...text,
|
||||
...toast,
|
||||
...submission,
|
||||
...search,
|
||||
...preset,
|
||||
|
|
14
client/src/store/toast.ts
Normal file
14
client/src/store/toast.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { atom } from 'recoil';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
|
||||
const toastState = atom({
|
||||
key: 'toastState',
|
||||
default: {
|
||||
open: false,
|
||||
message: '',
|
||||
severity: NotificationSeverity.SUCCESS,
|
||||
showIcon: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default { toastState };
|
|
@ -1532,3 +1532,64 @@ button.scroll-convo {
|
|||
.hidden-visibility {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.toast-root {
|
||||
align-items:center;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
height:0;
|
||||
transition:all .24s cubic-bezier(0,0,.2,1)
|
||||
}
|
||||
|
||||
.toast-root[data-state=open] {
|
||||
-webkit-animation:toast-open .24s cubic-bezier(.175,.885,.32,1.175) both;
|
||||
animation:toast-open .24s cubic-bezier(.175,.885,.32,1.175) both
|
||||
}
|
||||
.toast-root[data-state=closed] {
|
||||
-webkit-animation:toast-close .12s cubic-bezier(.4,0,1,1) both;
|
||||
animation:toast-close .12s cubic-bezier(.4,0,1,1) both
|
||||
}
|
||||
.toast-root .alert-root {
|
||||
box-shadow:0 0 1px rgba(67,90,111,.3),0 5px 8px -4px rgba(67,90,111,.3);
|
||||
flex-shrink:0;
|
||||
pointer-events:all
|
||||
}
|
||||
|
||||
@-webkit-keyframes toast-open {
|
||||
0% {
|
||||
opacity:0;
|
||||
-webkit-transform:translateY(-100%);
|
||||
transform:translateY(-100%)
|
||||
}
|
||||
to {
|
||||
-webkit-transform:translateY(0);
|
||||
transform:translateY(0)
|
||||
}
|
||||
}
|
||||
@keyframes toast-open {
|
||||
0% {
|
||||
opacity:0;
|
||||
-webkit-transform:translateY(-100%);
|
||||
transform:translateY(-100%)
|
||||
}
|
||||
to {
|
||||
-webkit-transform:translateY(0);
|
||||
transform:translateY(0)
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes toast-close {
|
||||
0% {
|
||||
opacity:1
|
||||
}
|
||||
to {
|
||||
opacity:0
|
||||
}
|
||||
}
|
||||
@keyframes toast-close {
|
||||
0% {
|
||||
opacity:1
|
||||
}
|
||||
to {
|
||||
opacity:0
|
||||
}
|
||||
}
|
87
package-lock.json
generated
87
package-lock.json
generated
|
@ -607,6 +607,7 @@
|
|||
"@radix-ui/react-slider": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.3",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"@tanstack/react-query": "^4.28.0",
|
||||
"@zattoo/use-double-click": "1.2.0",
|
||||
|
@ -6331,6 +6332,90 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toast": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz",
|
||||
"integrity": "sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||
"@radix-ui/react-portal": "1.0.4",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1",
|
||||
"@radix-ui/react-visually-hidden": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
|
||||
"integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz",
|
||||
"integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.6.tgz",
|
||||
|
@ -23994,7 +24079,7 @@
|
|||
},
|
||||
"packages/data-provider": {
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^4.28.0",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue