Skip to content

Commit e093eab

Browse files
authored
Merge pull request #2934 from SUI-Components/feature/migrate-atom-toast-to-typescript
Feature/migrate atom toast to typescript
2 parents 1aaa97c + 5b2bc8f commit e093eab

File tree

10 files changed

+369
-346
lines changed

10 files changed

+369
-346
lines changed
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import PropTypes from 'prop-types'
44

55
import {Article, Cell, Grid, Label} from '@s-ui/documentation-library'
66

7-
import AtomToast from '../src/index.js'
7+
import AtomToast, {type AtomToastProps} from '../src/index'
88

9-
const ToastDemo = ({className, onClose, iconClose, ...toastProps}) => {
9+
interface ToastDemoProps extends AtomToastProps {
10+
className?: string
11+
iconClose?: React.ReactNode
12+
onClose?: () => void
13+
}
14+
15+
const ToastDemo = ({className, onClose, iconClose, ...toastProps}: ToastDemoProps) => {
1016
return (
1117
<AtomToast onClose={onClose} iconClose={iconClose} {...toastProps}>
1218
<Article className={className}>
Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import {useState} from 'react'
2-
3-
import {atomToastAutoCloseTimes, atomToastPositions} from 'components/atom/toast/src/index.js'
1+
import {type ChangeEvent, type FormEvent, useState} from 'react'
42

53
import {
64
AntDesignIcon,
@@ -21,18 +19,24 @@ import {
2119
} from '@s-ui/documentation-library'
2220
import AtomIcon from '@s-ui/react-atom-icon'
2321

24-
import ToastDemo from './ToastDemo.js'
22+
import {type AutoCloseTime, type Position, AUTO_CLOSE_TIMES} from '../src/config'
23+
import {type AtomToastProps, atomToastAutoCloseTimes} from '../src/index'
24+
import ToastDemo from './ToastDemo'
2525

2626
import './index.scss'
2727

28+
interface AtomToastPropsWithId extends AtomToastProps {
29+
id: number
30+
}
31+
2832
const Demo = () => {
29-
const [toasts, setToasts] = useState([])
30-
const pushToast = props => {
33+
const [toasts, setToasts] = useState<AtomToastPropsWithId[]>([])
34+
const pushToast = (props: AtomToastProps) => {
3135
const newToasts = [...toasts, {id: performance.now(), ...props}]
3236
setToasts(newToasts)
3337
return newToasts
3438
}
35-
const popToast = id => {
39+
const popToast = (id: number) => {
3640
if (!show) {
3741
const newToasts = toasts.filter(toast => toast.id !== id)
3842
setToasts(newToasts)
@@ -41,13 +45,48 @@ const Demo = () => {
4145
return toasts
4246
}
4347

44-
const [iconClose, setIconClose] = useState()
48+
const [iconClose, setIconClose] = useState<React.ReactNode>()
4549
const [show, setShow] = useState(true)
4650
const [autoClose, setAutoClose] = useState(true)
47-
const [autoCloseTime, setAutoCloseTime] = useState(atomToastAutoCloseTimes.short)
48-
const [globalClose, setGlobalClose] = useState()
51+
const [autoCloseTime, setAutoCloseTime] = useState<AutoCloseTime>(AUTO_CLOSE_TIMES.short)
52+
const [globalClose, setGlobalClose] = useState(false)
4953
const [effect, setEffect] = useState(true)
5054

55+
const gridOptions: Array<{
56+
position?: Position
57+
span?: number
58+
style: React.CSSProperties
59+
}> = [
60+
{
61+
position: 'top-left',
62+
style: {justifyContent: 'flex-start', display: 'flex'}
63+
},
64+
{
65+
position: 'top',
66+
style: {justifyContent: 'center', display: 'flex'}
67+
},
68+
{
69+
position: 'top-right',
70+
style: {justifyContent: 'flex-end', display: 'flex'}
71+
},
72+
{
73+
span: 3,
74+
style: {justifyContent: 'center', display: 'flex'}
75+
},
76+
{
77+
position: 'bottom-left',
78+
style: {justifyContent: 'flex-start', display: 'flex'}
79+
},
80+
{
81+
position: 'bottom',
82+
style: {justifyContent: 'center', display: 'flex'}
83+
},
84+
{
85+
position: 'bottom-right',
86+
style: {justifyContent: 'flex-end', display: 'flex'}
87+
}
88+
]
89+
5190
return (
5291
<div className="sui-StudioPreview">
5392
<H1>Toast</H1>
@@ -62,38 +101,9 @@ const Demo = () => {
62101
</Paragraph>
63102
<Box outline>
64103
<Grid cols={3} gutter={[8, 8]}>
65-
{[
66-
{
67-
position: atomToastPositions.topLeft,
68-
style: {justifyContent: 'flex-start', display: 'flex'}
69-
},
70-
{
71-
position: atomToastPositions.top,
72-
style: {justifyContent: 'center', display: 'flex'}
73-
},
74-
{
75-
position: atomToastPositions.topRight,
76-
style: {justifyContent: 'flex-end', display: 'flex'}
77-
},
78-
{
79-
span: 3,
80-
style: {justifyContent: 'center', display: 'flex'}
81-
},
82-
{
83-
position: atomToastPositions.bottomLeft,
84-
style: {justifyContent: 'flex-start', display: 'flex'}
85-
},
86-
{
87-
position: atomToastPositions.bottom,
88-
style: {justifyContent: 'center', display: 'flex'}
89-
},
90-
{
91-
position: atomToastPositions.bottomRight,
92-
style: {justifyContent: 'flex-end', display: 'flex'}
93-
}
94-
].map(({position, span, style}, index) => (
95-
<Cell key={position || index} span={span}>
96-
{position ? (
104+
{gridOptions.map(({position, span, style}, index) => (
105+
<Cell key={position !== undefined ? position : index} span={span}>
106+
{position !== undefined ? (
97107
<div style={style}>
98108
<Button onClick={() => pushToast({position})}>{position}</Button>
99109
</div>
@@ -112,7 +122,7 @@ const Demo = () => {
112122
autoCloseTime={autoCloseTime}
113123
effect={effect}
114124
iconClose={
115-
iconClose && (
125+
iconClose !== undefined && (
116126
<AtomIcon>
117127
<AntDesignIcon icon={iconClose} style={{color: 'currentColor'}} />
118128
</AtomIcon>
@@ -151,7 +161,7 @@ const Demo = () => {
151161
<Grid cols={1} gutter={[8, 0]}>
152162
<Cell>
153163
<RadioButtonGroup
154-
onChange={(event, value) => {
164+
onChange={(_event: FormEvent, value: AutoCloseTime) => {
155165
setAutoCloseTime(value === undefined ? autoCloseTime : value)
156166
}}
157167
>
@@ -168,7 +178,13 @@ const Demo = () => {
168178
</RadioButtonGroup>
169179
</Cell>
170180
<Cell>
171-
<Input value={autoCloseTime} onChange={event => setAutoCloseTime(event.target.value)} tyype="number" />
181+
<Input
182+
value={autoCloseTime}
183+
onChange={(event: ChangeEvent<HTMLInputElement>) =>
184+
setAutoCloseTime(Number(event.target.value) as AutoCloseTime)
185+
}
186+
type="number"
187+
/>
172188
</Cell>
173189
</Grid>
174190
</Cell>
@@ -191,21 +207,21 @@ const Demo = () => {
191207
The <Code>iconClose</Code> (node) prop gives the posibility to customize the closing button.
192208
</Paragraph>
193209
<RadioButtonGroup
194-
onChange={(event, value) => {
210+
onChange={(_event: ChangeEvent<HTMLInputElement>, value: React.ReactNode) => {
195211
setIconClose(value)
196212
}}
197213
>
198-
{[undefined, 'AiOutlineClose', 'AiOutlinePoweroff', 'AiFillCloseCircle'].map((iconKey, index) => (
214+
{[undefined, 'AiOutlineClose', 'AiOutlinePoweroff', 'AiFillCloseCircle'].map(iconKey => (
199215
<RadioButton
200-
key={`${iconKey}`}
216+
key={iconKey}
201217
checked={iconClose === iconKey}
202218
label={
203-
iconKey ? (
219+
iconKey !== undefined ? (
204220
<AtomIcon>
205221
<AntDesignIcon icon={iconKey} style={{color: 'currentColor'}} />
206222
</AtomIcon>
207223
) : (
208-
`${iconKey}`
224+
String(iconKey)
209225
)
210226
}
211227
value={iconKey}

components/atom/toast/index.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type {FC} from 'react'
2+
3+
import type {RenderOptions, RenderResult} from '@testing-library/react'
4+
5+
interface SetupEnvironmentOptions extends RenderOptions {
6+
contexts: unknown[]
7+
}
8+
9+
declare global {
10+
const setupEnvironment: <T>(
11+
Component: FC<T>,
12+
{contexts, hydrate, queries, wrapper}?: SetupEnvironmentOptions
13+
) => (props: {[k: string]: unknown}) => RenderResult
14+
}

components/atom/toast/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55
"publishConfig": {
66
"access": "public"
77
},
8-
"keywords": ["@s-ui", "react", "component", "atom", "toast"],
8+
"keywords": [
9+
"@s-ui",
10+
"react",
11+
"component",
12+
"atom",
13+
"toast"
14+
],
915
"main": "lib/index.js",
1016
"scripts": {
1117
"prepublishOnly": "rimraf ./lib && npm run build:js && npm run build:styles",
12-
"build:js": "babel --presets sui ./src --out-dir ./lib",
18+
"build:js": "sui-js-compiler",
1319
"build:styles": "cpx './src/**/*.scss' ./lib"
1420
},
1521
"dependencies": {

components/atom/toast/src/config.js

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const BASE_CLASS = 'sui-AtomToast'
2+
3+
export const EFFECT_DELAY = {
4+
close: 1000,
5+
open: 1
6+
}
7+
8+
export const AUTO_CLOSE_TIMES = {
9+
short: 3000,
10+
medium: 6000,
11+
long: 9000
12+
} as const
13+
14+
export type AutoCloseTime = typeof AUTO_CLOSE_TIMES[keyof typeof AUTO_CLOSE_TIMES]
15+
export type Position = 'top-left' | 'top' | 'top-right' | 'bottom-left' | 'bottom' | 'bottom-right'
Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,46 @@
1-
import {useCallback, useEffect, useRef, useState} from 'react'
1+
import type React from 'react'
2+
import {type PropsWithChildren, useCallback, useEffect, useRef, useState} from 'react'
23

34
import cx from 'classnames'
4-
import PropTypes from 'prop-types'
55

6-
import {AUTO_CLOSE_TIMES, BASE_CLASS, EFFECT_DELAY, POSITIONS} from './config.js'
6+
import {type AutoCloseTime, type Position, AUTO_CLOSE_TIMES, BASE_CLASS} from './config'
7+
import {EFFECT_DELAY} from './config'
8+
9+
export interface AtomToastProps {
10+
/** Enable/disable auto close */
11+
autoClose?: boolean
12+
/** Auto close times: 'short' (3000s), 'medium' (6000s), 'long' (9000s) */
13+
autoCloseTime?: AutoCloseTime
14+
/** Enable/disable toast transition */
15+
effect?: boolean
16+
/** Custom close icon */
17+
iconClose?: React.ReactNode
18+
/** On close callback */
19+
onClose?: () => void
20+
/** Positions: 'top-left', 'top', 'top-right', 'bottom-left', 'bottom', 'bottom-right' */
21+
position?: Position
22+
show?: boolean
23+
/** Enable/disable global close */
24+
globalClose?: boolean
25+
}
726

827
const AtomToast = ({
928
autoClose = true,
1029
autoCloseTime = AUTO_CLOSE_TIMES.medium,
11-
children,
1230
effect = true,
13-
globalClose = false,
14-
iconClose = null,
31+
iconClose,
1532
onClose,
16-
position = POSITIONS.topRight,
17-
show: showFromProps = true
18-
}) => {
33+
position = 'top-right',
34+
show: showFromProps = true,
35+
globalClose = false,
36+
children
37+
}: PropsWithChildren<AtomToastProps>) => {
1938
const [show, setShow] = useState(showFromProps)
2039
const [delay, setDelay] = useState(true)
2140

22-
const autoCloseTimeout = useRef()
23-
const delayTimeout = useRef()
24-
const toastRef = useRef()
41+
const autoCloseTimeout = useRef<NodeJS.Timeout>()
42+
const delayTimeout = useRef<NodeJS.Timeout>()
43+
const toastRef = useRef<HTMLDivElement>(null)
2544

2645
const containerClassName = cx(`${BASE_CLASS}-container`, `${BASE_CLASS}-position--${position}`, {
2746
[`${BASE_CLASS}-effect--${position}`]: effect,
@@ -69,8 +88,10 @@ const AtomToast = ({
6988

7089
useEffect(() => {
7190
if (globalClose) {
72-
const handleClickOutside = e => {
73-
if (!toastRef.current.contains(e.target)) {
91+
const handleClickOutside = (e: MouseEvent) => {
92+
const targetNode = e.target as Node
93+
94+
if (toastRef.current !== null && !toastRef.current.contains(targetNode)) {
7495
handleClose()
7596
}
7697
}
@@ -98,26 +119,6 @@ const AtomToast = ({
98119

99120
AtomToast.displayName = 'AtomToast'
100121

101-
AtomToast.propTypes = {
102-
/** Enable/disable auto close */
103-
autoClose: PropTypes.bool,
104-
/** Auto close times: 'short' (3000s), 'medium' (6000s), 'long' (9000s) */
105-
autoCloseTime: PropTypes.oneOf(Object.keys(AUTO_CLOSE_TIMES)),
106-
/** Toast content */
107-
children: PropTypes.node.isRequired,
108-
/** Enable/disable toast transition */
109-
effect: PropTypes.bool,
110-
/** Custom close icon */
111-
iconClose: PropTypes.node,
112-
/** On close callback */
113-
onClose: PropTypes.func,
114-
/** Positions: 'top-left', 'top', 'top-right', 'bottom-left', 'bottom', 'bottom-right' */
115-
position: PropTypes.oneOf(Object.values(POSITIONS)),
116-
/** Show/hide notification */
117-
show: PropTypes.bool,
118-
/** Enable/disable global close */
119-
globalClose: PropTypes.bool
120-
}
122+
export {AUTO_CLOSE_TIMES as atomToastAutoCloseTimes}
121123

122-
export {POSITIONS as atomToastPositions, AUTO_CLOSE_TIMES as atomToastAutoCloseTimes}
123124
export default AtomToast

0 commit comments

Comments
 (0)