sonner-native
Version:
An opinionated toast component for React Native. A port of @emilkowalski's sonner.
275 lines (273 loc) • 9.43 kB
JavaScript
;
import * as React from 'react';
import { Platform } from 'react-native';
import { FullWindowOverlay } from 'react-native-screens';
import { toastDefaultValues } from "./constants.js";
import { ToastContext } from "./context.js";
import { Positioner } from "./positioner.js";
import { Toast } from "./toast.js";
import { areToastsEqual } from "./toast-comparator.js";
import { ANIMATION_DURATION } from "./animations.js";
import { jsx as _jsx } from "react/jsx-runtime";
import { createElement as _createElement } from "react";
let addToastHandler;
let dismissToastHandler;
let wiggleHandler;
export const Toaster = ({
ToasterOverlayWrapper,
...toasterProps
}) => {
const toastsCounter = React.useRef(1);
const toastRefs = React.useRef({});
const [toasts, setToasts] = React.useState([]);
const [toastsVisible, setToastsVisible] = React.useState(false);
React.useLayoutEffect(() => {
if (toasts.length > 0) {
setToastsVisible(true);
return;
}
// let the animation finish
const timeout = setTimeout(() => {
setToastsVisible(false);
}, ANIMATION_DURATION);
return () => clearTimeout(timeout);
}, [toasts.length]);
const props = React.useMemo(() => {
return {
...toasterProps,
toasts,
setToasts,
toastsCounter,
toastRefs
};
}, [toasterProps, toasts]);
if (!toastsVisible) {
return /*#__PURE__*/_jsx(ToasterUI, {
...props
});
}
if (ToasterOverlayWrapper) {
return /*#__PURE__*/_jsx(ToasterOverlayWrapper, {
children: /*#__PURE__*/_jsx(ToasterUI, {
...props
})
});
}
if (Platform.OS === 'ios') {
return /*#__PURE__*/_jsx(FullWindowOverlay, {
children: /*#__PURE__*/_jsx(ToasterUI, {
...props
})
});
}
return /*#__PURE__*/_jsx(ToasterUI, {
...props
});
};
export const ToasterUI = ({
duration = toastDefaultValues.duration,
position = toastDefaultValues.position,
offset = toastDefaultValues.offset,
visibleToasts = toastDefaultValues.visibleToasts,
swipeToDismissDirection = toastDefaultValues.swipeToDismissDirection,
closeButton,
invert,
toastOptions = {},
icons,
pauseWhenPageIsHidden,
gap,
theme,
autoWiggleOnUpdate,
richColors,
toasts,
setToasts,
toastsCounter,
toastRefs,
ToastWrapper,
positionerStyle,
...props
}) => {
addToastHandler = React.useCallback(options => {
const id = typeof options?.id === 'number' || options.id && options.id?.length > 0 ? options.id : toastsCounter.current++;
setToasts(currentToasts => {
const newToast = {
...options,
id: options?.id ?? id,
variant: options.variant ?? toastDefaultValues.variant
};
const existingToast = currentToasts.find(currentToast => currentToast.id === newToast.id);
const shouldUpdate = existingToast && options?.id;
if (shouldUpdate) {
const shouldWiggle = autoWiggleOnUpdate === 'always' || autoWiggleOnUpdate === 'toast-change' && !areToastsEqual(newToast, existingToast);
if (shouldWiggle && options.id) {
wiggleHandler(options.id);
}
return currentToasts.map(currentToast => {
if (currentToast.id === options.id) {
return {
...currentToast,
...newToast,
duration: options.duration ?? duration,
id: options.id
};
}
return currentToast;
});
} else {
const newToasts = [...currentToasts, newToast];
if (!(newToast.id in toastRefs.current)) {
toastRefs.current[newToast.id] = /*#__PURE__*/React.createRef();
}
if (newToasts.length > visibleToasts) {
newToasts.shift();
}
return newToasts;
}
});
return id;
}, [toastsCounter, toastRefs, visibleToasts, duration, autoWiggleOnUpdate, setToasts]);
const dismissToast = React.useCallback((id, origin) => {
if (!id) {
toasts.forEach(currentToast => {
if (origin === 'onDismiss') {
currentToast.onDismiss?.(currentToast.id);
} else {
currentToast.onAutoClose?.(currentToast.id);
}
});
setToasts([]);
toastsCounter.current = 1;
return;
}
setToasts(currentToasts => currentToasts.filter(currentToast => currentToast.id !== id));
const toastForCallback = toasts.find(currentToast => currentToast.id === id);
if (origin === 'onDismiss') {
toastForCallback?.onDismiss?.(id);
} else {
toastForCallback?.onAutoClose?.(id);
}
return id;
}, [setToasts, toasts, toastsCounter]);
dismissToastHandler = React.useCallback(id => {
return dismissToast(id);
}, [dismissToast]);
wiggleHandler = React.useCallback(id => {
const toastRef = toastRefs.current[id];
if (toastRef && toastRef.current) {
toastRef.current.wiggle();
}
}, [toastRefs]);
const {
unstyled
} = toastOptions;
const value = React.useMemo(() => ({
duration: duration ?? toastDefaultValues.duration,
position: position ?? toastDefaultValues.position,
offset: offset ?? toastDefaultValues.offset,
swipeToDismissDirection: swipeToDismissDirection ?? toastDefaultValues.swipeToDismissDirection,
closeButton: closeButton ?? toastDefaultValues.closeButton,
unstyled: unstyled ?? toastDefaultValues.unstyled,
addToast: addToastHandler,
invert: invert ?? toastDefaultValues.invert,
icons: icons ?? {},
pauseWhenPageIsHidden: pauseWhenPageIsHidden ?? toastDefaultValues.pauseWhenPageIsHidden,
gap: gap ?? toastDefaultValues.gap,
theme: theme ?? toastDefaultValues.theme,
toastOptions,
autoWiggleOnUpdate: autoWiggleOnUpdate ?? toastDefaultValues.autoWiggleOnUpdate,
richColors: richColors ?? toastDefaultValues.richColors
}), [duration, position, offset, swipeToDismissDirection, closeButton, unstyled, invert, icons, pauseWhenPageIsHidden, gap, theme, toastOptions, autoWiggleOnUpdate, richColors]);
const orderToastsFromPosition = React.useCallback(currentToasts => {
return position === 'bottom-center' ? currentToasts : currentToasts.slice().reverse();
}, [position]);
const onDismiss = React.useCallback(id => {
dismissToast(id, 'onDismiss');
}, [dismissToast]);
const onAutoClose = React.useCallback(id => {
dismissToast(id, 'onAutoClose');
}, [dismissToast]);
const allPositions = React.useMemo(() => {
return ['top-center', 'bottom-center', 'center'];
}, []);
const possiblePositions = React.useMemo(() => {
return allPositions.filter(possiblePossition => {
return toasts.find(positionedToast => positionedToast.position === possiblePossition) || value.position === possiblePossition;
});
}, [allPositions, toasts, value.position]);
const orderedToasts = React.useMemo(() => {
return orderToastsFromPosition(toasts);
}, [toasts, orderToastsFromPosition]);
return /*#__PURE__*/_jsx(ToastContext.Provider, {
value: value,
children: possiblePositions.map((currentPosition, positionIndex) => /*#__PURE__*/_jsx(Positioner, {
style: positionerStyle,
position: currentPosition,
children: orderedToasts.filter(possibleToast => !possibleToast.position && positionIndex === 0 || possibleToast.position === currentPosition).map(toastToRender => {
const ToastToRender = /*#__PURE__*/_createElement(Toast, {
...props,
...toastToRender,
style: {
...props.style,
...toastToRender.style
},
styles: {
toastContainer: {
...props.styles?.toastContainer,
...toastToRender.styles?.toastContainer
},
toast: {
...props.styles?.toast,
...toastToRender.styles?.toast
},
toastContent: {
...props.styles?.toastContent,
...toastToRender.styles?.toastContent
},
title: {
...props.styles?.title,
...toastToRender.styles?.title
},
description: {
...props.styles?.description,
...toastToRender.styles?.description
},
buttons: {
...props.styles?.buttons,
...toastToRender.styles?.buttons
},
closeButton: {
...props.styles?.closeButton,
...toastToRender.styles?.closeButton
},
closeButtonIcon: {
...props.styles?.closeButtonIcon,
...toastToRender.styles?.closeButtonIcon
}
},
onDismiss: onDismiss,
onAutoClose: onAutoClose,
ref: toastRefs.current[toastToRender.id],
key: toastToRender.id
});
if (ToastWrapper) {
return /*#__PURE__*/_jsx(ToastWrapper, {
toastId: toastToRender.id,
children: ToastToRender
}, toastToRender.id);
}
return ToastToRender;
})
}, currentPosition))
});
};
export const getToastContext = () => {
if (!addToastHandler || !dismissToastHandler || !wiggleHandler) {
throw new Error('ToastContext is not initialized');
}
return {
addToast: addToastHandler,
dismissToast: dismissToastHandler,
wiggleToast: wiggleHandler
};
};
//# sourceMappingURL=toaster.js.map