File size: 2,792 Bytes
f23825d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { keyframes } from "@emotion/react"
import styled from "@emotion/styled"
import * as Portal from "@radix-ui/react-portal"
import Error from "mdi-react/AlertCircleIcon"
import Warning from "mdi-react/AlertIcon"
import CheckCircle from "mdi-react/CheckCircleIcon"
import Info from "mdi-react/InformationIcon"
import { FC, useEffect, useState } from "react"
import { Theme } from "../common/theme/Theme"
import { useTheme } from "../main/hooks/useTheme"
import { ToastSeverity } from "../main/hooks/useToast"

export interface ToastProps {
  message: string
  severity: ToastSeverity
  onExited: () => void
}

const contentShow = keyframes`
  from {
    opacity: 0;
    transform: translate(0, 0.5rem) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translate(0, 0) scale(1);
  }
`

const contentHide = keyframes`
  from {
    opacity: 1;
    transform: translate(0, 0) scale(1);
  }
  to {
    opacity: 0;
    transform: translate(0, 0.5rem) scale(0.96);
  }
`

const Root = styled(Portal.Root)`
  position: fixed;
  bottom: 2rem;
  left: 0;
  right: 0;
  display: flex;
`

const Content = styled.div`
  margin: 0 auto;
  background: ${({ theme }) => theme.secondaryBackgroundColor};
  padding: 1rem;
  border-radius: 0.5rem;
  font-size: 0.8rem;
  box-shadow: 0 0.5rem 3rem ${({ theme }) => theme.shadowColor};
  display: flex;
  align-items: center;

  animation: ${({ show }: { show: boolean }) =>
      show ? contentShow : contentHide}
    500ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
`

const SeverityIcon: FC<{ severity: ToastSeverity }> = ({ severity }) => {
  const theme = useTheme()
  const fill = colorForSeverity(severity, theme)
  switch (severity) {
    case "error":
      return <Error style={{ fill }} />
    case "info":
      return <Info style={{ fill }} />
    case "success":
      return <CheckCircle style={{ fill }} />
    case "warning":
      return <Warning style={{ fill }} />
  }
}

const exitDuration = 5000

export const Toast: FC<ToastProps> = ({ message, severity, onExited }) => {
  const [show, setShow] = useState(true)

  useEffect(() => {
    const timeout = setTimeout(() => setShow(false), exitDuration - 500)
    const timeout2 = setTimeout(onExited, exitDuration)
    return () => {
      clearTimeout(timeout)
      clearTimeout(timeout2)
    }
  })
  return (
    <Root>
      <Content show={show}>
        <SeverityIcon severity={severity} />
        <div style={{ width: "0.5rem" }} />
        {message}
      </Content>
    </Root>
  )
}

const colorForSeverity = (severity: ToastSeverity, theme: Theme) => {
  switch (severity) {
    case "error":
      return theme.redColor
    case "info":
      return theme.textColor
    case "success":
      return theme.greenColor
    case "warning":
      return theme.yellowColor
  }
}