@impulse-ui-native/toast 6.0.0 → 6.1.0
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/README.md
CHANGED
|
@@ -101,7 +101,7 @@ The timer starts after entry animation finishes, pauses when the app leaves the
|
|
|
101
101
|
|
|
102
102
|
onOpen and onOpenFinished run during entry; onClose runs on dismissal, and onCloseFinished lets OverlayHost remove the entry after exit. Automatic dismissal calls the existing OverlayStore.close(id). Duration, foreground/background timer handling, and animation are component-owned. Unmounting cancels animation, timer and app-state subscriptions.
|
|
103
103
|
|
|
104
|
-
Toasts fade and translate by the theme edge offset. Top toasts sit 8 px below the top safe-area inset, using the toast topOffset token; bottom and side gaps use the 16 px edgeOffset token. Placements and IDs should remain stable for an entry's lifetime. Root is designed for imperative OverlayStore registration; use the returned controller to dismiss it.
|
|
104
|
+
Toasts fade and translate by the theme edge offset. Top toasts sit 8 px below the top safe-area inset, using the toast topOffset token; bottom and side gaps use the 16 px edgeOffset token. The newest toast is full size at the screen edge. Older toasts stack downward at both placements by the existing `gap` token and shrink by `stackScaleStep` (4%) per level. Changes to stack depth animate position and scale together over 220 ms, including moving forward when newer entries are removed. `stackLimit` defaults to five visual levels: scales are 100%, 96%, 92%, 88%, and 84%. All further toasts share the fifth level's scale and offset, behind newer entries; the limit does not discard or queue toasts. Component identity is the registered `Component`, including wrappers. Placements and IDs should remain stable for an entry's lifetime. Root is designed for imperative OverlayStore registration; use the returned controller to dismiss it.
|
|
105
105
|
|
|
106
106
|
## Theme
|
|
107
107
|
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { memo, useMemo } from "react";
|
|
2
|
+
import { memo, useEffect, useMemo } from "react";
|
|
3
3
|
import { StyleSheet } from "react-native";
|
|
4
|
-
import Animated, {
|
|
4
|
+
import Animated, {
|
|
5
|
+
cancelAnimation,
|
|
6
|
+
useAnimatedStyle,
|
|
7
|
+
useSharedValue,
|
|
8
|
+
withTiming
|
|
9
|
+
} from "react-native-reanimated";
|
|
5
10
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
11
|
+
import { useOverlayLayer } from "@impulse-ui-native/overlay";
|
|
6
12
|
import { useComponentsTokens, useThemedStyles } from "@impulse-ui-native/theme";
|
|
7
|
-
import { DefaultDuration } from "../constants/toast.constants";
|
|
13
|
+
import { DefaultDuration, StackDuration } from "../constants/toast.constants";
|
|
8
14
|
import { ToastContext } from "../contexts/toast.context";
|
|
9
15
|
import { useToastLifecycle } from "../hooks/use-toast-lifecycle.hook";
|
|
10
16
|
const ToastRoot = memo(function ToastRoot2(props) {
|
|
11
17
|
const {
|
|
18
|
+
id,
|
|
12
19
|
children,
|
|
13
20
|
placement = "bottom",
|
|
14
21
|
tone = "success",
|
|
@@ -18,15 +25,22 @@ const ToastRoot = memo(function ToastRoot2(props) {
|
|
|
18
25
|
const insets = useSafeAreaInsets();
|
|
19
26
|
const tokens = useComponentsTokens().toast;
|
|
20
27
|
const lifecycle = useToastLifecycle(props, duration);
|
|
28
|
+
const toastLayer = useOverlayLayer(id, void 0, "newest-first");
|
|
21
29
|
const styles = useThemedStyles(themedStyles);
|
|
22
30
|
const { progress } = lifecycle;
|
|
23
31
|
const edgeOffset = tokens.edgeOffset;
|
|
32
|
+
const stackDepth = Math.min(
|
|
33
|
+
Math.max(0, toastLayer),
|
|
34
|
+
Math.max(0, tokens.stackLimit - 1)
|
|
35
|
+
);
|
|
36
|
+
const animatedDepth = useSharedValue(stackDepth);
|
|
24
37
|
const animatedStyle = useAnimatedStyle(() => ({
|
|
25
38
|
opacity: progress.value,
|
|
26
39
|
transform: [
|
|
27
40
|
{
|
|
28
|
-
translateY: (1 - progress.value) * (placement === "top" ? -edgeOffset : edgeOffset)
|
|
29
|
-
}
|
|
41
|
+
translateY: (1 - progress.value) * (placement === "top" ? -edgeOffset : edgeOffset) + animatedDepth.value * tokens.gap
|
|
42
|
+
},
|
|
43
|
+
{ scale: Math.max(0, 1 - animatedDepth.value * tokens.stackScaleStep) }
|
|
30
44
|
]
|
|
31
45
|
}));
|
|
32
46
|
const context = useMemo(
|
|
@@ -44,12 +58,17 @@ const ToastRoot = memo(function ToastRoot2(props) {
|
|
|
44
58
|
left: insets.left + tokens.edgeOffset,
|
|
45
59
|
right: insets.right + tokens.edgeOffset,
|
|
46
60
|
[placement]: insets[placement] + (placement === "top" ? tokens.topOffset : tokens.edgeOffset),
|
|
61
|
+
transformOrigin: "center top",
|
|
47
62
|
zIndex: tokens.zIndexBase + layer
|
|
48
63
|
},
|
|
49
64
|
animatedStyle
|
|
50
65
|
],
|
|
51
66
|
[styles.container, insets, tokens, placement, layer, animatedStyle]
|
|
52
67
|
);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
animatedDepth.value = withTiming(stackDepth, { duration: StackDuration });
|
|
70
|
+
return () => cancelAnimation(animatedDepth);
|
|
71
|
+
}, [animatedDepth, stackDepth]);
|
|
53
72
|
if (lifecycle.finished) {
|
|
54
73
|
return null;
|
|
55
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/toast-root.tsx"],"sourcesContent":["import { memo, useMemo } from \"react\";\nimport { StyleSheet } from \"react-native\";\nimport Animated, {
|
|
1
|
+
{"version":3,"sources":["../../src/components/toast-root.tsx"],"sourcesContent":["import { memo, useEffect, useMemo } from \"react\";\nimport { StyleSheet } from \"react-native\";\nimport Animated, {\n cancelAnimation,\n useAnimatedStyle,\n useSharedValue,\n withTiming,\n} from \"react-native-reanimated\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\n\nimport type { AppTheme } from \"@impulse-ui-native/theme\";\nimport { useOverlayLayer } from \"@impulse-ui-native/overlay\";\nimport { useComponentsTokens, useThemedStyles } from \"@impulse-ui-native/theme\";\n\nimport type { ToastRootProps } from \"../types\";\nimport { DefaultDuration, StackDuration } from \"../constants/toast.constants\";\nimport { ToastContext } from \"../contexts/toast.context\";\nimport { useToastLifecycle } from \"../hooks/use-toast-lifecycle.hook\";\n\nexport const ToastRoot = memo(function ToastRoot(props: ToastRootProps) {\n const {\n id,\n children,\n placement = \"bottom\",\n tone = \"success\",\n duration = DefaultDuration,\n layer = 0,\n } = props;\n\n const insets = useSafeAreaInsets();\n\n const tokens = useComponentsTokens().toast;\n\n const lifecycle = useToastLifecycle(props, duration);\n const toastLayer = useOverlayLayer(id, undefined, \"newest-first\");\n const styles = useThemedStyles(themedStyles);\n const { progress } = lifecycle;\n const edgeOffset = tokens.edgeOffset;\n const stackDepth = Math.min(\n Math.max(0, toastLayer),\n Math.max(0, tokens.stackLimit - 1),\n );\n const animatedDepth = useSharedValue(stackDepth);\n const animatedStyle = useAnimatedStyle(() => ({\n opacity: progress.value,\n transform: [\n {\n translateY:\n (1 - progress.value) *\n (placement === \"top\" ? -edgeOffset : edgeOffset) +\n animatedDepth.value * tokens.gap,\n },\n { scale: Math.max(0, 1 - animatedDepth.value * tokens.stackScaleStep) },\n ],\n }));\n\n const context = useMemo(\n () => ({\n tone,\n interactive: lifecycle.interactive,\n close: lifecycle.close,\n }),\n [tone, lifecycle.interactive, lifecycle.close],\n );\n const containerStyle = useMemo(\n () => [\n styles.container,\n {\n left: insets.left + tokens.edgeOffset,\n right: insets.right + tokens.edgeOffset,\n [placement]:\n insets[placement] +\n (placement === \"top\" ? tokens.topOffset : tokens.edgeOffset),\n transformOrigin: \"center top\",\n zIndex: tokens.zIndexBase + layer,\n },\n animatedStyle,\n ],\n [styles.container, insets, tokens, placement, layer, animatedStyle],\n );\n\n useEffect(() => {\n animatedDepth.value = withTiming(stackDepth, { duration: StackDuration });\n\n return () => cancelAnimation(animatedDepth);\n }, [animatedDepth, stackDepth]);\n\n if (lifecycle.finished) {\n return null;\n }\n\n return (\n <ToastContext.Provider value={context}>\n <Animated.View\n pointerEvents={lifecycle.interactive ? \"auto\" : \"none\"}\n style={containerStyle}\n >\n {children}\n </Animated.View>\n </ToastContext.Provider>\n );\n});\n\nfunction themedStyles(theme: AppTheme) {\n const tokens = theme.components.toast;\n\n return StyleSheet.create({\n container: {\n position: \"absolute\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: tokens.gap,\n padding: tokens.padding,\n borderRadius: tokens.borderRadius,\n borderWidth: tokens.borderWidth,\n borderColor: tokens.borderColor,\n backgroundColor: tokens.backgroundColor,\n },\n });\n}\n"],"mappings":"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","names":["ToastRoot"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
declare const DefaultDuration = 4000;
|
|
2
2
|
declare const EnterDuration = 180;
|
|
3
3
|
declare const ExitDuration = 150;
|
|
4
|
+
declare const StackDuration = 220;
|
|
4
5
|
|
|
5
|
-
export { DefaultDuration, EnterDuration, ExitDuration };
|
|
6
|
+
export { DefaultDuration, EnterDuration, ExitDuration, StackDuration };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const DefaultDuration = 4e3;
|
|
2
2
|
const EnterDuration = 180;
|
|
3
3
|
const ExitDuration = 150;
|
|
4
|
+
const StackDuration = 220;
|
|
4
5
|
export {
|
|
5
6
|
DefaultDuration,
|
|
6
7
|
EnterDuration,
|
|
7
|
-
ExitDuration
|
|
8
|
+
ExitDuration,
|
|
9
|
+
StackDuration
|
|
8
10
|
};
|
|
9
11
|
//# sourceMappingURL=toast.constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/constants/toast.constants.ts"],"sourcesContent":["export const DefaultDuration = 4000;\nexport const EnterDuration = 180;\nexport const ExitDuration = 150;\n"],"mappings":"AAAO,MAAM,kBAAkB;AACxB,MAAM,gBAAgB;AACtB,MAAM,eAAe;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/constants/toast.constants.ts"],"sourcesContent":["export const DefaultDuration = 4000;\nexport const EnterDuration = 180;\nexport const ExitDuration = 150;\nexport const StackDuration = 220;\n"],"mappings":"AAAO,MAAM,kBAAkB;AACxB,MAAM,gBAAgB;AACtB,MAAM,eAAe;AACrB,MAAM,gBAAgB;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@impulse-ui-native/toast",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "Compound toast notifications for ImpulseUI Native.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
],
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@impulse-ui-native/theme": "6.
|
|
31
|
-
"@impulse-ui-native/
|
|
32
|
-
"@impulse-ui-native/
|
|
33
|
-
"@impulse-ui-native/core": "6.
|
|
34
|
-
"@impulse-ui-native/icon": "6.
|
|
30
|
+
"@impulse-ui-native/theme": "6.1.0",
|
|
31
|
+
"@impulse-ui-native/primitives": "6.1.0",
|
|
32
|
+
"@impulse-ui-native/overlay": "6.1.0",
|
|
33
|
+
"@impulse-ui-native/core": "6.1.0",
|
|
34
|
+
"@impulse-ui-native/icon": "6.1.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/react": "~19.2.14",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"react-native-safe-area-context": "~5.7.0",
|
|
45
45
|
"typescript": "~6.0.3",
|
|
46
46
|
"react-native-svg": "15.15.4",
|
|
47
|
-
"@impulse-ui-native/prettier-config": "1.0.0",
|
|
48
47
|
"@impulse-ui-native/eslint-config": "1.0.0",
|
|
48
|
+
"@impulse-ui-native/prettier-config": "1.0.0",
|
|
49
49
|
"@impulse-ui-native/tsconfig": "1.0.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|