-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* wip for toasts * set max width * getting toasts working * update workspace timeout ui and add toast * put in a portal * adding some aria props * renaming to toast() * improve mobile styles * shift dotfiles repo update into mutation * remove test button * Update components/dashboard/src/user-settings/Preferences.tsx Co-authored-by: George Tsiolis <tsiolis.g@gmail.com> * Update components/dashboard/src/user-settings/Preferences.tsx Co-authored-by: George Tsiolis <tsiolis.g@gmail.com> * Adjusting styling per PR feedback * don't hide toasts on hover --------- Co-authored-by: George Tsiolis <tsiolis.g@gmail.com>
- Loading branch information
1 parent
2401a18
commit ee4a5a0
Showing
8 changed files
with
354 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/** | ||
* Copyright (c) 2023 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License.AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import classNames from "classnames"; | ||
import { FC, useCallback, useEffect, useRef } from "react"; | ||
import { useId } from "../../hooks/useId"; | ||
import { ToastEntry } from "./reducer"; | ||
|
||
type Props = ToastEntry & { | ||
onRemove: (id: string) => void; | ||
}; | ||
|
||
export const Toast: FC<Props> = ({ id, message, duration = 5000, autoHide = true, onRemove }) => { | ||
const elId = useId(); | ||
const hideTimeout = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
|
||
const handleRemove = useCallback( | ||
(e) => { | ||
e.preventDefault(); | ||
|
||
onRemove(id); | ||
}, | ||
[id, onRemove], | ||
); | ||
|
||
useEffect(() => { | ||
if (!autoHide) { | ||
return; | ||
} | ||
|
||
hideTimeout.current = setTimeout(() => { | ||
onRemove(id); | ||
}, duration); | ||
|
||
return () => { | ||
if (hideTimeout.current) { | ||
clearTimeout(hideTimeout.current); | ||
} | ||
}; | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
|
||
const onMouseEnter = useCallback(() => { | ||
if (hideTimeout.current) { | ||
clearTimeout(hideTimeout.current); | ||
} | ||
}, []); | ||
|
||
const onMouseLeave = useCallback(() => { | ||
if (!autoHide) { | ||
return; | ||
} | ||
|
||
if (hideTimeout.current) { | ||
clearTimeout(hideTimeout.current); | ||
} | ||
|
||
hideTimeout.current = setTimeout(() => { | ||
onRemove(id); | ||
}, duration); | ||
}, [autoHide, duration, id, onRemove]); | ||
|
||
return ( | ||
<div | ||
className={classNames( | ||
"relative flex justify-between items-center", | ||
"w-full md:w-96 max-w-full", | ||
"p-4 md:rounded-md", | ||
"bg-gray-800 dark:bg-gray-100", | ||
"text-white dark:text-gray-800", | ||
"transition-transform animate-toast-in-right", | ||
)} | ||
onMouseEnter={onMouseEnter} | ||
onMouseLeave={onMouseLeave} | ||
role="alert" | ||
aria-labelledby={elId} | ||
> | ||
<p className="text-white dark:text-gray-800" id={elId}> | ||
{message} | ||
</p> | ||
<button | ||
className={classNames( | ||
"cursor-pointer p-2", | ||
"bg-transparent hover:bg-transparent", | ||
"text-white hover:text-gray-300 dark:text-gray-800 dark:hover:text-gray-600", | ||
)} | ||
onClick={handleRemove} | ||
> | ||
<svg version="1.1" width="10px" height="10px" viewBox="0 0 100 100"> | ||
<line x1="0" y1="0" x2="100" y2="100" stroke="currentColor" strokeWidth="20" /> | ||
<line x1="0" y1="100" x2="100" y2="0" stroke="currentColor" strokeWidth="20" /> | ||
</svg> | ||
</button> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/** | ||
* Copyright (c) 2023 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License.AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import classNames from "classnames"; | ||
import { createContext, FC, memo, useCallback, useContext, useMemo, useReducer } from "react"; | ||
import { Portal } from "react-portal"; | ||
import { ToastEntry, toastReducer } from "./reducer"; | ||
import { Toast } from "./Toast"; | ||
|
||
type ToastFnProps = string | (Pick<ToastEntry, "message"> & Partial<ToastEntry>); | ||
|
||
const ToastContext = createContext<{ | ||
toast: (toast: ToastFnProps, opts?: Partial<ToastEntry>) => void; | ||
}>({ | ||
toast: () => undefined, | ||
}); | ||
|
||
export const useToast = () => { | ||
return useContext(ToastContext); | ||
}; | ||
|
||
export const ToastContextProvider: FC = ({ children }) => { | ||
const [toasts, dispatch] = useReducer(toastReducer, []); | ||
|
||
const removeToast = useCallback((id) => { | ||
dispatch({ type: "remove", id }); | ||
}, []); | ||
|
||
const addToast = useCallback((message: ToastFnProps, opts = {}) => { | ||
let newToast: ToastEntry = { | ||
...(typeof message === "string" | ||
? { | ||
id: `${Math.random()}`, | ||
message, | ||
} | ||
: { | ||
id: `${Math.random()}`, | ||
...message, | ||
}), | ||
...opts, | ||
}; | ||
|
||
dispatch({ type: "add", toast: newToast }); | ||
}, []); | ||
|
||
const ctxValue = useMemo(() => ({ toast: addToast }), [addToast]); | ||
|
||
return ( | ||
<ToastContext.Provider value={ctxValue}> | ||
{children} | ||
<ToastsList toasts={toasts} onRemove={removeToast} /> | ||
</ToastContext.Provider> | ||
); | ||
}; | ||
|
||
type ToastsListProps = { | ||
toasts: ToastEntry[]; | ||
onRemove: (id: string) => void; | ||
}; | ||
const ToastsList: FC<ToastsListProps> = memo(({ toasts, onRemove }) => { | ||
return ( | ||
<Portal> | ||
<div | ||
className={classNames( | ||
"fixed box-border space-y-2", | ||
"w-full md:w-auto", | ||
"bottom-0 md:bottom-2 right-0 md:right-2", | ||
)} | ||
tabIndex={-1} | ||
role="region" | ||
aria-label="Notifications" | ||
> | ||
{toasts.map((toast) => { | ||
return <Toast key={toast.id} {...toast} onRemove={onRemove} />; | ||
})} | ||
</div> | ||
</Portal> | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** | ||
* Copyright (c) 2023 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License.AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
export type ToastEntry = { | ||
id: string; | ||
message: string; | ||
duration?: number; | ||
autoHide?: boolean; | ||
}; | ||
|
||
type ToastAction = | ||
| { | ||
type: "add"; | ||
toast: ToastEntry; | ||
} | ||
| { | ||
type: "remove"; | ||
id: string; | ||
}; | ||
export const toastReducer = (state: ToastEntry[], action: ToastAction) => { | ||
if (action.type === "add") { | ||
return [...state, action.toast]; | ||
} | ||
|
||
if (action.type === "remove") { | ||
return state.filter((toast) => toast.id !== action.id); | ||
} | ||
|
||
return state; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.