Skip to content

Commit

Permalink
Merge pull request #5 from mvriu5/feat/portal
Browse files Browse the repository at this point in the history
Feat/portal
  • Loading branch information
mvriu5 authored Aug 12, 2024
2 parents b76818b + a5ac790 commit 102ad13
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 96 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "griller",
"license": "MIT",
"version": "1.0.15",
"version": "1.0.16",
"private": false,
"repository": {
"url": "https://github.com/mvriu5/griller"
Expand Down
128 changes: 64 additions & 64 deletions src/component/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ const Toast: React.FC<ToastProps & {
removeToast: (id: string) => void,
isPaused: boolean,
}> = ({
id, title, secondTitle, icon, position = "br", closeButton,
actionButton, onAction, actionButtonText, duration = 3000, theme = "light", titleClassname,
secondTitleClassname, closeClassname, closeDivClassname, motionClassname, iconClassname, actionButtonClassname,
className, removeToast, isPaused, ...props }) => {
id, title, secondTitle, icon, position = "br", closeButton, actionButton, onAction, actionButtonText,
duration = 3000, theme = "light", titleClassname, secondTitleClassname, closeClassname, closeDivClassname,
motionClassname, iconClassname, actionButtonClassname, className, removeToast, isPaused, ...props
}) => {

const [visible, setVisible] = useState(true);
const [width, setWidth] = useState<number>(0);
Expand Down Expand Up @@ -103,74 +103,74 @@ const Toast: React.FC<ToastProps & {
};

return (
<AnimatePresence onExitComplete={() => removeToast(id)}>
{visible && (
<motion.div
key={id}
ref={toastRef}
initial="initial"
animate="animate"
exit="exit"
variants={variants}
transition={{duration: 0.5,}}
className={cn("shadow-xs shadow-zinc-900 rounded-lg", positionClasses(position), motionClassname)}
style={position === "tc" || position === "bc" ? { marginLeft: `-${width / 2}px` } : {}}
>
<div
className={cn("min-w-72 min-h-16 flex flex-row justify-between p-2 pl-4 rounded-lg",
theme === "light" ?
"bg-zinc-50 border border-zinc-200" :
"bg-zinc-800 border border-zinc-700",
className
)}
{...props}
<AnimatePresence onExitComplete={() => removeToast(id)}>
{visible && (
<motion.div
key={id}
ref={toastRef}
initial="initial"
animate="animate"
exit="exit"
variants={variants}
transition={{duration: 0.5,}}
className={cn("shadow-xs shadow-zinc-900 rounded-lg", positionClasses(position), motionClassname)}
style={position === "tc" || position === "bc" ? { marginLeft: `-${width / 2}px` } : {}}
>
<div className={cn("flex flex-row items-center space-x-2", closeButton && "mr-2")}>
{icon && (
<div className={cn(iconClassname, theme === "light" ? "text-zinc-600" : "text-zinc-100",)}>
{icon}
</div>
<div
className={cn("min-w-72 min-h-16 flex flex-row justify-between p-2 pl-4 rounded-lg",
theme === "light" ?
"bg-zinc-50 border border-zinc-200" :
"bg-zinc-800 border border-zinc-700",
className
)}
<div className={cn("flex flex-col max-w-60", icon && "ml-4", actionButton)}>
<span className={cn("text-sm font-medium text-nowrap truncate",
theme === "light" ? "text-zinc-600" : "text-zinc-100",
titleClassname
{...props}
>
<div className={cn("flex flex-row items-center space-x-2", closeButton && "mr-2")}>
{icon && (
<div className={cn(iconClassname, theme === "light" ? "text-zinc-600" : "text-zinc-100",)}>
{icon}
</div>
)}
>
{title}
</span>
{secondTitle && secondTitle.trim() !== "" && (
<span className={cn("text-xs", theme === "light" ? "text-zinc-400" : "text-zinc-300", secondTitleClassname)}>
{secondTitle}
<div className={cn("flex flex-col max-w-60", icon && "ml-4", actionButton)}>
<span className={cn("text-sm font-medium text-nowrap truncate",
theme === "light" ? "text-zinc-600" : "text-zinc-100",
titleClassname
)}
>
{title}
</span>
)}
{secondTitle && secondTitle.trim() !== "" && (
<span className={cn("text-xs", theme === "light" ? "text-zinc-400" : "text-zinc-300", secondTitleClassname)}>
{secondTitle}
</span>
)}
</div>
{actionButton &&
<button className={cn("h-max py-1 px-2 rounded-lg text-xs",
theme === "light" ?
"bg-zinc-100 border border-zinc-200 text-zinc-500 hover:bg-zinc-200" :
"bg-zinc-800 border border-zinc-700 text-zinc-300 hover:bg-zinc-700",
actionButtonClassname
)}
onClick={onAction}>
{actionButtonText || "Undo"}
</button>
}
</div>
{actionButton &&
<button className={cn("h-max py-1 px-2 rounded-lg text-xs",
theme === "light" ?
"bg-zinc-100 border border-zinc-200 text-zinc-500 hover:bg-zinc-200" :
"bg-zinc-800 border border-zinc-700 text-zinc-300 hover:bg-zinc-700",
actionButtonClassname
{closeButton &&
<div className={cn("h-max p-0.5 rounded-lg cursor-pointer",
theme === "light" ? "hover:bg-zinc-200" : "hover:bg-zinc-700",
closeDivClassname
)}
onClick={onAction}>
{actionButtonText || "Undo"}
</button>
onClick={() => setVisible(false)}
>
<X size={16} className={cn(theme === "light" ? "text-zinc-500" : "text-zinc-400", closeClassname)}/>
</div>
}
</div>
{closeButton &&
<div className={cn("h-max p-0.5 rounded-lg cursor-pointer",
theme === "light" ? "hover:bg-zinc-200" : "hover:bg-zinc-700",
closeDivClassname
)}
onClick={() => setVisible(false)}
>
<X size={16} className={cn(theme === "light" ? "text-zinc-500" : "text-zinc-400", closeClassname)}/>
</div>
}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
)}
</AnimatePresence>
);
};

Expand Down
65 changes: 34 additions & 31 deletions src/component/toaster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React, {createContext, ReactNode, useCallback, useContext, useMemo, useRef, useState} from 'react';
import {Position, positionClasses, Toast, ToastProps} from './toast';
import {AnimatePresence, motion} from "framer-motion";
import { ToastPortal } from './toastportal';

interface ToastContextType {
addToast: (props: Omit<ToastProps, 'id'>) => string;
Expand Down Expand Up @@ -84,37 +85,39 @@ export const Toaster: React.FC<ToasterProps> = ({ children, layout = "stack" })
return (
<ToastContext.Provider value={{ addToast, removeToast }}>
{children}
{Object.entries(groupedToasts).map(([position, positionToasts]) => (
<motion.div
key={position}
className={`fixed z-50 flex flex-col ${positionClasses(position as Position)}`}
variants={containerVariants}
initial="initial"
whileHover="hover"
whileTap="hover"
onMouseEnter={() => setIsPaused(true)}
onMouseLeave={() => setIsPaused(false)}
>
<AnimatePresence>
{positionToasts.map((toast) => {
return(
<motion.div
key={toast.id}
layout
variants={layout === "stack" ? childStackVariants : childExpandVariants}
custom={position as Position}
>
<Toast key={toast.id}
removeToast={removeToast}
isPaused={isPaused}
{...toast}
/>
</motion.div>
)
})}
</AnimatePresence>
</motion.div>
))}
<ToastPortal>
{Object.entries(groupedToasts).map(([position, positionToasts]) => (
<motion.div
key={position}
className={`fixed z-50 flex flex-col ${positionClasses(position as Position)}`}
variants={containerVariants}
initial="initial"
whileHover="hover"
whileTap="hover"
onMouseEnter={() => setIsPaused(true)}
onMouseLeave={() => setIsPaused(false)}
>
<AnimatePresence>
{positionToasts.map((toast) => {
return(
<motion.div
key={toast.id}
layout
variants={layout === "stack" ? childStackVariants : childExpandVariants}
custom={position as Position}
>
<Toast key={toast.id}
removeToast={removeToast}
isPaused={isPaused}
{...toast}
/>
</motion.div>
)
})}
</AnimatePresence>
</motion.div>
))}
</ToastPortal>
</ToastContext.Provider>
);
};
Expand Down
30 changes: 30 additions & 0 deletions src/component/toastportal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import ReactDOM from 'react-dom';
import React, {ReactNode, useEffect, useState} from 'react';

interface ToastPortalProps {
children: ReactNode;
}

export const ToastPortal: React.FC<ToastPortalProps> = ({ children }) => {
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null);

useEffect(() => {
let element = document.getElementById('toast-portal-root');
if (!element) {
element = document.createElement('div');
element.id = 'toast-portal-root';
document.body.appendChild(element);
}
setPortalElement(element);

return () => {
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
};
}, []);

if (!portalElement) return null;

return ReactDOM.createPortal(children, portalElement);
};

0 comments on commit 102ad13

Please sign in to comment.