diff --git a/playground/src/components/App.tsx b/playground/src/components/App.tsx
index acde3d51..485404a0 100644
--- a/playground/src/components/App.tsx
+++ b/playground/src/components/App.tsx
@@ -1,263 +1,17 @@
-/**
- * The playground could use some love 💖. To the brave soul reading this
- * message, any help would be appreciated 🙏
- *
- * The code is full of bad assertion 😆
- */
+import { ToastContainer, toast } from '../../../src';
-import { Checkbox } from './Checkbox';
-import { ContainerCode, ContainerCodeProps } from './ContainerCode';
-import { Header } from './Header';
-import { Radio } from './Radio';
-import { ToastCode, ToastCodeProps } from './ToastCode';
-import { flags, positions, themes, transitions, typs } from './constants';
+function App() {
+ return (
+
- By default, all toasts will inherit ToastContainer's props. Props defined on toast supersede
- ToastContainer's props. Props marked with * can only be set on the ToastContainer. The demo is not
- exhaustive, check the repo for more!
-
-
-
-
Position
-
-
-
-
-
-
Type
-
-
-
-
-
-
Options
-
-
-
-
-
-
-
-
{this.renderFlags()}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
+
+
+
+
+ );
}
export { App };
diff --git a/src/components/ToastContainer.tsx b/src/components/ToastContainer.tsx
index a3d22b2c..94d2adac 100644
--- a/src/components/ToastContainer.tsx
+++ b/src/components/ToastContainer.tsx
@@ -32,6 +32,10 @@ export function ToastContainer(props: ToastContainerProps) {
};
const stacked = props.stacked;
const [collapsed, setIsCollapsed] = useState(true);
+ const [visualViewportState, setVisualViewportState] = useState<{
+ offsetTop: number;
+ offsetLeft: number;
+ } | null>(null);
const containerRef = useRef(null);
const { getToastToRender, isToastActive, count } = useToastContainer(containerProps);
const { className, style, rtl, containerId, hotKeys } = containerProps;
@@ -58,6 +62,14 @@ export function ToastContainer(props: ToastContainerProps) {
}
}
+ function isIOSMobile() {
+ if (typeof navigator === 'undefined') return false;
+ const ua = navigator.userAgent;
+ const iOS = /iPad|iPhone|iPod/.test(ua);
+ const iPadOS13Plus = ua.includes('Mac') && 'ontouchend' in document;
+ return iOS || iPadOS13Plus;
+ }
+
useIsomorphicLayoutEffect(() => {
if (stacked) {
const nodes = containerRef.current!.querySelectorAll('[data-in="true"]');
@@ -103,11 +115,35 @@ export function ToastContainer(props: ToastContainerProps) {
}
document.addEventListener('keydown', focusFirst);
+ return () => document.removeEventListener('keydown', focusFirst);
+ }, [hotKeys]);
+
+ useEffect(() => {
+ if (!isIOSMobile()) return;
+ const vv = window.visualViewport;
+ if (!vv) return;
+
+ const update = () => {
+ // offsetTop: distance between the top of the visual viewport and the top of the layout viewport.
+ // offsetLeft: same for horizontal axis.
+ // These change when the keyboard opens or the user scrolls on iOS.
+ // By applying these as a CSS translate we re-anchor `position: fixed` elements
+ // (which are fixed to the layout viewport on iOS) back to the visible screen.
+ setVisualViewportState({
+ offsetTop: vv.offsetTop,
+ offsetLeft: vv.offsetLeft
+ });
+ };
+
+ vv.addEventListener('resize', update);
+ vv.addEventListener('scroll', update);
+ update();
return () => {
- document.removeEventListener('keydown', focusFirst);
+ vv.removeEventListener('resize', update);
+ vv.removeEventListener('scroll', update);
};
- }, [hotKeys]);
+ }, []);
return (
{getToastToRender((position, toastList) => {
- const containerStyle: React.CSSProperties = !toastList.length
- ? { ...style, pointerEvents: 'none' }
- : { ...style };
+ const isTop = position.includes('top');
+ const isCenter = position.includes('center');
+
+ let containerStyle: React.CSSProperties = {
+ ...style,
+ ...(toastList.length ? {} : { pointerEvents: 'none' })
+ };
+
+ if (isTop && visualViewportState) {
+ const { offsetTop, offsetLeft } = visualViewportState;
+ // Translate the container by the visual viewport's offset.
+ // This counteracts iOS's behaviour of fixing elements to the layout viewport
+ // so the toast stays pinned to the actual visible corner of the screen.
+ const existingTransform = isCenter ? 'translateX(-50%) ' : '';
+ containerStyle = {
+ ...containerStyle,
+ transform: `${existingTransform}translate(${offsetLeft}px, ${offsetTop}px)`
+ };
+ }
return (