Skip to content

Instantly share code, notes, and snippets.

@timc1
Created August 25, 2019 22:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timc1/cbb9a817d29a1a94bd23c42bb1099533 to your computer and use it in GitHub Desktop.
Save timc1/cbb9a817d29a1a94bd23c42bb1099533 to your computer and use it in GitHub Desktop.
import React from 'react'
import styled from '@emotion/styled'
type StatusType = 'error' | 'neutral' | 'success'
type State = {
isStatusShowing: boolean
status: string
type: StatusType
}
type Action = {
type: string
payload: {
[k: string]: any
}
}
const reducer = (state: State, action: Action) => {
switch (action.type) {
case 'SET_STATUS_MESSAGE':
return {
isStatusShowing: true,
status: action.payload.value,
type: action.payload.type,
}
case 'HIDE_STATUS_MESSAGE':
return {
...state,
isStatusShowing: false,
}
default:
throw new Error(`No action type of ${action.type} found.`)
}
}
const StatusNotificationContext = React.createContext<any>(undefined)
export function StatusNotificationProvider({
children,
}: {
children: React.ReactNode
}) {
const [state, dispatch] = React.useReducer(reducer, {
isStatusShowing: false,
status: '',
type: 'neutral',
})
const currentTimeoutId = React.useRef<any>(null)
React.useEffect(() => {
if (state.isStatusShowing) {
clearTimeout(currentTimeoutId.current)
currentTimeoutId.current = setTimeout(() => {
dispatch({
type: 'HIDE_STATUS_MESSAGE',
payload: {},
})
}, 2500)
}
}, [state.isStatusShowing])
// Expose separate methods for toggling various
// statuses so users don't have to remember the type.
const setSuccessStatus = (value: string) => {
dispatch({
type: 'SET_STATUS_MESSAGE',
payload: { value, type: 'success' },
})
}
const setErrorStatus = (value: string) => {
dispatch({
type: 'SET_STATUS_MESSAGE',
payload: { value, type: 'error' },
})
}
const setNeutralStatus = (value: string) => {
dispatch({
type: 'SET_STATUS_MESSAGE',
payload: { value, type: 'neutral' },
})
}
const value = {
setSuccessStatus,
setNeutralStatus,
setErrorStatus,
}
return (
<StatusNotificationContext.Provider value={value}>
<StatusNotification
isShowing={state.isStatusShowing}
status={state.status}
type={state.type}
/>
{children}
</StatusNotificationContext.Provider>
)
}
export default function useStatusNotification() {
const context = React.useContext(StatusNotificationContext)
if (!context) {
throw new Error(
`useStatusNotification must be used within an StatusNotificationProvider.`
)
}
return context
}
const StatusNotification = ({
isShowing,
status,
type,
}: {
isShowing: boolean
status: string
type: StatusType
}) => {
return (
<NotificationContainer
isShowing={isShowing}
type={type}
aria-hidden={isShowing ? 'false' : 'true'}
>
<span aria-label={status}>{status}</span>
</NotificationContainer>
)
}
// eslint-disable-next-line
const NotificationContainer = styled.div<{
isShowing: boolean
type: StatusType
}>`
border: 1px solid var(--color-dark-3);
border-radius: var(--border-radius-1);
padding: var(--padding);
background: var(--color-light-1);
position: fixed;
bottom: calc(var(--padding) * 2);
left: calc(var(--padding) * 2);
max-width: 400px;
opacity: ${props => (props.isShowing ? '1' : '0')};
transform: translateY(${props => (props.isShowing ? '0px' : '40px')});
transition: transform 250ms var(--ease), opacity 200ms var(--ease) 50ms;
pointer-events: ${props => (props.isShowing ? 'initial' : 'none')};
touch-action: ${props => (props.isShowing ? 'initial' : 'none')};
z-index: 9;
> span {
font-weight: var(--font-weight-md);
color: ${props =>
props.type === 'error'
? 'var(--color-error-1)'
: props.type === 'success'
? 'var(--color-success-1)'
: 'var(--color-dark-1)'};
}
`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment