Skip to content

Commit

Permalink
Merge pull request #15 from nhanluongoe/feaet/promise-support
Browse files Browse the repository at this point in the history
Add support for promise
  • Loading branch information
nhanluongoe authored Jan 10, 2024
2 parents aaf725c + 0611393 commit 9b37c8f
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 14 deletions.
78 changes: 75 additions & 3 deletions src/components/icons.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { styled } from 'goober';
import { keyframes, styled } from 'goober';

const StyledCheck = styled('i')`
& {
Expand Down Expand Up @@ -44,6 +44,8 @@ const StyledClose = styled('i')`
border: 2px solid transparent;
border-radius: 40px;
color: #ff4b4b;
border: 2px solid red;
border-radius: 9999px;
}
&::after,
&::before {
Expand All @@ -56,8 +58,8 @@ const StyledClose = styled('i')`
height: 3px;
background: currentColor;
transform: rotate(45deg);
top: 10px;
left: 4px;
top: 12px;
left: 3px;
}
&::after {
transform: rotate(-45deg);
Expand All @@ -66,3 +68,73 @@ const StyledClose = styled('i')`
export const Close: React.FC = () => {
return <StyledClose />;
};

const spinnerTwoAltAnimation = keyframes`
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
`;
const StyledSpinner = styled('i')`
position: relative;
&,
&::before {
box-sizing: border-box;
display: block;
width: 30px;
height: 30px;
}
&::before {
content: '';
position: absolute;
border-radius: 100px;
animation: ${spinnerTwoAltAnimation} 1s cubic-bezier(0.6, 0, 0.4, 1)
infinite;
border: 3px solid #5fbdff;
border-bottom-color: transparent;
border-top-color: transparent;
}
`;
export const Spinner: React.FC = () => {
return <StyledSpinner />;
};

const StyledWarning = styled('i')`
& {
box-sizing: border-box;
position: relative;
display: block;
width: 30px;
height: 30px;
border: 2px solid;
border-radius: 40px;
color: #f6d776;
}
&::after,
&::before {
content: '';
display: block;
box-sizing: border-box;
position: absolute;
border-radius: 3px;
width: 3px;
background: currentColor;
left: 11.5px;
color: #f6d776;
}
&::after {
top: 4px;
height: 14px;
}
&::before {
height: 3px;
bottom: 3px;
width: 3px;
border-radius: 9999px;
}
`;
export const Warning: React.FC = () => {
return <StyledWarning />;
};
8 changes: 7 additions & 1 deletion src/components/toast-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { keyframes, styled } from 'goober';
import React from 'react';
import { Toast } from '../core/types';
import { Check, Close } from './icons';
import { Check, Close, Spinner, Warning } from './icons';

interface ToastIconProps {
icon?: Toast['icon'];
Expand Down Expand Up @@ -42,6 +42,12 @@ const ToastIcon: React.FC<ToastIconProps> = (props) => {
if (type === 'error') {
return <Close />;
}
if (type === 'loading') {
return <Spinner />;
}
if (type === 'warning') {
return <Warning />;
}
return null;
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/toaster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ToastIcon from './toast-icon';

setup(React.createElement);

interface ToasterProps {
export interface ToasterProps {
position?: 'left' | 'center' | 'right';
toastOptions?: ToastsOptions;
}
Expand Down
43 changes: 39 additions & 4 deletions src/core/store.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React from 'react';
import { Toast, ToastsOptions } from './types';

export const TOAST_EXPIRE_DISMISS_DELAY = 1000;
const TOAST_LIMIT = 3;

type ActionType = {
ADD_TOAST: 'ADD_TOAST';
REMOVE_TOAST: 'REMOVE_TOAST';
DISMISS_TOAST: 'DISMISS_TOAST';
UPSERT_TOAST: 'UPSERT_TOAST';
UPDATE_TOAST: 'UPDATE_TOAST';
PAUSE: 'PAUSE';
RESUME: 'RESUME';
};
Expand All @@ -22,6 +27,14 @@ type Action =
toastId?: Toast['id'];
type: ActionType['REMOVE_TOAST'];
}
| {
type: ActionType['UPSERT_TOAST'];
toast: Toast;
}
| {
type: ActionType['UPDATE_TOAST'];
toast: Partial<Toast>;
}
| {
type: ActionType['PAUSE'];
time: number;
Expand All @@ -39,9 +52,6 @@ interface State {

const toastTimeouts = new Map<Toast['id'], ReturnType<typeof setTimeout>>();

export const TOAST_EXPIRE_DISMISS_DELAY = 0;
const TOAST_LIMIT = 3;

const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return;
Expand All @@ -58,6 +68,13 @@ const addToRemoveQueue = (toastId: string) => {
toastTimeouts.set(toastId, timeout);
};

const clearFromRemoveQueue = (toastId: string) => {
const timeout = toastTimeouts.get(toastId);
if (timeout) {
clearTimeout(timeout);
}
};

export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'ADD_TOAST': {
Expand All @@ -70,7 +87,6 @@ export const reducer = (state: State, action: Action): State => {
case 'DISMISS_TOAST': {
const { toastId } = action;

// ! Side effects ! - This could be execrated into a dismissToast() action, but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId);
} else {
Expand Down Expand Up @@ -107,6 +123,25 @@ export const reducer = (state: State, action: Action): State => {
};
}

case 'UPSERT_TOAST':
const { toast } = action;
return state.toasts.find((t) => t.id === toast.id)
? reducer(state, { type: 'UPDATE_TOAST', toast })
: reducer(state, { type: 'ADD_TOAST', toast });

case 'UPDATE_TOAST': {
if (action.toast.id) {
clearFromRemoveQueue(action.toast.id);
}

return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
};
}

case 'PAUSE': {
return {
...state,
Expand Down
7 changes: 6 additions & 1 deletion src/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React, { CSSProperties } from 'react';

export type ToasterType = 'success' | 'error' | 'default';
export type ToasterType =
| 'success'
| 'error'
| 'default'
| 'loading'
| 'warning';

export type Toast = {
id: string;
Expand Down
29 changes: 26 additions & 3 deletions src/core/use-toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { dispatch, useStore } from './store';
import {
Toast,
ToastArg,
ToastOptions,
ToasterType,
ToastsOptions,
isFunction,
} from './types';
import { genId } from './utils';

const DEFAULT_DURATION = 3 * 1000;

function createToast(type: ToasterType = 'default', arg: ToastArg): Toast {
if (isFunction(arg)) {
const id = genId();
Expand Down Expand Up @@ -45,7 +48,7 @@ function createHandler(type?: ToasterType) {
return (options: ToastArg) => {
const toast = createToast(type, options);
dispatch({
type: 'ADD_TOAST',
type: 'UPSERT_TOAST',
toast,
});
return toast.id;
Expand All @@ -55,6 +58,28 @@ function createHandler(type?: ToasterType) {
const toast = (opts: ToastArg) => createHandler('default')(opts);
toast.error = createHandler('error');
toast.success = createHandler('success');
toast.loading = createHandler('loading');
toast.warning = createHandler('warning');

toast.promise = <T>(
promise: Promise<T>,
content: {
loading: ToastOptions;
success: ToastOptions;
error: ToastOptions;
}
) => {
const id = toast.loading(content.loading);
promise
.then((p) => {
toast.success({ ...content.success, id });
return p;
})
.catch(() => {
toast.error({ ...content.error, id });
});
return promise;
};

toast.dismiss = (toastId?: string) => {
dispatch({
Expand All @@ -63,8 +88,6 @@ toast.dismiss = (toastId?: string) => {
});
};

const DEFAULT_DURATION = 3 * 1000;

const pause = () => {
dispatch({
type: 'PAUSE',
Expand Down
Loading

0 comments on commit 9b37c8f

Please sign in to comment.