parent
66126a4d15
commit
eb11ba7842
@ -0,0 +1,30 @@ |
|||||||
|
import { AppModal } from '../interface' |
||||||
|
|
||||||
|
export type ActionMap<M extends { [index: string]: any }> = { |
||||||
|
[Key in keyof M]: M[Key] extends undefined |
||||||
|
? { |
||||||
|
type: Key; |
||||||
|
} |
||||||
|
: { |
||||||
|
type: Key; |
||||||
|
payload: M[Key]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const enum actionTypes { |
||||||
|
setModal = 'SET_MODAL', |
||||||
|
setToast = 'SET_TOAST', |
||||||
|
handleHideModal = 'HANDLE_HIDE_MODAL', |
||||||
|
handleToaster = 'HANDLE_HIDE_TOAST' |
||||||
|
} |
||||||
|
|
||||||
|
type ModalPayload = { |
||||||
|
[actionTypes.setModal]: AppModal |
||||||
|
[actionTypes.handleHideModal]: any |
||||||
|
[actionTypes.setToast]: string |
||||||
|
[actionTypes.handleToaster]: any |
||||||
|
} |
||||||
|
|
||||||
|
export type ModalAction = ActionMap<ModalPayload>[keyof ActionMap< |
||||||
|
ModalPayload |
||||||
|
>] |
@ -0,0 +1,27 @@ |
|||||||
|
import React, { useEffect } from 'react' |
||||||
|
import { useDialogDispatchers, useDialogs } from '../../context/provider' |
||||||
|
import { Toaster } from '@remix-ui/toaster' |
||||||
|
import ModalWrapper from './modal-wrapper' |
||||||
|
import { ModalTypes } from '../../types' |
||||||
|
|
||||||
|
const AppDialogs = () => { |
||||||
|
const { modal, toast, alert, handleHideModal, handleToaster } = useDialogDispatchers() |
||||||
|
const { focusModal, focusToaster } = useDialogs() |
||||||
|
|
||||||
|
const ok = (returnedValue) => { |
||||||
|
console.log('ok', returnedValue) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
modal('test', 'Enter gist url', 'yes', ok, 'cancel', () => { }, ModalTypes.prompt, 'wss://remix.ethereum.org:8545') |
||||||
|
toast('test toast') |
||||||
|
// alert('fat error')
|
||||||
|
}, []) |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<ModalWrapper {...focusModal } handleHide={handleHideModal}></ModalWrapper> |
||||||
|
<Toaster message={focusToaster} handleHide={handleToaster} /> |
||||||
|
</>) |
||||||
|
} |
||||||
|
export default AppDialogs |
@ -1,6 +1,6 @@ |
|||||||
import React, { useContext, useEffect, useState } from 'react' |
import React, { useContext, useEffect, useState } from 'react' |
||||||
import { ModalDialog } from '@remix-ui/modal-dialog' |
import { ModalDialog } from '@remix-ui/modal-dialog' |
||||||
import AppContext from '../context/context' |
import { AppContext } from '../../context/context' |
||||||
const _paq = window._paq = window._paq || [] |
const _paq = window._paq = window._paq || [] |
||||||
|
|
||||||
const MatomoDialog = (props) => { |
const MatomoDialog = (props) => { |
@ -0,0 +1,54 @@ |
|||||||
|
import React, { useEffect, useRef, useState } from 'react' |
||||||
|
import { ModalDialog } from '@remix-ui/modal-dialog' |
||||||
|
import { ModalDialogProps } from 'libs/remix-ui/modal-dialog/src/lib/types' |
||||||
|
import { ModalTypes } from '../../types' |
||||||
|
|
||||||
|
interface ModalWrapperProps extends ModalDialogProps { |
||||||
|
modalType?: ModalTypes |
||||||
|
defaultValue?: string |
||||||
|
} |
||||||
|
|
||||||
|
const ModalWrapper = (props: ModalWrapperProps) => { |
||||||
|
const [state, setState] = useState<ModalDialogProps>() |
||||||
|
const ref = useRef() |
||||||
|
|
||||||
|
const onFinishPrompt = async () => { |
||||||
|
if (ref.current === undefined) { |
||||||
|
props.okFn() |
||||||
|
} else { |
||||||
|
// @ts-ignore: Object is possibly 'null'.
|
||||||
|
props.okFn(ref.current.value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const createModalMessage = () => { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
{props.message} |
||||||
|
<input type={props.modalType === ModalTypes.password ? 'password' : 'text'} value={props.defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" /></> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
console.log(props) |
||||||
|
if (props.modalType) { |
||||||
|
switch (props.modalType) { |
||||||
|
case ModalTypes.prompt: |
||||||
|
case ModalTypes.password: |
||||||
|
setState({ |
||||||
|
...props, |
||||||
|
okFn: onFinishPrompt, |
||||||
|
message: createModalMessage() |
||||||
|
}) |
||||||
|
break |
||||||
|
default: |
||||||
|
setState({ ...props }) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
}, [props]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<ModalDialog id='appDialog' {...state} handleHide={props.handleHide} />) |
||||||
|
} |
||||||
|
export default ModalWrapper |
@ -1,5 +1,15 @@ |
|||||||
import React from 'react' |
import React from 'react' |
||||||
|
import { ModalInitialState } from '../state/modals' |
||||||
|
import { ModalTypes } from '../types' |
||||||
|
|
||||||
const AppContext = React.createContext(null) |
export const AppContext = React.createContext(null) |
||||||
|
|
||||||
export default AppContext |
export const dispatchModalContext = React.createContext({ |
||||||
|
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: (value?:any) => void, cancelLabel?: string, cancelFn?: () => void, modalType?: ModalTypes, defaultValue?: string) => { }, |
||||||
|
toast: (message: string) => {}, |
||||||
|
alert: (title: string, message?: string | JSX.Element) => {}, |
||||||
|
handleHideModal: () => { }, |
||||||
|
handleToaster: () => {} |
||||||
|
}) |
||||||
|
|
||||||
|
export const modalContext = React.createContext(ModalInitialState) |
||||||
|
@ -0,0 +1,62 @@ |
|||||||
|
import React, { useReducer } from 'react' |
||||||
|
import { actionTypes } from '../actions/modals' |
||||||
|
import { modalReducer } from '../reducer/modals' |
||||||
|
import { ModalInitialState } from '../state/modals' |
||||||
|
import { ModalTypes } from '../types' |
||||||
|
import { AppContext, dispatchModalContext, modalContext } from './context' |
||||||
|
|
||||||
|
export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => { |
||||||
|
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) |
||||||
|
|
||||||
|
const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: (value?:any) => void, cancelLabel?: string, cancelFn?: () => void, modalType?: ModalTypes, defaultValue?: string) => { |
||||||
|
dispatch({ |
||||||
|
type: actionTypes.setModal, |
||||||
|
payload: { title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const alert = (title: string, message?: string | JSX.Element) => { |
||||||
|
modal(message ? title : 'Alert', message || title, 'OK', null, null, null) |
||||||
|
} |
||||||
|
|
||||||
|
const handleHideModal = () => { |
||||||
|
dispatch({ |
||||||
|
type: actionTypes.handleHideModal, |
||||||
|
payload: null |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const toast = (message: string) => { |
||||||
|
dispatch({ |
||||||
|
type: actionTypes.setToast, |
||||||
|
payload: message |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleToaster = () => { |
||||||
|
dispatch({ |
||||||
|
type: actionTypes.handleToaster, |
||||||
|
payload: null |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return (<dispatchModalContext.Provider value={{ modal, toast, alert, handleHideModal, handleToaster }}> |
||||||
|
<modalContext.Provider value={{ modals, toasters, focusModal, focusToaster }}> |
||||||
|
{children} |
||||||
|
</modalContext.Provider> |
||||||
|
</dispatchModalContext.Provider>) |
||||||
|
} |
||||||
|
|
||||||
|
export const AppProvider = ({ children = [], value = {} } = {}) => { |
||||||
|
return <AppContext.Provider value={value}> |
||||||
|
<ModalProvider>{children}</ModalProvider> |
||||||
|
</AppContext.Provider> |
||||||
|
} |
||||||
|
|
||||||
|
export const useDialogs = () => { |
||||||
|
return React.useContext(modalContext) |
||||||
|
} |
||||||
|
|
||||||
|
export const useDialogDispatchers = () => { |
||||||
|
return React.useContext(dispatchModalContext) |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
import { ModalTypes } from '../types' |
||||||
|
|
||||||
|
export interface AppModal { |
||||||
|
hide?: boolean |
||||||
|
title: string |
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
message: string | JSX.Element |
||||||
|
okLabel: string |
||||||
|
okFn: (value?:any) => void |
||||||
|
cancelLabel: string |
||||||
|
cancelFn: () => void, |
||||||
|
modalType?: ModalTypes, |
||||||
|
defaultValue?: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface ModalState { |
||||||
|
modals: AppModal[], |
||||||
|
toasters: string[], |
||||||
|
focusModal: AppModal, |
||||||
|
focusToaster: string |
||||||
|
} |
@ -1,43 +1,48 @@ |
|||||||
import { actionTypes } from '../state/modals' |
import { actionTypes, ModalAction } from '../actions/modals' |
||||||
import { Modal } from '../types' |
import { ModalInitialState } from '../state/modals' |
||||||
|
import { AppModal, ModalState } from '../interface' |
||||||
|
|
||||||
interface Action { |
export const modalReducer = (state: ModalState = ModalInitialState, action: ModalAction) => { |
||||||
type: string |
console.log(action) |
||||||
payload: any |
switch (action.type) { |
||||||
} |
case actionTypes.setModal: { |
||||||
|
console.log('set modal', action, Date.now()) |
||||||
export interface ModalState { |
let modalList:AppModal[] = state.modals |
||||||
modals: Modal[], |
modalList.push(action.payload) |
||||||
toasters: string[], |
if (state.modals.length === 1 && state.focusModal.hide === true) { // if it's the first one show it
|
||||||
focusModal: Modal, |
const focusModal: AppModal = { |
||||||
focusToaster: string |
hide: false, |
||||||
} |
title: modalList[0].title, |
||||||
|
message: modalList[0].message, |
||||||
|
okLabel: modalList[0].okLabel, |
||||||
|
okFn: modalList[0].okFn, |
||||||
|
cancelLabel: modalList[0].cancelLabel, |
||||||
|
cancelFn: modalList[0].cancelFn, |
||||||
|
modalType: modalList[0].modalType, |
||||||
|
defaultValue: modalList[0].defaultValue |
||||||
|
} |
||||||
|
|
||||||
const ModalInitialState: ModalState = { |
modalList = modalList.slice() |
||||||
modals: [], |
modalList.shift() |
||||||
toasters: [], |
return { ...state, modals: modalList, focusModal: focusModal } |
||||||
focusModal: { |
} |
||||||
hide: true, |
return { ...state, modals: modalList } |
||||||
title: '', |
} |
||||||
message: '', |
case actionTypes.handleHideModal: |
||||||
okLabel: '', |
console.log('handle hid', JSON.stringify(state.modals)) |
||||||
okFn: () => {}, |
state.focusModal = { ...state.focusModal, hide: true, message: null } |
||||||
cancelLabel: '', |
return { ...state } |
||||||
cancelFn: () => {} |
|
||||||
}, |
|
||||||
focusToaster: '' |
|
||||||
} |
|
||||||
|
|
||||||
export const modalReducer = (state: ModalState = ModalInitialState, action: Action) => { |
case actionTypes.setToast: |
||||||
switch (action.type) { |
state.toasters.push(action.payload) |
||||||
case actionTypes.setModal: |
if (state.toasters.length > 0) { |
||||||
state.modals.push(action.payload) |
const focus = state.toasters[0] |
||||||
|
state.toasters.shift() |
||||||
|
return { ...state, focusToaster: focus } |
||||||
|
} |
||||||
return { ...state } |
return { ...state } |
||||||
} |
|
||||||
return { |
case actionTypes.handleToaster: |
||||||
modals: state.modals, |
return { ...state, focusToaster: '' } |
||||||
toasters: state.toasters, |
|
||||||
focusModal: state.focusModal, |
|
||||||
focusToaster: state.focusToaster |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,48 +1,16 @@ |
|||||||
import { useReducer } from 'react' |
import { ModalState } from '../interface' |
||||||
import { modalReducer } from '../reducer/modals' |
|
||||||
|
|
||||||
export const actionTypes = { |
export const ModalInitialState: ModalState = { |
||||||
setModal: 'SET_MODAL', |
modals: [], |
||||||
setToast: 'SET_TOAST' |
toasters: [], |
||||||
} |
focusModal: { |
||||||
|
hide: true, |
||||||
export const setModal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { |
title: '', |
||||||
return { |
message: '', |
||||||
type: actionTypes.setModal, |
okLabel: '', |
||||||
payload: { message, title, okLabel, okFn, cancelLabel, cancelFn } |
okFn: () => { }, |
||||||
} |
cancelLabel: '', |
||||||
} |
cancelFn: () => { } |
||||||
|
}, |
||||||
export const setToast = (toasterMsg: string) => { |
focusToaster: '' |
||||||
return { |
|
||||||
type: actionTypes.setToast, |
|
||||||
payload: toasterMsg |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function useModals ({ reducer = modalReducer } = {}) { |
|
||||||
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, { |
|
||||||
modals: [], |
|
||||||
toasters: [], |
|
||||||
focusModal: { |
|
||||||
hide: true, |
|
||||||
title: '', |
|
||||||
message: '', |
|
||||||
okLabel: '', |
|
||||||
okFn: () => {}, |
|
||||||
cancelLabel: '', |
|
||||||
cancelFn: () => {} |
|
||||||
}, |
|
||||||
focusToaster: '' |
|
||||||
}) |
|
||||||
|
|
||||||
const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { |
|
||||||
dispatch(setModal(title, message, okLabel, okFn, cancelLabel, cancelFn)) |
|
||||||
} |
|
||||||
|
|
||||||
const toast = (toasterMsg: string) => { |
|
||||||
dispatch(setToast(toasterMsg)) |
|
||||||
} |
|
||||||
|
|
||||||
return { focusModal, modals, focusToaster, toasters, modal, toast } |
|
||||||
} |
} |
||||||
|
@ -1,11 +1,7 @@ |
|||||||
|
export const enum ModalTypes { |
||||||
export interface Modal { |
alert = 'alert', |
||||||
hide?: boolean |
confirm = 'confirm', |
||||||
title: string |
prompt = 'prompt', |
||||||
// eslint-disable-next-line no-undef
|
password = 'password', |
||||||
message: string | JSX.Element |
default = 'default', |
||||||
okLabel: string |
} |
||||||
okFn: () => void |
|
||||||
cancelLabel: string |
|
||||||
cancelFn: () => void |
|
||||||
} |
|
||||||
|
Loading…
Reference in new issue