Skip to content

Commit 690659c

Browse files
committed
feat(toast): handle different height case
1 parent 2404f9e commit 690659c

File tree

6 files changed

+106
-15
lines changed

6 files changed

+106
-15
lines changed

example/src/app/(home)/components/toast.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
useToast,
66
type ToastComponentProps,
77
} from 'heroui-native';
8+
import { useCallback } from 'react';
89
import { View } from 'react-native';
910
import type { UsageVariant } from '../../../components/component-presentation/types';
1011
import { UsageVariantFlatList } from '../../../components/component-presentation/usage-variant-flatlist';
@@ -76,15 +77,17 @@ import { UsageVariantFlatList } from '../../../components/component-presentation
7677

7778
const MyToast1 = (props: ToastComponentProps) => {
7879
const { id, hide } = props;
80+
console.log('🔴 id 🔴', id); // VS remove
7981

8082
return (
8183
<Toast
8284
variant="accent"
85+
placement="top"
8386
duration="persistent"
8487
className="flex-row items-center gap-3"
8588
{...props}
8689
>
87-
<View className="flex-1 h-[150px]">
90+
<View className="flex-1">
8891
<Toast.Label>{id}</Toast.Label>
8992
<Toast.Description>
9093
Use buttons below to control this toast
@@ -97,18 +100,22 @@ const MyToast1 = (props: ToastComponentProps) => {
97100

98101
const MyToast2 = (props: ToastComponentProps) => {
99102
const { id, hide } = props;
103+
console.log('🔴 id 🔴', id); // VS remove
100104

101105
return (
102106
<Toast
103107
variant="success"
108+
placement="top"
104109
duration="persistent"
105110
className="flex-row items-center gap-3"
106111
{...props}
107112
>
108-
<View className="flex-1 h-[100px]">
113+
<View className="flex-1">
109114
<Toast.Label>{id}</Toast.Label>
110115
<Toast.Description>
111-
Use buttons below to control this toast
116+
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Nam tenetur
117+
maxime ab laboriosam qui praesentium facere? Ad dolor fugiat,
118+
molestiae esse laudantium sed ut ullam.
112119
</Toast.Description>
113120
</View>
114121
<Toast.Action onPress={() => hide(id)}>Close</Toast.Action>
@@ -118,18 +125,24 @@ const MyToast2 = (props: ToastComponentProps) => {
118125

119126
const MyToast3 = (props: ToastComponentProps) => {
120127
const { id, hide } = props;
128+
console.log('🔴 id 🔴', id); // VS remove
121129

122130
return (
123131
<Toast
124132
variant="warning"
133+
placement="top"
125134
duration="persistent"
126135
className="flex-row items-center gap-3"
127136
{...props}
128137
>
129-
<View className="flex-1 h-[200px]">
138+
<View className="flex-1">
130139
<Toast.Label>{id}</Toast.Label>
131140
<Toast.Description>
132-
Use buttons below to control this toast
141+
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Nam tenetur
142+
maxime ab laboriosam qui praesentium facere? Ad dolor fugiat,
143+
molestiae esse laudantium sed ut ullam. Lorem, ipsum dolor sit amet
144+
consectetur adipisicing elit. Nam tenetur maxime ab laboriosam qui
145+
praesentium facere?
133146
</Toast.Description>
134147
</View>
135148
<Toast.Action onPress={() => hide(id)}>Close</Toast.Action>
@@ -139,9 +152,18 @@ const MyToast3 = (props: ToastComponentProps) => {
139152
const InteractiveDemoContent = () => {
140153
const { toast } = useToast();
141154

142-
const _renderToast1 = (props: ToastComponentProps) => <MyToast1 {...props} />;
143-
const _renderToast2 = (props: ToastComponentProps) => <MyToast2 {...props} />;
144-
const _renderToast3 = (props: ToastComponentProps) => <MyToast3 {...props} />;
155+
const _renderToast1 = useCallback(
156+
(props: ToastComponentProps) => <MyToast1 {...props} />,
157+
[]
158+
);
159+
const _renderToast2 = useCallback(
160+
(props: ToastComponentProps) => <MyToast2 {...props} />,
161+
[]
162+
);
163+
const _renderToast3 = useCallback(
164+
(props: ToastComponentProps) => <MyToast3 {...props} />,
165+
[]
166+
);
145167

146168
return (
147169
<View className="flex-1 px-5">

src/components/toast/toast.animation.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
withDecay,
1313
withSpring,
1414
withTiming,
15+
type SharedValue,
1516
} from 'react-native-reanimated';
1617
import { scheduleOnRN } from 'react-native-worklets';
1718
import {
@@ -75,6 +76,7 @@ export function useToastRootAnimation(options: {
7576
style: ViewStyle | undefined;
7677
index: number;
7778
total: number;
79+
heights: SharedValue<number[]>;
7880
placement: ToastPlacement;
7981
hide?: ((ids?: string | string[]) => void) | undefined;
8082
id?: string | undefined;
@@ -85,6 +87,7 @@ export function useToastRootAnimation(options: {
8587
style,
8688
index,
8789
total,
90+
heights,
8891
placement,
8992
hide,
9093
id,
@@ -289,10 +292,17 @@ export function useToastRootAnimation(options: {
289292
});
290293

291294
const rContainerStyle = useAnimatedStyle(() => {
295+
const lastToastId = Object.keys(heights.get())[
296+
Object.keys(heights.get()).length - 1
297+
];
298+
const lastToastHeight = heights.get()[lastToastId];
299+
292300
const sign = placement === 'top' ? 1 : -1;
293301

294-
const inputRange = [total - 1, total - 2];
295-
const opacityInputRange = [total - 3, total - 4];
302+
const totalValue = total.get();
303+
304+
const inputRange = [totalValue - 1, totalValue - 2];
305+
const opacityInputRange = [totalValue - 3, totalValue - 4];
296306

297307
const opacity = interpolate(index, opacityInputRange, opacityValue);
298308
const scale = interpolate(index, inputRange, scaleValue);
@@ -312,6 +322,7 @@ export function useToastRootAnimation(options: {
312322

313323
if (isAnimationDisabled) {
314324
return {
325+
height: lastToastHeight,
315326
pointerEvents: opacity === 0 ? 'none' : 'auto',
316327
opacity,
317328
transform: [
@@ -328,6 +339,13 @@ export function useToastRootAnimation(options: {
328339
}
329340

330341
return {
342+
height: lastToastHeight
343+
? withSpring(lastToastHeight, {
344+
damping: 100,
345+
stiffness: 1200,
346+
mass: 3,
347+
})
348+
: undefined,
331349
pointerEvents: opacity === 0 ? 'none' : 'auto',
332350
opacity: withTiming(opacity, opacityTimingConfig),
333351
transform: [

src/components/toast/toast.styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { tv } from 'tailwind-variants';
33
import { combineStyles } from '../../helpers/theme/utils/combine-styles';
44

55
const root = tv({
6-
base: 'rounded-3xl p-4 bg-surface border border-muted/10 shadow-2xl shadow-black/5',
6+
base: 'rounded-3xl p-4 bg-surface border border-muted/10 shadow-2xl shadow-black/5 overflow-hidden',
77
});
88

99
const label = tv({

src/components/toast/toast.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const ToastRoot = forwardRef<ViewRef, ToastRootProps>((props, ref) => {
3838
placement = 'top',
3939
index,
4040
total,
41+
heights,
4142
className,
4243
style,
4344
animation,
@@ -64,6 +65,7 @@ const ToastRoot = forwardRef<ViewRef, ToastRootProps>((props, ref) => {
6465
style: style as ViewStyle | undefined,
6566
index,
6667
total,
68+
heights,
6769
placement,
6870
hide,
6971
id,
@@ -88,6 +90,7 @@ const ToastRoot = forwardRef<ViewRef, ToastRootProps>((props, ref) => {
8890
entering={entering}
8991
exiting={exiting}
9092
>
93+
{/* Animated toast instance */}
9194
<AnimatedToastRoot
9295
ref={ref}
9396
className={tvStyles}
@@ -96,6 +99,25 @@ const ToastRoot = forwardRef<ViewRef, ToastRootProps>((props, ref) => {
9699
>
97100
{children}
98101
</AnimatedToastRoot>
102+
{/* Static toast instance */}
103+
<AnimatedToastRoot
104+
className={cn(
105+
tvStyles,
106+
'absolute top-[200px] opacity-0 pointer-events-none'
107+
)}
108+
style={[styleSheet.root, style]}
109+
onLayout={(event) => {
110+
const measuredHeight = event.nativeEvent.layout.height;
111+
heights.modify((value: Record<string, number>) => {
112+
'worklet';
113+
value[id] = measuredHeight;
114+
return value;
115+
});
116+
}}
117+
{...restProps}
118+
>
119+
{children}
120+
</AnimatedToastRoot>
99121
</Animated.View>
100122
</GestureDetector>
101123
</ToastProvider>

src/providers/toast/provider.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
useRef,
88
} from 'react';
99
import { View } from 'react-native';
10+
import { useSharedValue } from 'react-native-reanimated';
1011
import { InsetsContainer } from './insets-container';
1112
import { toastReducer } from './reducer';
1213
import { ToastItemRenderer } from './toast-item-renderer';
@@ -28,6 +29,10 @@ const ToasterContext = createContext<ToasterContextValue | null>(null);
2829
export function ToastProvider({ insets, children }: ToastProviderProps) {
2930
const [toasts, dispatch] = useReducer(toastReducer, []);
3031

32+
const heights = useSharedValue<Record<string, number>>({});
33+
34+
const total = useSharedValue<number>(0);
35+
3136
const idCounter = useRef(0);
3237

3338
/**
@@ -44,6 +49,8 @@ export function ToastProvider({ insets, children }: ToastProviderProps) {
4449
},
4550
});
4651

52+
total.set((value) => value + 1);
53+
4754
return id;
4855
}, []);
4956

@@ -54,13 +61,27 @@ export function ToastProvider({ insets, children }: ToastProviderProps) {
5461
if (ids === undefined) {
5562
// Hide all toasts
5663
dispatch({ type: 'HIDE_ALL' });
64+
heights.set({});
65+
total.set(0);
5766
} else {
5867
// Hide specific toast(s)
5968
const idsArray = Array.isArray(ids) ? ids : [ids];
69+
const idsToRemove = idsArray;
6070
dispatch({
6171
type: 'HIDE',
6272
payload: { ids: idsArray },
6373
});
74+
75+
heights.modify(<T extends Record<string, number>>(value: T): T => {
76+
'worklet';
77+
const result = { ...value };
78+
for (const id of idsToRemove) {
79+
delete result[id];
80+
}
81+
return result;
82+
});
83+
84+
total.set((value) => value - 1);
6485
}
6586
}, []);
6687

@@ -92,7 +113,8 @@ export function ToastProvider({ insets, children }: ToastProviderProps) {
92113
show={show}
93114
hide={hide}
94115
index={index}
95-
total={toasts.length}
116+
total={total}
117+
heights={heights}
96118
/>
97119
))}
98120
</View>

src/providers/toast/toast-item-renderer.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ interface ToastItemRendererProps {
1414
* Only re-renders when the toast item itself changes
1515
*/
1616
export const ToastItemRenderer = memo(
17-
({ toastItem, show, hide, index, total }: ToastItemRendererProps) => {
17+
({
18+
toastItem,
19+
show,
20+
hide,
21+
index,
22+
total,
23+
heights,
24+
}: ToastItemRendererProps) => {
1825
if (typeof toastItem.component !== 'function') {
1926
throw new Error(
2027
'Toast component must be a function that receives ToastComponentProps'
@@ -25,6 +32,7 @@ export const ToastItemRenderer = memo(
2532
id: toastItem.id,
2633
index,
2734
total,
35+
heights,
2836
show,
2937
hide,
3038
});
@@ -37,8 +45,7 @@ export const ToastItemRenderer = memo(
3745
return (
3846
prevProps.toastItem.id === nextProps.toastItem.id &&
3947
prevProps.toastItem.component === nextProps.toastItem.component &&
40-
prevProps.index === nextProps.index &&
41-
prevProps.total === nextProps.total
48+
prevProps.index === nextProps.index
4249
);
4350
}
4451
);

0 commit comments

Comments
 (0)