diff --git a/src/index.tsx b/src/index.tsx index 1c411a2..f4f16c0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -22,8 +22,9 @@ const VISIBLE_TOASTS_AMOUNT = 3; // Viewport padding const VIEWPORT_OFFSET = '32px'; -const MOBILE_VIEWPORT_X_OFFSET = '16px'; -const MOBILE_VIEWPORT_Y_OFFSET = '20px'; + +// Mobile viewport padding +const MOBILE_VIEWPORT_OFFSET = '16px'; // Default lifetime of a toasts (in ms) const TOAST_LIFETIME = 4000; @@ -448,6 +449,38 @@ function getDocumentDirection(): ToasterProps['dir'] { return dirAttribute as ToasterProps['dir']; } +function assignOffset(defaultOffset: ToasterProps['offset'], mobileOffset: ToasterProps['mobileOffset']) { + const styles = {} as React.CSSProperties; + + [defaultOffset, mobileOffset].forEach((offset, index) => { + const isMobile = index === 1; + const prefix = isMobile ? '--mobile-offset' : '--offset'; + const defaultValue = isMobile ? MOBILE_VIEWPORT_OFFSET : VIEWPORT_OFFSET; + + function assignAll(offset: string | number) { + ['top', 'right', 'bottom', 'left'].forEach((key) => { + styles[`${prefix}-${key}`] = typeof offset === 'number' ? `${offset}px` : offset; + }); + } + + if (typeof offset === 'number' || typeof offset === 'string') { + assignAll(offset); + } else if (typeof offset === 'object') { + ['top', 'right', 'bottom', 'left'].forEach((key) => { + if (offset[key] === undefined) { + styles[`${prefix}-${key}`] = defaultValue; + } else { + styles[`${prefix}-${key}`] = typeof offset[key] === 'number' ? `${offset[key]}px` : offset[key]; + } + }); + } else { + assignAll(defaultValue); + } + }); + + return styles; +} + function useSonner() { const [activeToasts, setActiveToasts] = React.useState([]); @@ -484,8 +517,7 @@ const Toaster = forwardRef(function Toaster(props, re closeButton, className, offset, - mobileXOffset, - mobileYOffset, + mobileOffset, theme = 'light', richColors, duration, @@ -679,14 +711,10 @@ const Toaster = forwardRef(function Toaster(props, re style={ { '--front-toast-height': `${heights[0]?.height || 0}px`, - '--offset': typeof offset === 'number' ? `${offset}px` : offset || VIEWPORT_OFFSET, - '--mobile-x-offset': - typeof mobileXOffset === 'number' ? `${mobileXOffset}px` : mobileXOffset || MOBILE_VIEWPORT_X_OFFSET, - '--mobile-y-offset': - typeof mobileYOffset === 'number' ? `${mobileYOffset}px` : mobileYOffset || MOBILE_VIEWPORT_Y_OFFSET, '--width': `${TOAST_WIDTH}px`, '--gap': `${gap}px`, ...style, + ...assignOffset(offset, mobileOffset), } as React.CSSProperties } onBlur={(event) => { diff --git a/src/styles.css b/src/styles.css index 596acea..913b7cb 100644 --- a/src/styles.css +++ b/src/styles.css @@ -62,11 +62,11 @@ } :where([data-sonner-toaster][data-x-position='right']) { - right: max(var(--offset), env(safe-area-inset-right)); + right: var(--offset-right); } :where([data-sonner-toaster][data-x-position='left']) { - left: max(var(--offset), env(safe-area-inset-left)); + left: var(--offset-left); } :where([data-sonner-toaster][data-x-position='center']) { @@ -75,11 +75,11 @@ } :where([data-sonner-toaster][data-y-position='top']) { - top: max(var(--offset), env(safe-area-inset-top)); + top: var(--offset-top); } :where([data-sonner-toaster][data-y-position='bottom']) { - bottom: max(var(--offset), env(safe-area-inset-bottom)); + bottom: var(--offset-bottom); } :where([data-sonner-toast]) { @@ -369,36 +369,36 @@ @media (max-width: 600px) { [data-sonner-toaster] { position: fixed; - right: var(--mobile-x-offset); - left: var(--mobile-x-offset); + right: var(--mobile-offset-right); + left: var(--mobile-offset-left); width: 100%; } [data-sonner-toaster][dir='rtl'] { - left: calc(var(--mobile-x-offset) * -1); + left: calc(var(--mobile-offset-left) * -1); } [data-sonner-toaster] [data-sonner-toast] { left: 0; right: 0; - width: calc(100% - var(--mobile-x-offset) * 2); + width: calc(100% - var(--mobile-offset-left) * 2); } [data-sonner-toaster][data-x-position='left'] { - left: var(--mobile-x-offset); + left: var(--mobile-offset-left); } [data-sonner-toaster][data-y-position='bottom'] { - bottom: max(var(--mobile-y-offset), env(safe-area-inset-bottom)); + bottom: var(--mobile-offset-bottom); } [data-sonner-toaster][data-y-position='top'] { - top: max(var(--mobile-y-offset), env(safe-area-inset-top)); + top: var(--mobile-offset-top); } [data-sonner-toaster][data-x-position='center'] { - left: var(--mobile-x-offset); - right: var(--mobile-x-offset); + left: var(--mobile-offset-left); + right: var(--mobile-offset-right); transform: none; } } diff --git a/src/types.ts b/src/types.ts index ff195b4..e80d64a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,6 +105,16 @@ interface ToastOptions { type CnFunction = (...classes: Array) => string; +type Offset = + | { + top?: string | number; + right?: string | number; + bottom?: string | number; + left?: string | number; + } + | string + | number; + export interface ToasterProps { invert?: boolean; theme?: 'light' | 'dark' | 'system'; @@ -119,9 +129,8 @@ export interface ToasterProps { toastOptions?: ToastOptions; className?: string; style?: React.CSSProperties; - offset?: string | number; - mobileXOffset?: string | number; - mobileYOffset?: string | number; + offset?: Offset; + mobileOffset?: Offset; dir?: 'rtl' | 'ltr' | 'auto'; /** * @deprecated Please use the `icons` prop instead: diff --git a/website/src/pages/toaster.mdx b/website/src/pages/toaster.mdx index c01ac4f..84f0991 100644 --- a/website/src/pages/toaster.mdx +++ b/website/src/pages/toaster.mdx @@ -58,8 +58,7 @@ Changes the directionality of the toast's text. | position | Place where the toasts will be rendered | `bottom-right` | | closeButton | Adds a close button to all toasts | `false` | | offset | Offset from the edges of the screen. | `32px` | -| mobileXOffset | Offset from the left/right edges of the screen on mobile. | `16px` | -| mobileYOffset | Offset from the top/bottom edges of the screen on mobile. | `20px` | +| mobileOffset | Offset from the left/right edges of the screen on screens with width smaller than 600px. | `16px` | | dir | Directionality of toast's text | `ltr` | | hotkey | Keyboard shortcut that will move focus to the toaster area. | `⌥/alt + T` | | invert | Dark toasts in light mode and vice versa. | `false` |