mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Toast refactored to not use modals context
This commit is contained in:
parent
0b7aa5068a
commit
56ebd9dafc
16 changed files with 119 additions and 118 deletions
|
|
@ -2,7 +2,7 @@ require('spatial-navigation-polyfill');
|
|||
const React = require('react');
|
||||
const { Router } = require('stremio-router');
|
||||
const { Core, KeyboardNavigation, ServicesProvider, Shell } = require('stremio/services');
|
||||
const { Toasts } = require('stremio/common');
|
||||
const { ToastProvider } = require('stremio/common');
|
||||
const routerViewsConfig = require('./routerViewsConfig');
|
||||
const styles = require('./styles');
|
||||
|
||||
|
|
@ -51,14 +51,14 @@ const App = () => {
|
|||
<ServicesProvider services={services}>
|
||||
{
|
||||
shellInitialized && coreInitialized ?
|
||||
<Toasts.ContainerProvider className={styles['toasts-container']}>
|
||||
<ToastProvider className={styles['toasts-container']}>
|
||||
<Router
|
||||
className={styles['router']}
|
||||
homePath={'/'}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</Toasts.ContainerProvider>
|
||||
</ToastProvider>
|
||||
:
|
||||
<div className={styles['app-loader']} />
|
||||
}
|
||||
|
|
|
|||
10
src/common/Toast/ToastContext.js
Normal file
10
src/common/Toast/ToastContext.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
const React = require('react');
|
||||
|
||||
const ToastContext = React.createContext({
|
||||
show: () => { },
|
||||
clear: () => { }
|
||||
});
|
||||
|
||||
ToastContext.displayName = 'ToastContext';
|
||||
|
||||
module.exports = ToastContext;
|
||||
|
|
@ -5,7 +5,7 @@ const Icon = require('stremio-icons/dom');
|
|||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Toast = ({ type, title, message, icon, dataset, onSelect, onClose }) => {
|
||||
const ToastItem = ({ type, title, message, icon, dataset, onSelect, onClose }) => {
|
||||
const toastOnClick = React.useCallback((event) => {
|
||||
if (!event.nativeEvent.selectPrevented && typeof onSelect === 'function') {
|
||||
onSelect({
|
||||
|
|
@ -28,7 +28,7 @@ const Toast = ({ type, title, message, icon, dataset, onSelect, onClose }) => {
|
|||
}
|
||||
}, [dataset, onClose]);
|
||||
return (
|
||||
<Button className={classnames(styles['toast-container'], styles['alert'], styles[type])} tabIndex={-1} onClick={toastOnClick}>
|
||||
<Button className={classnames(styles['toast-item-container'], styles['success'], styles[type])} tabIndex={-1} onClick={toastOnClick}>
|
||||
{
|
||||
typeof icon === 'string' && icon.length > 0 ?
|
||||
<div className={styles['icon-container']}>
|
||||
|
|
@ -44,7 +44,12 @@ const Toast = ({ type, title, message, icon, dataset, onSelect, onClose }) => {
|
|||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['message-container']}>{message}</div>
|
||||
{
|
||||
typeof message === 'string' && message.length > 0 ?
|
||||
<div className={styles['message-container']}>{message}</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<Button className={styles['close-button-container']} title={'Close'} tabIndex={-1} onClick={closeButtonOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_x'} />
|
||||
|
|
@ -53,14 +58,14 @@ const Toast = ({ type, title, message, icon, dataset, onSelect, onClose }) => {
|
|||
);
|
||||
};
|
||||
|
||||
Toast.propTypes = {
|
||||
ToastItem.propTypes = {
|
||||
type: PropTypes.oneOf(['success', 'alert', 'error']),
|
||||
title: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
dataset: PropTypes.objectOf(PropTypes.string),
|
||||
dataset: PropTypes.object,
|
||||
onSelect: PropTypes.func,
|
||||
onClose: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Toast;
|
||||
module.exports = ToastItem;
|
||||
3
src/common/Toast/ToastItem/index.js
Normal file
3
src/common/Toast/ToastItem/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
const ToastItem = require('./ToastItem');
|
||||
|
||||
module.exports = ToastItem;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
.toast-container {
|
||||
.toast-item-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 25rem;
|
||||
min-height: 6rem;
|
||||
max-width: 25rem;
|
||||
margin-bottom: 1rem;
|
||||
border: thin solid;
|
||||
background-color: var(--color-surfacelighter);
|
||||
72
src/common/Toast/ToastProvider.js
Normal file
72
src/common/Toast/ToastProvider.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const ToastItem = require('./ToastItem');
|
||||
const ToastContext = require('./ToastContext');
|
||||
|
||||
const DEFAULT_TIMEOUT = 3000;
|
||||
|
||||
const ToastProvider = ({ className, children }) => {
|
||||
const [container, setContainer] = React.useState(null);
|
||||
const [items, dispatch] = React.useReducer(
|
||||
(items, action) => {
|
||||
switch (action.type) {
|
||||
case 'add':
|
||||
return items.concat(action.item);
|
||||
case 'remove':
|
||||
return items.filter((item) => item.id !== action.id);
|
||||
case 'clear':
|
||||
return [];
|
||||
default:
|
||||
return items;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
const itemOnClose = React.useCallback((event) => {
|
||||
clearTimeout(event.dataset.id);
|
||||
dispatch({ type: 'remove', id: event.dataset.id });
|
||||
}, []);
|
||||
const toast = React.useMemo(() => ({
|
||||
show: (item) => {
|
||||
const timeout = typeof item.timeout === 'number' && !isNaN(item.timeout) ?
|
||||
item.timeout
|
||||
:
|
||||
DEFAULT_TIMEOUT;
|
||||
const id = setTimeout(() => {
|
||||
dispatch({ type: 'remove', id });
|
||||
}, timeout);
|
||||
dispatch({
|
||||
type: 'add',
|
||||
item: {
|
||||
...item,
|
||||
id,
|
||||
dataset: {
|
||||
...item.dataset,
|
||||
id
|
||||
},
|
||||
onClose: itemOnClose
|
||||
}
|
||||
});
|
||||
},
|
||||
clear: () => {
|
||||
dispatch({ type: 'clear' });
|
||||
}
|
||||
}), []);
|
||||
return (
|
||||
<ToastContext.Provider value={toast}>
|
||||
{container instanceof HTMLElement ? children : null}
|
||||
<div ref={setContainer} className={className}>
|
||||
{items.map((item, index) => (
|
||||
<ToastItem key={index} {...item} />
|
||||
))}
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ToastProvider.propTypes = {
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = ToastProvider;
|
||||
7
src/common/Toast/index.js
Normal file
7
src/common/Toast/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const ToastProvider = require('./ToastProvider');
|
||||
const useToast = require('./useToast');
|
||||
|
||||
module.exports = {
|
||||
ToastProvider,
|
||||
useToast
|
||||
};
|
||||
8
src/common/Toast/useToast.js
Normal file
8
src/common/Toast/useToast.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
const React = require('react');
|
||||
const ToastContext = require('./ToastContext');
|
||||
|
||||
const useToast = () => {
|
||||
return React.useContext(ToastContext);
|
||||
};
|
||||
|
||||
module.exports = useToast;
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
const Toast = require('./Toast');
|
||||
|
||||
module.exports = Toast;
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
const React = require('react');
|
||||
const { Modal } = require('stremio-router');
|
||||
const { ModalsContainerContext } = require('stremio-router');
|
||||
const { useToastsContainer, ToastsContainerProvider } = require('./ToastsContainerContext');
|
||||
const Toast = require('./Toast');
|
||||
|
||||
const DEFAULT_TIMEOUT = 2000;
|
||||
|
||||
const Toasts = React.forwardRef(({ className }, ref) => {
|
||||
const toastsContainer = useToastsContainer();
|
||||
const [toasts, dispatch] = React.useReducer(
|
||||
(state, action) => {
|
||||
switch (action.type) {
|
||||
case 'add':
|
||||
return state.concat([action.item]);
|
||||
case 'remove':
|
||||
return state.filter(item => item !== action.item);
|
||||
case 'removeAll':
|
||||
state.forEach(item => clearTimeout(item.timerId));
|
||||
return [];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}, []
|
||||
);
|
||||
const hideAll = React.useCallback(() => {
|
||||
dispatch({ type: 'removeAll' });
|
||||
}, []);
|
||||
const show = React.useCallback(({ type, icon, title, text, closeButton, timeout, onClick }) => {
|
||||
timeout = timeout !== null && !isNaN(timeout) ? timeout : DEFAULT_TIMEOUT;
|
||||
const close = () => {
|
||||
clearTimeout(newItem.timerId);
|
||||
dispatch({ type: 'remove', item: newItem });
|
||||
};
|
||||
|
||||
const newItem = { type, icon, title, text, closeButton, timeout, onClick, onClose: close };
|
||||
|
||||
if (timeout !== 0) {
|
||||
newItem.timerId = setTimeout(close, timeout);
|
||||
}
|
||||
dispatch({ type: 'add', item: newItem });
|
||||
return close;
|
||||
}, []);
|
||||
React.useImperativeHandle(ref, () => ({ show, hideAll }));
|
||||
|
||||
return toasts.length === 0 ? null : (
|
||||
<ModalsContainerContext.Provider value={toastsContainer}>
|
||||
<Modal className={className} disabled={true}>
|
||||
{toasts.map((toast, index) => (<Toast {...toast} key={index} />))}
|
||||
</Modal>
|
||||
</ModalsContainerContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
Toasts.ContainerProvider = ToastsContainerProvider;
|
||||
|
||||
module.exports = Toasts;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
const React = require('react');
|
||||
|
||||
const ToastsContainerContext = React.createContext(null);
|
||||
|
||||
ToastsContainerContext.displayName = 'ToastsContainerContext';
|
||||
|
||||
module.exports = ToastsContainerContext;
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const ToastsContainerContext = require('./ToastsContainerContext');
|
||||
|
||||
const ToastsContainerProvider = ({ className, children }) => {
|
||||
const [container, setContainer] = React.useState(null);
|
||||
return (
|
||||
<ToastsContainerContext.Provider value={container}>
|
||||
{container instanceof HTMLElement ? children : null}
|
||||
<div ref={setContainer} className={className} />
|
||||
</ToastsContainerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ToastsContainerProvider.propTypes = {
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = ToastsContainerProvider;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
const ToastsContainerProvider = require('./ToastsContainerProvider');
|
||||
const useToastsContainer = require('./useToastsContainer');
|
||||
|
||||
module.exports = {
|
||||
ToastsContainerProvider,
|
||||
useToastsContainer
|
||||
};
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
const React = require('react');
|
||||
const ToastsContainerContext = require('./ToastsContainerContext');
|
||||
|
||||
const useToastsContainer = () => {
|
||||
return React.useContext(ToastsContainerContext);
|
||||
};
|
||||
|
||||
module.exports = useToastsContainer;
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
const Toasts = require('./Toasts');
|
||||
|
||||
module.exports = Toasts;
|
||||
|
|
@ -16,7 +16,7 @@ const Popup = require('./Popup');
|
|||
const SharePrompt = require('./SharePrompt');
|
||||
const Slider = require('./Slider');
|
||||
const TextInput = require('./TextInput');
|
||||
const Toasts = require('./Toasts');
|
||||
const { ToastProvider, useToast } = require('./Toast');
|
||||
const routesRegexp = require('./routesRegexp');
|
||||
const useAnimationFrame = require('./useAnimationFrame');
|
||||
const useBinaryState = require('./useBinaryState');
|
||||
|
|
@ -47,7 +47,8 @@ module.exports = {
|
|||
SharePrompt,
|
||||
Slider,
|
||||
TextInput,
|
||||
Toasts,
|
||||
ToastProvider,
|
||||
useToast,
|
||||
routesRegexp,
|
||||
useAnimationFrame,
|
||||
useBinaryState,
|
||||
|
|
|
|||
Loading…
Reference in a new issue