Skip to content

Commit

Permalink
Add loading icon prop (#219)
Browse files Browse the repository at this point in the history
* Add loading icon prop

* Update docs

* Tweak implementation
  • Loading branch information
emilkowalski authored Nov 5, 2023
1 parent 8a17c96 commit f8c7128
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 61 deletions.
19 changes: 16 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ interface ToastProps {
className?: string;
unstyled?: boolean;
descriptionClassName?: string;
loadingIcon?: React.ReactNode;
}

const Toast = (props: ToastProps) => {
Expand All @@ -68,6 +69,7 @@ const Toast = (props: ToastProps) => {
duration: durationFromToaster,
position,
gap = GAP,
loadingIcon: loadingIconProp,
expandByDefault,
} = props;
const [mounted, setMounted] = React.useState(false);
Expand Down Expand Up @@ -206,6 +208,17 @@ const Toast = (props: ToastProps) => {
}
}, [deleteToast, toast.delete]);

function getLoadingIcon() {
if (loadingIconProp) {
return (
<div className="loader" data-visible={toastType === 'loading'}>
{loadingIconProp}
</div>
);
}
return <Loader visible={toastType === 'loading'} />;
}

return (
<li
aria-live={toast.important ? 'assertive' : 'polite'}
Expand Down Expand Up @@ -327,9 +340,7 @@ const Toast = (props: ToastProps) => {
<>
{toastType || toast.icon || toast.promise ? (
<div data-icon="">
{(toast.promise || toast.type === 'loading') && !toast.icon ? (
<Loader visible={toastType === 'loading'} />
) : null}
{(toast.promise || toast.type === 'loading') && !toast.icon ? getLoadingIcon() : null}
{toast.icon || getAsset(toastType)}
</div>
) : null}
Expand Down Expand Up @@ -404,6 +415,7 @@ const Toaster = (props: ToasterProps) => {
toastOptions,
dir = getDocumentDirection(),
gap,
loadingIcon,
} = props;
const [toasts, setToasts] = React.useState<ToastT[]>([]);
const possiblePositions = React.useMemo(() => {
Expand Down Expand Up @@ -618,6 +630,7 @@ const Toaster = (props: ToasterProps) => {
setHeights={setHeights}
expandByDefault={expand}
gap={gap}
loadingIcon={loadingIcon}
expanded={expanded}
/>
))}
Expand Down
91 changes: 35 additions & 56 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ html[dir='rtl'],
[data-sonner-toaster] {
position: fixed;
width: var(--width);
font-family: ui-sans-serif, system-ui, -apple-system,
BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue,
Arial, Noto Sans, sans-serif, Apple Color Emoji,
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial,
Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
--gray1: hsl(0, 0%, 99%);
--gray2: hsl(0, 0%, 97.3%);
--gray3: hsl(0, 0%, 95.1%);
Expand Down Expand Up @@ -83,8 +81,7 @@ html[dir='rtl'],
/* https://stackoverflow.com/questions/48124372/pointermove-event-not-working-with-touch-why-not */
touch-action: none;
will-change: transform, opacity, height;
transition: transform 400ms, opacity 400ms, height 400ms,
box-shadow 200ms;
transition: transform 400ms, opacity 400ms, height 400ms, box-shadow 200ms;
box-sizing: border-box;
outline: none;
overflow-wrap: anywhere;
Expand All @@ -105,8 +102,7 @@ html[dir='rtl'],
}

[data-sonner-toast]:focus-visible {
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1),
0 0 0 2px rgba(0, 0, 0, 0.2);
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(0, 0, 0, 0.2);
}

[data-sonner-toast][data-y-position='top'] {
Expand Down Expand Up @@ -222,13 +218,11 @@ html[dir='rtl'],
opacity: 0;
cursor: pointer;
z-index: 1;
transition: opacity 100ms, background 200ms,
border-color 200ms;
transition: opacity 100ms, background 200ms, border-color 200ms;
}

[data-sonner-toast] [data-close-button]:focus-visible {
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1),
0 0 0 2px rgba(0, 0, 0, 0.2);
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(0, 0, 0, 0.2);
}

[data-sonner-toast] [data-disabled='true'] {
Expand Down Expand Up @@ -296,19 +290,15 @@ html[dir='rtl'],

[data-sonner-toast][data-expanded='false'][data-front='false'] {
--scale: var(--toasts-before) * 0.05 + 1;
--y: translateY(
calc(var(--lift-amount) * var(--toasts-before))
)
scale(calc(-1 * var(--scale)));
--y: translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));
height: var(--front-toast-height);
}

[data-sonner-toast] > * {
transition: opacity 400ms;
}

[data-sonner-toast][data-expanded='false'][data-front='false'][data-styled='true']
> * {
[data-sonner-toast][data-expanded='false'][data-front='false'][data-styled='true'] > * {
opacity: 0;
}

Expand All @@ -328,9 +318,7 @@ html[dir='rtl'],
}

[data-sonner-toast][data-removed='true'][data-front='false'][data-swipe-out='false'][data-expanded='true'] {
--y: translateY(
calc(var(--lift) * var(--offset) + var(--lift) * -100%)
);
--y: translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));
opacity: 0;
}

Expand All @@ -357,21 +345,12 @@ html[dir='rtl'],

@keyframes swipe-out {
from {
transform: translateY(
calc(
var(--lift) * var(--offset) + var(--swipe-amount)
)
);
transform: translateY(calc(var(--lift) * var(--offset) + var(--swipe-amount)));
opacity: 1;
}

to {
transform: translateY(
calc(
var(--lift) * var(--offset) + var(--swipe-amount) +
var(--lift) * -100%
)
);
transform: translateY(calc(var(--lift) * var(--offset) + var(--swipe-amount) + var(--lift) * -100%));
opacity: 0;
}
}
Expand Down Expand Up @@ -432,15 +411,13 @@ html[dir='rtl'],
--error-text: hsl(360, 100%, 45%);
}

[data-sonner-toaster][data-theme='light']
[data-sonner-toast][data-invert='true'] {
[data-sonner-toaster][data-theme='light'] [data-sonner-toast][data-invert='true'] {
--normal-bg: #000;
--normal-border: hsl(0, 0%, 20%);
--normal-text: var(--gray1);
}

[data-sonner-toaster][data-theme='dark']
[data-sonner-toast][data-invert='true'] {
[data-sonner-toaster][data-theme='dark'] [data-sonner-toast][data-invert='true'] {
--normal-bg: #fff;
--normal-border: var(--gray3);
--normal-text: var(--gray12);
Expand Down Expand Up @@ -468,61 +445,49 @@ html[dir='rtl'],
--error-text: hsl(358, 100%, 81%);
}

[data-rich-colors='true']
[data-sonner-toast][data-type='success'] {
[data-rich-colors='true'] [data-sonner-toast][data-type='success'] {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}

[data-rich-colors='true']
[data-sonner-toast][data-type='success']
[data-close-button] {
[data-rich-colors='true'] [data-sonner-toast][data-type='success'] [data-close-button] {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}

[data-rich-colors='true']
[data-sonner-toast][data-type='info'] {
[data-rich-colors='true'] [data-sonner-toast][data-type='info'] {
background: var(--info-bg);
border-color: var(--info-border);
color: var(--info-text);
}

[data-rich-colors='true']
[data-sonner-toast][data-type='info']
[data-close-button] {
[data-rich-colors='true'] [data-sonner-toast][data-type='info'] [data-close-button] {
background: var(--info-bg);
border-color: var(--info-border);
color: var(--info-text);
}

[data-rich-colors='true']
[data-sonner-toast][data-type='warning'] {
[data-rich-colors='true'] [data-sonner-toast][data-type='warning'] {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}

[data-rich-colors='true']
[data-sonner-toast][data-type='warning']
[data-close-button] {
[data-rich-colors='true'] [data-sonner-toast][data-type='warning'] [data-close-button] {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}

[data-rich-colors='true']
[data-sonner-toast][data-type='error'] {
[data-rich-colors='true'] [data-sonner-toast][data-type='error'] {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}

[data-rich-colors='true']
[data-sonner-toast][data-type='error']
[data-close-button] {
[data-rich-colors='true'] [data-sonner-toast][data-type='error'] [data-close-button] {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
Expand Down Expand Up @@ -661,3 +626,17 @@ html[dir='rtl'],
animation: none !important;
}
}

.loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transform-origin: center;
transition: opacity 200ms, transform 200ms;
}

.loader[data-visible='false'] {
opacity: 0;
transform: scale(0.8) translate(-50%, -50%);
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface ToasterProps {
style?: React.CSSProperties;
offset?: string | number;
dir?: 'rtl' | 'ltr' | 'auto';
loadingIcon?: React.ReactNode;
}

export enum SwipeStateTypes {
Expand Down
4 changes: 2 additions & 2 deletions test/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ export default function Home({ searchParams }: any) {
onClick={() => {
const toastId = toast('My Unupdated Toast', {
duration: 10000,
});
toast('My Updated Toast', {
});
toast('My Updated Toast', {
id: toastId,
duration: 10000,
});
Expand Down
1 change: 1 addition & 0 deletions website/src/pages/toaster.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ Changes the directionality of the toast's text.
| invert | Dark toasts in light mode and vice versa. | `false` |
| toastOptions | These will act as default options for all toasts. See [toast()](/toast) for all available options. | `4000` |
| gap | Gap between toasts when expanded | `14` |
| loadingIcon | Changes the default loading icon | `-` |

1 comment on commit f8c7128

@vercel
Copy link

@vercel vercel bot commented on f8c7128 Nov 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.