jfs-components 0.0.42 → 0.0.43
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/lib/commonjs/components/Toast/Toast.js +93 -0
- package/lib/commonjs/components/Toast/ToastProvider.js +61 -0
- package/lib/commonjs/components/Toast/useToast.js +61 -0
- package/lib/commonjs/components/index.js +39 -0
- package/lib/commonjs/design-tokens/JFS Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Toast/Toast.js +88 -0
- package/lib/module/components/Toast/ToastProvider.js +55 -0
- package/lib/module/components/Toast/useToast.js +54 -0
- package/lib/module/components/index.js +4 -1
- package/lib/module/design-tokens/JFS Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Toast/Toast.d.ts +14 -0
- package/lib/typescript/src/components/Toast/ToastProvider.d.ts +11 -0
- package/lib/typescript/src/components/Toast/useToast.d.ts +24 -0
- package/lib/typescript/src/components/index.d.ts +3 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Toast/Toast.tsx +105 -0
- package/src/components/Toast/ToastProvider.tsx +75 -0
- package/src/components/Toast/useToast.ts +80 -0
- package/src/components/index.ts +3 -0
- package/src/design-tokens/JFS Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
2
|
+
import { type ToastPlacement } from './useToast';
|
|
3
|
+
export interface ToastProps {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
timeout?: number | undefined;
|
|
7
|
+
onClose?: (() => void) | undefined;
|
|
8
|
+
modes?: Record<string, any> | undefined;
|
|
9
|
+
placement?: ToastPlacement | undefined;
|
|
10
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
11
|
+
}
|
|
12
|
+
declare function Toast({ id, title, timeout, onClose, modes, placement, style, }: ToastProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export default Toast;
|
|
14
|
+
//# sourceMappingURL=Toast.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type ToastPlacement } from './useToast';
|
|
3
|
+
export interface ToastProviderProps {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
maxVisibleToasts?: number;
|
|
6
|
+
placement?: ToastPlacement;
|
|
7
|
+
modes?: Record<string, any>;
|
|
8
|
+
}
|
|
9
|
+
declare function ToastProvider({ children, maxVisibleToasts, placement, modes, }: ToastProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export default ToastProvider;
|
|
11
|
+
//# sourceMappingURL=ToastProvider.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ToastPlacement = 'top' | 'bottom';
|
|
2
|
+
export interface ToastOptions {
|
|
3
|
+
title: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
modes?: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
export interface ToastEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
timeout: number;
|
|
12
|
+
onClose?: (() => void) | undefined;
|
|
13
|
+
modes?: Record<string, any> | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare function addToast(options: ToastOptions): string;
|
|
16
|
+
export declare function closeToast(id: string): void;
|
|
17
|
+
export declare function closeAll(): void;
|
|
18
|
+
export declare function useToast(): {
|
|
19
|
+
toasts: ToastEntry[];
|
|
20
|
+
addToast: typeof addToast;
|
|
21
|
+
closeToast: typeof closeToast;
|
|
22
|
+
closeAll: typeof closeAll;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=useToast.d.ts.map
|
|
@@ -50,4 +50,7 @@ export { default as InputSearch, type InputSearchProps } from './InputSearch/Inp
|
|
|
50
50
|
export { default as SupportText, type SupportTextProps } from './SupportText/SupportText';
|
|
51
51
|
export { default as SupportTextIcon, type SupportTextIconProps } from './SupportText/SupportTextIcon';
|
|
52
52
|
export { default as RadioButton, type RadioButtonProps } from './RadioButton/RadioButton';
|
|
53
|
+
export { default as Toast, type ToastProps } from './Toast/Toast';
|
|
54
|
+
export { default as ToastProvider, type ToastProviderProps } from './Toast/ToastProvider';
|
|
55
|
+
export { useToast, addToast, closeToast, closeAll, type ToastOptions, type ToastEntry, type ToastPlacement } from './Toast/useToast';
|
|
53
56
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Auto-generated from SVG files in src/icons/
|
|
5
5
|
* DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
|
|
6
6
|
*
|
|
7
|
-
* Generated: 2026-02-
|
|
7
|
+
* Generated: 2026-02-19T12:53:50.558Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
import { StyleSheet, Text, type StyleProp, type ViewStyle } from 'react-native'
|
|
3
|
+
import Animated, { FadeIn, FadeOut, SlideInDown, SlideInUp } from 'react-native-reanimated'
|
|
4
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
5
|
+
import { closeToast, type ToastPlacement } from './useToast'
|
|
6
|
+
|
|
7
|
+
export interface ToastProps {
|
|
8
|
+
id: string
|
|
9
|
+
title: string
|
|
10
|
+
timeout?: number | undefined
|
|
11
|
+
onClose?: (() => void) | undefined
|
|
12
|
+
modes?: Record<string, any> | undefined
|
|
13
|
+
placement?: ToastPlacement | undefined
|
|
14
|
+
style?: StyleProp<ViewStyle> | undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ANIMATION_DURATION = 250
|
|
18
|
+
|
|
19
|
+
function Toast({
|
|
20
|
+
id,
|
|
21
|
+
title,
|
|
22
|
+
timeout = 4000,
|
|
23
|
+
onClose,
|
|
24
|
+
modes = {},
|
|
25
|
+
placement = 'bottom',
|
|
26
|
+
style,
|
|
27
|
+
}: ToastProps) {
|
|
28
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (timeout <= 0) return
|
|
32
|
+
timerRef.current = setTimeout(() => closeToast(id), timeout)
|
|
33
|
+
return () => {
|
|
34
|
+
if (timerRef.current) clearTimeout(timerRef.current)
|
|
35
|
+
}
|
|
36
|
+
}, [id, timeout])
|
|
37
|
+
|
|
38
|
+
const backgroundColor = getVariableByName('toast/background', modes) || '#303338'
|
|
39
|
+
const foreground = getVariableByName('toast/foreground', modes) || '#ffffff'
|
|
40
|
+
const fontSize = getVariableByName('toast/fontSize', modes) || 14
|
|
41
|
+
const fontFamily = getVariableByName('toast/fontFamily', modes) || undefined
|
|
42
|
+
const fontWeight = getVariableByName('toast/fontWeight', modes) || '500'
|
|
43
|
+
const lineHeight = getVariableByName('toast/lineHeight', modes) || 18
|
|
44
|
+
const radius = getVariableByName('toast/radius', modes) || 14
|
|
45
|
+
const paddingHorizontal = getVariableByName('toast/padding/horizontal', modes) || 16
|
|
46
|
+
const paddingVertical = getVariableByName('toast/padding/vertical', modes) || 14
|
|
47
|
+
const gap = getVariableByName('toast/gap', modes) || 8
|
|
48
|
+
const borderWidth = getVariableByName('toast/border/size', modes) || 1
|
|
49
|
+
const borderColor = getVariableByName('toast/border/color', modes) || 'rgba(255,255,255,0.1)'
|
|
50
|
+
const shadowBlurPrimary = getVariableByName('toast/shadow/primary/blur', modes) || 48
|
|
51
|
+
const shadowOffsetYPrimary = getVariableByName('toast/shadow/primary/offsetY', modes) || 16
|
|
52
|
+
const shadowColorPrimary = getVariableByName('toast/shadow/primary/color', modes) || 'rgba(13,13,15,0.16)'
|
|
53
|
+
|
|
54
|
+
const enterAnimation = placement === 'top'
|
|
55
|
+
? SlideInUp.duration(ANIMATION_DURATION)
|
|
56
|
+
: SlideInDown.duration(ANIMATION_DURATION)
|
|
57
|
+
|
|
58
|
+
const containerStyle: ViewStyle = {
|
|
59
|
+
backgroundColor,
|
|
60
|
+
borderRadius: radius,
|
|
61
|
+
paddingHorizontal,
|
|
62
|
+
paddingVertical,
|
|
63
|
+
gap,
|
|
64
|
+
borderWidth,
|
|
65
|
+
borderColor,
|
|
66
|
+
shadowColor: shadowColorPrimary,
|
|
67
|
+
shadowOffset: { width: 0, height: shadowOffsetYPrimary },
|
|
68
|
+
shadowOpacity: 1,
|
|
69
|
+
shadowRadius: shadowBlurPrimary / 2,
|
|
70
|
+
elevation: 8,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const textStyle = {
|
|
74
|
+
color: foreground,
|
|
75
|
+
fontSize,
|
|
76
|
+
fontFamily,
|
|
77
|
+
fontWeight: String(fontWeight) as any,
|
|
78
|
+
lineHeight,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Animated.View
|
|
83
|
+
entering={enterAnimation}
|
|
84
|
+
exiting={FadeOut.duration(ANIMATION_DURATION)}
|
|
85
|
+
style={[styles.toast, containerStyle, style]}
|
|
86
|
+
accessibilityRole="alert"
|
|
87
|
+
accessibilityLiveRegion="assertive"
|
|
88
|
+
>
|
|
89
|
+
<Text style={textStyle} numberOfLines={2}>
|
|
90
|
+
{title}
|
|
91
|
+
</Text>
|
|
92
|
+
</Animated.View>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const styles = StyleSheet.create({
|
|
97
|
+
toast: {
|
|
98
|
+
alignSelf: 'center',
|
|
99
|
+
maxWidth: '90%',
|
|
100
|
+
minWidth: 200,
|
|
101
|
+
overflow: 'hidden',
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
export default Toast
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import { StyleSheet, View } from 'react-native'
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
4
|
+
import { useToast, type ToastPlacement } from './useToast'
|
|
5
|
+
import Toast from './Toast'
|
|
6
|
+
|
|
7
|
+
export interface ToastProviderProps {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
maxVisibleToasts?: number
|
|
10
|
+
placement?: ToastPlacement
|
|
11
|
+
modes?: Record<string, any>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ToastProvider({
|
|
15
|
+
children,
|
|
16
|
+
maxVisibleToasts = 3,
|
|
17
|
+
placement = 'bottom',
|
|
18
|
+
modes,
|
|
19
|
+
}: ToastProviderProps) {
|
|
20
|
+
const { toasts } = useToast()
|
|
21
|
+
const insets = useSafeAreaInsets()
|
|
22
|
+
|
|
23
|
+
const visibleToasts = useMemo(
|
|
24
|
+
() => toasts.slice(-maxVisibleToasts),
|
|
25
|
+
[toasts, maxVisibleToasts],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const regionStyle = useMemo(
|
|
29
|
+
() => [
|
|
30
|
+
styles.region,
|
|
31
|
+
placement === 'top'
|
|
32
|
+
? { top: insets.top + 8 }
|
|
33
|
+
: { bottom: insets.bottom + 8 },
|
|
34
|
+
],
|
|
35
|
+
[placement, insets.top, insets.bottom],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View style={styles.container}>
|
|
40
|
+
{children}
|
|
41
|
+
{visibleToasts.length > 0 && (
|
|
42
|
+
<View style={regionStyle} pointerEvents="box-none">
|
|
43
|
+
{visibleToasts.map((entry) => (
|
|
44
|
+
<Toast
|
|
45
|
+
key={entry.id}
|
|
46
|
+
id={entry.id}
|
|
47
|
+
title={entry.title}
|
|
48
|
+
timeout={entry.timeout}
|
|
49
|
+
onClose={entry.onClose}
|
|
50
|
+
modes={entry.modes ?? modes}
|
|
51
|
+
placement={placement}
|
|
52
|
+
/>
|
|
53
|
+
))}
|
|
54
|
+
</View>
|
|
55
|
+
)}
|
|
56
|
+
</View>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const styles = StyleSheet.create({
|
|
61
|
+
container: {
|
|
62
|
+
flex: 1,
|
|
63
|
+
},
|
|
64
|
+
region: {
|
|
65
|
+
position: 'absolute',
|
|
66
|
+
left: 0,
|
|
67
|
+
right: 0,
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
gap: 8,
|
|
70
|
+
zIndex: 9999,
|
|
71
|
+
pointerEvents: 'box-none',
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export default ToastProvider
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from 'react'
|
|
2
|
+
|
|
3
|
+
export type ToastPlacement = 'top' | 'bottom'
|
|
4
|
+
|
|
5
|
+
export interface ToastOptions {
|
|
6
|
+
title: string
|
|
7
|
+
timeout?: number
|
|
8
|
+
onClose?: () => void
|
|
9
|
+
modes?: Record<string, any>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ToastEntry {
|
|
13
|
+
id: string
|
|
14
|
+
title: string
|
|
15
|
+
timeout: number
|
|
16
|
+
onClose?: (() => void) | undefined
|
|
17
|
+
modes?: Record<string, any> | undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Listener = () => void
|
|
21
|
+
|
|
22
|
+
let idCounter = 0
|
|
23
|
+
let toasts: ToastEntry[] = []
|
|
24
|
+
const listeners = new Set<Listener>()
|
|
25
|
+
|
|
26
|
+
function emit() {
|
|
27
|
+
for (const listener of listeners) {
|
|
28
|
+
listener()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getSnapshot(): ToastEntry[] {
|
|
33
|
+
return toasts
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function subscribe(listener: Listener): () => void {
|
|
37
|
+
listeners.add(listener)
|
|
38
|
+
return () => {
|
|
39
|
+
listeners.delete(listener)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function addToast(options: ToastOptions): string {
|
|
44
|
+
const id = `toast-${++idCounter}`
|
|
45
|
+
const entry: ToastEntry = {
|
|
46
|
+
id,
|
|
47
|
+
title: options.title,
|
|
48
|
+
timeout: options.timeout ?? 4000,
|
|
49
|
+
onClose: options.onClose,
|
|
50
|
+
modes: options.modes,
|
|
51
|
+
}
|
|
52
|
+
toasts = [...toasts, entry]
|
|
53
|
+
emit()
|
|
54
|
+
return id
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function closeToast(id: string): void {
|
|
58
|
+
const entry = toasts.find((t) => t.id === id)
|
|
59
|
+
toasts = toasts.filter((t) => t.id !== id)
|
|
60
|
+
emit()
|
|
61
|
+
entry?.onClose?.()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function closeAll(): void {
|
|
65
|
+
const prev = toasts
|
|
66
|
+
toasts = []
|
|
67
|
+
emit()
|
|
68
|
+
prev.forEach((t) => t.onClose?.())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function useToast() {
|
|
72
|
+
const queue = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
toasts: queue,
|
|
76
|
+
addToast: useCallback(addToast, []),
|
|
77
|
+
closeToast: useCallback(closeToast, []),
|
|
78
|
+
closeAll: useCallback(closeAll, []),
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -51,3 +51,6 @@ export { default as InputSearch, type InputSearchProps } from './InputSearch/Inp
|
|
|
51
51
|
export { default as SupportText, type SupportTextProps } from './SupportText/SupportText';
|
|
52
52
|
export { default as SupportTextIcon, type SupportTextIconProps } from './SupportText/SupportTextIcon';
|
|
53
53
|
export { default as RadioButton, type RadioButtonProps } from './RadioButton/RadioButton';
|
|
54
|
+
export { default as Toast, type ToastProps } from './Toast/Toast';
|
|
55
|
+
export { default as ToastProvider, type ToastProviderProps } from './Toast/ToastProvider';
|
|
56
|
+
export { useToast, addToast, closeToast, closeAll, type ToastOptions, type ToastEntry, type ToastPlacement } from './Toast/useToast';
|