@startupjs-ui/toast 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.helpers.js +76 -0
- package/README.mdx +79 -0
- package/ToastProvider.tsx +24 -0
- package/ToastView.tsx +138 -0
- package/helpers.ts +3 -0
- package/index.cssx.styl +68 -0
- package/index.d.ts +6 -0
- package/index.mdx.cssx.styl +2 -0
- package/index.tsx +3 -0
- package/package.json +24 -0
- package/toast.ts +127 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @startupjs-ui/toast
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
|
|
20
|
+
* **toast:** refactor toast, ToastProvider, Toast components ([f56f8be](https://github.com/startupjs/startupjs-ui/commit/f56f8be5c63dfb7c44855b7c35758be9a5c49387))
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Fragment } from 'react'
|
|
2
|
+
import { pug } from 'startupjs'
|
|
3
|
+
import Button from '@startupjs-ui/button'
|
|
4
|
+
import Br from '@startupjs-ui/br'
|
|
5
|
+
import Div from '@startupjs-ui/div'
|
|
6
|
+
import Toast from './ToastView'
|
|
7
|
+
import { toast } from './index'
|
|
8
|
+
|
|
9
|
+
const BASE_TOAST_PROPS = {
|
|
10
|
+
show: true,
|
|
11
|
+
topPosition: 0,
|
|
12
|
+
height: 72,
|
|
13
|
+
type: 'info',
|
|
14
|
+
title: 'Info',
|
|
15
|
+
text: 'Note archived',
|
|
16
|
+
actionLabel: 'View',
|
|
17
|
+
onAction: () => {}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ToastProviderSandbox () {
|
|
21
|
+
function onPressInfo () {
|
|
22
|
+
toast({ text: 'Note archived' })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function onPressSuccess () {
|
|
26
|
+
toast({ type: 'success', title: 'Success', text: 'Profile saved' })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function onPressAlert () {
|
|
30
|
+
toast({ type: 'error', title: 'Error', text: 'Something went wrong', alert: true })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return pug`
|
|
34
|
+
Fragment
|
|
35
|
+
Button(onPress=onPressInfo) Show info toast
|
|
36
|
+
Br
|
|
37
|
+
Button(onPress=onPressSuccess) Show success toast
|
|
38
|
+
Br
|
|
39
|
+
Button(onPress=onPressAlert) Show alert toast
|
|
40
|
+
`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function ToastFunctionSandbox ({
|
|
44
|
+
alert,
|
|
45
|
+
icon,
|
|
46
|
+
text,
|
|
47
|
+
type,
|
|
48
|
+
title,
|
|
49
|
+
actionLabel
|
|
50
|
+
}) {
|
|
51
|
+
function onPress () {
|
|
52
|
+
toast({
|
|
53
|
+
alert,
|
|
54
|
+
icon,
|
|
55
|
+
text,
|
|
56
|
+
type,
|
|
57
|
+
title,
|
|
58
|
+
actionLabel
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return pug`
|
|
63
|
+
Fragment
|
|
64
|
+
Button(onPress=onPress) Show toast
|
|
65
|
+
`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function ToastComponentSandbox (props) {
|
|
69
|
+
const onLayout = () => {}
|
|
70
|
+
const mergedProps = { ...BASE_TOAST_PROPS, ...props }
|
|
71
|
+
|
|
72
|
+
return pug`
|
|
73
|
+
Div(style={ position: 'relative', minHeight: 120 })
|
|
74
|
+
Toast(...mergedProps onLayout=onLayout)
|
|
75
|
+
`
|
|
76
|
+
}
|
package/README.mdx
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { pug } from 'startupjs'
|
|
2
|
+
import { Sandbox } from '@startupjs-ui/docs'
|
|
3
|
+
import Button from '@startupjs-ui/button'
|
|
4
|
+
import { toast } from './index'
|
|
5
|
+
import { _PropsJsonSchema as ToastOptionsPropsJsonSchema } from './toast'
|
|
6
|
+
import { _PropsJsonSchema as ToastPropsJsonSchema } from './ToastView'
|
|
7
|
+
import { _PropsJsonSchema as ToastProviderPropsJsonSchema } from './ToastProvider'
|
|
8
|
+
import {
|
|
9
|
+
ToastProviderSandbox,
|
|
10
|
+
ToastFunctionSandbox,
|
|
11
|
+
ToastComponentSandbox
|
|
12
|
+
} from './README.helpers'
|
|
13
|
+
import './index.mdx.cssx.styl'
|
|
14
|
+
|
|
15
|
+
# Toast
|
|
16
|
+
|
|
17
|
+
Toast provides brief non-blocking notifications that inform users of a process that an app has performed or will perform.
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { ToastProvider, toast } from 'startupjs-ui'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Render `ToastProvider` once inside `Portal.Provider` before your page context (for example, `Stack`).
|
|
26
|
+
|
|
27
|
+
```jsx
|
|
28
|
+
import { Portal, ToastProvider, DialogsProvider } from 'startupjs-ui'
|
|
29
|
+
import { Stack } from 'expo-router'
|
|
30
|
+
|
|
31
|
+
export default function RootLayout () {
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<Portal.Provider>
|
|
35
|
+
<ToastProvider />
|
|
36
|
+
<Stack />
|
|
37
|
+
</Portal.Provider>
|
|
38
|
+
<DialogsProvider />
|
|
39
|
+
</>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Simple example
|
|
45
|
+
|
|
46
|
+
```jsx example
|
|
47
|
+
function onPress () {
|
|
48
|
+
toast({ text: 'Note archived' })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return pug`
|
|
52
|
+
Button(onPress=onPress) Show toast
|
|
53
|
+
`
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Sandbox
|
|
57
|
+
|
|
58
|
+
### toast
|
|
59
|
+
|
|
60
|
+
<Sandbox
|
|
61
|
+
Component={ToastFunctionSandbox}
|
|
62
|
+
props={{ title: 'Info', text: 'Note archived', type: 'info' }}
|
|
63
|
+
propsJsonSchema={ToastOptionsPropsJsonSchema}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
### ToastProvider
|
|
67
|
+
|
|
68
|
+
<Sandbox
|
|
69
|
+
Component={ToastProviderSandbox}
|
|
70
|
+
propsJsonSchema={ToastProviderPropsJsonSchema}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
### Toast (internal component, usually you won't want to use it directly)
|
|
74
|
+
|
|
75
|
+
<Sandbox
|
|
76
|
+
Component={ToastComponentSandbox}
|
|
77
|
+
props={{ title: 'Info', text: 'Note archived', type: 'info' }}
|
|
78
|
+
propsJsonSchema={ToastPropsJsonSchema}
|
|
79
|
+
/>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { pug, observer } from 'startupjs'
|
|
3
|
+
import Portal from '@startupjs-ui/portal'
|
|
4
|
+
import { $toasts } from './helpers'
|
|
5
|
+
import Toast, { type ToastProps } from './ToastView'
|
|
6
|
+
|
|
7
|
+
export const _PropsJsonSchema = {/* ToastProviderProps */}
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
10
|
+
export interface ToastProviderProps {}
|
|
11
|
+
|
|
12
|
+
function ToastProvider (): ReactNode {
|
|
13
|
+
const toasts = $toasts.get() as ToastProps[] | undefined
|
|
14
|
+
|
|
15
|
+
if (!toasts?.length) return null
|
|
16
|
+
|
|
17
|
+
return pug`
|
|
18
|
+
Portal
|
|
19
|
+
each toast in toasts
|
|
20
|
+
Toast(...toast key=toast.key)
|
|
21
|
+
`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default observer(ToastProvider) as any
|
package/ToastView.tsx
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState, type ReactNode } from 'react'
|
|
2
|
+
import { Animated } from 'react-native'
|
|
3
|
+
import { pug, observer } from 'startupjs'
|
|
4
|
+
import Button from '@startupjs-ui/button'
|
|
5
|
+
import Div from '@startupjs-ui/div'
|
|
6
|
+
import Icon, { type IconProps } from '@startupjs-ui/icon'
|
|
7
|
+
import Span from '@startupjs-ui/span'
|
|
8
|
+
import { themed } from '@startupjs-ui/core'
|
|
9
|
+
import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons/faExclamationCircle'
|
|
10
|
+
import { faTimes } from '@fortawesome/free-solid-svg-icons/faTimes'
|
|
11
|
+
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons/faCheckCircle'
|
|
12
|
+
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons/faExclamationTriangle'
|
|
13
|
+
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons/faInfoCircle'
|
|
14
|
+
import './index.cssx.styl'
|
|
15
|
+
|
|
16
|
+
const DURATION_OPEN = 300
|
|
17
|
+
const DURATION_CLOSE = 150
|
|
18
|
+
|
|
19
|
+
const ICONS = {
|
|
20
|
+
info: faInfoCircle,
|
|
21
|
+
error: faExclamationCircle,
|
|
22
|
+
warning: faExclamationTriangle,
|
|
23
|
+
success: faCheckCircle
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const TITLES = {
|
|
27
|
+
info: 'Info',
|
|
28
|
+
error: 'Error',
|
|
29
|
+
warning: 'Warning',
|
|
30
|
+
success: 'Success'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const _PropsJsonSchema = {/* ToastProps */}
|
|
34
|
+
|
|
35
|
+
export interface ToastProps {
|
|
36
|
+
/** Visual style variant @default 'info' */
|
|
37
|
+
type?: 'info' | 'error' | 'warning' | 'success'
|
|
38
|
+
/** Y offset used to stack multiple toasts */
|
|
39
|
+
topPosition: number
|
|
40
|
+
/** Current toast height for stacking calculations */
|
|
41
|
+
height?: number
|
|
42
|
+
/** Controls whether the toast is visible */
|
|
43
|
+
show: boolean
|
|
44
|
+
/** Custom icon shown next to the title */
|
|
45
|
+
icon?: IconProps['icon']
|
|
46
|
+
/** Body text displayed under the title */
|
|
47
|
+
text?: string
|
|
48
|
+
/** Title text displayed in the header */
|
|
49
|
+
title?: string
|
|
50
|
+
/** Action button label displayed below the text @default 'View' */
|
|
51
|
+
actionLabel?: string
|
|
52
|
+
/** Action button press handler */
|
|
53
|
+
onAction?: () => void
|
|
54
|
+
/** Called after the toast is closed and removed */
|
|
55
|
+
onClose?: () => void
|
|
56
|
+
/** Layout callback used to measure toast height */
|
|
57
|
+
onLayout: (layout: { height: number }) => void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function Toast ({
|
|
61
|
+
type = 'info',
|
|
62
|
+
topPosition,
|
|
63
|
+
height,
|
|
64
|
+
show,
|
|
65
|
+
icon,
|
|
66
|
+
text,
|
|
67
|
+
title,
|
|
68
|
+
actionLabel = 'View',
|
|
69
|
+
onAction,
|
|
70
|
+
onClose,
|
|
71
|
+
onLayout
|
|
72
|
+
}: ToastProps): ReactNode {
|
|
73
|
+
const [showAnimation] = useState(() => new Animated.Value(0))
|
|
74
|
+
const [topAnimation] = useState(() => new Animated.Value(topPosition))
|
|
75
|
+
|
|
76
|
+
const onShow = useCallback(() => {
|
|
77
|
+
Animated
|
|
78
|
+
.timing(showAnimation, { toValue: 1, duration: DURATION_OPEN, useNativeDriver: false })
|
|
79
|
+
.start()
|
|
80
|
+
}, [showAnimation])
|
|
81
|
+
|
|
82
|
+
const onHide = useCallback(() => {
|
|
83
|
+
Animated
|
|
84
|
+
.timing(showAnimation, { toValue: 0, duration: DURATION_CLOSE, useNativeDriver: false })
|
|
85
|
+
.start(onClose)
|
|
86
|
+
}, [onClose, showAnimation])
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
Animated
|
|
90
|
+
.timing(topAnimation, { toValue: topPosition, duration: DURATION_OPEN, useNativeDriver: false })
|
|
91
|
+
.start()
|
|
92
|
+
}, [topPosition, topAnimation])
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (show && height) onShow()
|
|
96
|
+
if (!show) onHide()
|
|
97
|
+
}, [height, onHide, onShow, show])
|
|
98
|
+
|
|
99
|
+
return pug`
|
|
100
|
+
Animated.View.root(
|
|
101
|
+
style={
|
|
102
|
+
opacity: showAnimation,
|
|
103
|
+
right: showAnimation.interpolate({
|
|
104
|
+
inputRange: [0, 1],
|
|
105
|
+
outputRange: [-48, 0]
|
|
106
|
+
}),
|
|
107
|
+
top: topAnimation
|
|
108
|
+
}
|
|
109
|
+
onLayout=e=> onLayout(e.nativeEvent.layout)
|
|
110
|
+
)
|
|
111
|
+
Div.toast(styleName=[type])
|
|
112
|
+
Div.header(vAlign='center' row)
|
|
113
|
+
Div(vAlign='center' row)
|
|
114
|
+
Icon.icon(
|
|
115
|
+
icon=icon ? icon : ICONS[type]
|
|
116
|
+
styleName=[type]
|
|
117
|
+
)
|
|
118
|
+
Span.title(styleName=[type])
|
|
119
|
+
= title ? title : TITLES[type]
|
|
120
|
+
Div(onPress=onHide)
|
|
121
|
+
Icon(icon=faTimes)
|
|
122
|
+
|
|
123
|
+
if text
|
|
124
|
+
Span.text= text
|
|
125
|
+
|
|
126
|
+
if onAction
|
|
127
|
+
Div.actions(row)
|
|
128
|
+
Button(
|
|
129
|
+
size='s'
|
|
130
|
+
onPress=() => {
|
|
131
|
+
onAction()
|
|
132
|
+
onHide()
|
|
133
|
+
}
|
|
134
|
+
)= actionLabel
|
|
135
|
+
`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export default observer(themed('Toast', Toast))
|
package/helpers.ts
ADDED
package/index.cssx.styl
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
.root
|
|
2
|
+
position absolute
|
|
3
|
+
width 100%
|
|
4
|
+
|
|
5
|
+
+from($UI.media.mobile)
|
|
6
|
+
max-width 40u
|
|
7
|
+
min-width 30u
|
|
8
|
+
padding-right 2u
|
|
9
|
+
padding-top 2u
|
|
10
|
+
|
|
11
|
+
.toast
|
|
12
|
+
padding 2u
|
|
13
|
+
justify-content center
|
|
14
|
+
border-radius .5u
|
|
15
|
+
background-color var(--color-bg-main-strong)
|
|
16
|
+
border-top-width 4px
|
|
17
|
+
border-top-style solid
|
|
18
|
+
shadow(2)
|
|
19
|
+
|
|
20
|
+
&.info
|
|
21
|
+
border-top-color var(--color-border-primary)
|
|
22
|
+
|
|
23
|
+
&.error
|
|
24
|
+
border-top-color var(--color-border-error)
|
|
25
|
+
|
|
26
|
+
&.warning
|
|
27
|
+
border-top-color var(--color-border-warning)
|
|
28
|
+
|
|
29
|
+
&.success
|
|
30
|
+
border-top-color var(--color-border-success)
|
|
31
|
+
|
|
32
|
+
.header
|
|
33
|
+
justify-content space-between
|
|
34
|
+
|
|
35
|
+
.text
|
|
36
|
+
margin-top 1u
|
|
37
|
+
|
|
38
|
+
.title
|
|
39
|
+
margin-left 1u
|
|
40
|
+
|
|
41
|
+
&.info
|
|
42
|
+
color var(--color-text-primary)
|
|
43
|
+
|
|
44
|
+
&.error
|
|
45
|
+
color var(--color-text-error)
|
|
46
|
+
|
|
47
|
+
&.warning
|
|
48
|
+
color var(--color-text-warning)
|
|
49
|
+
|
|
50
|
+
&.success
|
|
51
|
+
color var(--color-text-success)
|
|
52
|
+
|
|
53
|
+
.icon
|
|
54
|
+
&.info
|
|
55
|
+
color var(--color-text-primary)
|
|
56
|
+
|
|
57
|
+
&.error
|
|
58
|
+
color var(--color-text-error)
|
|
59
|
+
|
|
60
|
+
&.warning
|
|
61
|
+
color var(--color-text-warning)
|
|
62
|
+
|
|
63
|
+
&.success
|
|
64
|
+
color var(--color-text-success)
|
|
65
|
+
|
|
66
|
+
.actions
|
|
67
|
+
margin-top 1u
|
|
68
|
+
justify-content flex-end
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
|
|
3
|
+
|
|
4
|
+
export { default as Toast, type ToastProps } from './ToastView';
|
|
5
|
+
export { default as ToastProvider, type ToastProviderProps } from './ToastProvider';
|
|
6
|
+
export { default as toast, type ToastOptions } from './toast';
|
package/index.tsx
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@startupjs-ui/toast",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"main": "index.tsx",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@startupjs-ui/button": "^0.1.3",
|
|
12
|
+
"@startupjs-ui/core": "^0.1.3",
|
|
13
|
+
"@startupjs-ui/div": "^0.1.3",
|
|
14
|
+
"@startupjs-ui/icon": "^0.1.3",
|
|
15
|
+
"@startupjs-ui/portal": "^0.1.3",
|
|
16
|
+
"@startupjs-ui/span": "^0.1.3"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": "*",
|
|
20
|
+
"react-native": "*",
|
|
21
|
+
"startupjs": "*"
|
|
22
|
+
},
|
|
23
|
+
"gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
|
|
24
|
+
}
|
package/toast.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { $ } from 'startupjs'
|
|
2
|
+
import { $toasts } from './helpers'
|
|
3
|
+
import { type ToastProps } from './ToastView'
|
|
4
|
+
|
|
5
|
+
const MAX_SHOW_LENGTH = 3
|
|
6
|
+
|
|
7
|
+
export const _PropsJsonSchema = {/* ToastOptions */}
|
|
8
|
+
|
|
9
|
+
export interface ToastOptions {
|
|
10
|
+
/** Prevents auto-close after 5 seconds */
|
|
11
|
+
alert?: boolean
|
|
12
|
+
/** Custom icon shown next to the title */
|
|
13
|
+
icon?: ToastProps['icon']
|
|
14
|
+
/** Body text displayed under the title */
|
|
15
|
+
text?: string
|
|
16
|
+
/** Visual style variant @default 'info' */
|
|
17
|
+
type?: ToastProps['type']
|
|
18
|
+
/** Title text displayed in the header */
|
|
19
|
+
title?: string
|
|
20
|
+
/** Action button label displayed below the text @default 'View' */
|
|
21
|
+
actionLabel?: string
|
|
22
|
+
/** Action button press handler */
|
|
23
|
+
onAction?: () => void
|
|
24
|
+
/** Called after the toast is closed and removed */
|
|
25
|
+
onClose?: () => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ToastStackItem = ToastProps & {
|
|
29
|
+
key: string
|
|
30
|
+
alert?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// NOTE
|
|
34
|
+
// Is this the best way to update position of toasts?
|
|
35
|
+
// Is there a better way to do this?
|
|
36
|
+
// We want to remove unnecessary props from toast
|
|
37
|
+
// component that are added by these calculations.
|
|
38
|
+
const updateMatrixPositions = () => {
|
|
39
|
+
const toasts = $toasts.get() as ToastStackItem[]
|
|
40
|
+
|
|
41
|
+
const updateToasts = toasts.map((toast, index) => {
|
|
42
|
+
const prevToast = toasts[index - 1]
|
|
43
|
+
|
|
44
|
+
if (prevToast) {
|
|
45
|
+
const prevHeight = prevToast.height ?? 0
|
|
46
|
+
toast.topPosition = prevToast.topPosition + prevHeight
|
|
47
|
+
} else {
|
|
48
|
+
toast.topPosition = 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return toast
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
$toasts.set(updateToasts)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default function toast ({
|
|
58
|
+
alert,
|
|
59
|
+
icon,
|
|
60
|
+
text,
|
|
61
|
+
type,
|
|
62
|
+
title,
|
|
63
|
+
actionLabel,
|
|
64
|
+
onAction,
|
|
65
|
+
onClose
|
|
66
|
+
}: ToastOptions): void {
|
|
67
|
+
const toastId = $.id() as string
|
|
68
|
+
|
|
69
|
+
if ($toasts.get()?.length === MAX_SHOW_LENGTH) {
|
|
70
|
+
$toasts[MAX_SHOW_LENGTH - 1].show.set(false)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!alert) {
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
const index = getValidIndex()
|
|
76
|
+
if (index !== -1) $toasts[index].show.set(false)
|
|
77
|
+
}, 5000)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function onRemove () {
|
|
81
|
+
const index = getValidIndex()
|
|
82
|
+
if (index === -1) return
|
|
83
|
+
|
|
84
|
+
$toasts[index].del()
|
|
85
|
+
updateMatrixPositions()
|
|
86
|
+
onClose && onClose()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// toastId ensures that the correct index is found at the current moment
|
|
90
|
+
function getValidIndex () {
|
|
91
|
+
const toasts = $toasts.get() as ToastStackItem[]
|
|
92
|
+
return toasts.findIndex(toast => toast.key === toastId)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// NOTE
|
|
96
|
+
// Think about using context instead of model
|
|
97
|
+
// We can provide registerToast function in context
|
|
98
|
+
// Which will be better? model or context?
|
|
99
|
+
function onLayout (layout: { height: number }) {
|
|
100
|
+
$toasts[getValidIndex()].height.set(layout.height)
|
|
101
|
+
updateMatrixPositions()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const newToast: ToastStackItem = {
|
|
105
|
+
key: toastId,
|
|
106
|
+
show: true,
|
|
107
|
+
topPosition: 0,
|
|
108
|
+
alert,
|
|
109
|
+
icon,
|
|
110
|
+
type,
|
|
111
|
+
text,
|
|
112
|
+
title,
|
|
113
|
+
actionLabel,
|
|
114
|
+
onAction,
|
|
115
|
+
onClose: onRemove,
|
|
116
|
+
onLayout
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// TODO: The current implementation modifies the toast data directly, which violates the immutability principle of the model.
|
|
120
|
+
// This works only because the data is local, but it's a hacky solution.
|
|
121
|
+
// We should implement an .unshift() method on the Signal to handle this correctly in the future.
|
|
122
|
+
// For now, this serves as a quick fix, but we need to address this properly to ensure data immutability.
|
|
123
|
+
|
|
124
|
+
const toasts = $toasts.get() || []
|
|
125
|
+
toasts.unshift(newToast)
|
|
126
|
+
$toasts.set(toasts)
|
|
127
|
+
}
|