@cdx-ui/components 0.0.1-beta.159 → 0.0.1-beta.160

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.
Files changed (105) hide show
  1. package/coverage.svg +1 -1
  2. package/lib/commonjs/components/Menu/index.js +499 -0
  3. package/lib/commonjs/components/Menu/index.js.map +1 -0
  4. package/lib/commonjs/components/Menu/styles.js +100 -0
  5. package/lib/commonjs/components/Menu/styles.js.map +1 -0
  6. package/lib/commonjs/components/Select/index.js +57 -22
  7. package/lib/commonjs/components/Select/index.js.map +1 -1
  8. package/lib/commonjs/components/Select/styles.js +1 -40
  9. package/lib/commonjs/components/Select/styles.js.map +1 -1
  10. package/lib/commonjs/components/Slider/getSliderPopupPosition.js +16 -6
  11. package/lib/commonjs/components/Slider/getSliderPopupPosition.js.map +1 -1
  12. package/lib/commonjs/components/Slider/index.js +17 -5
  13. package/lib/commonjs/components/Slider/index.js.map +1 -1
  14. package/lib/commonjs/components/Toast/index.js +1 -1
  15. package/lib/commonjs/components/Toast/index.js.map +1 -1
  16. package/lib/commonjs/components/index.js +12 -0
  17. package/lib/commonjs/components/index.js.map +1 -1
  18. package/lib/commonjs/components/picker/surfaces.js +16 -9
  19. package/lib/commonjs/components/picker/surfaces.js.map +1 -1
  20. package/lib/commonjs/figma/Menu.Item.figma.js +52 -0
  21. package/lib/commonjs/figma/Menu.Item.figma.js.map +1 -0
  22. package/lib/commonjs/figma/Menu.figma.js +29 -0
  23. package/lib/commonjs/figma/Menu.figma.js.map +1 -0
  24. package/lib/commonjs/providers/forge-design/provider.js +25 -11
  25. package/lib/commonjs/providers/forge-design/provider.js.map +1 -1
  26. package/lib/commonjs/providers/toast/insets-container.js +4 -1
  27. package/lib/commonjs/providers/toast/insets-container.js.map +1 -1
  28. package/lib/commonjs/providers/toast/provider.js +126 -45
  29. package/lib/commonjs/providers/toast/provider.js.map +1 -1
  30. package/lib/commonjs/providers/toast/toast-item-renderer.js +2 -1
  31. package/lib/commonjs/providers/toast/toast-item-renderer.js.map +1 -1
  32. package/lib/module/components/Menu/index.js +495 -0
  33. package/lib/module/components/Menu/index.js.map +1 -0
  34. package/lib/module/components/Menu/styles.js +96 -0
  35. package/lib/module/components/Menu/styles.js.map +1 -0
  36. package/lib/module/components/Select/index.js +55 -20
  37. package/lib/module/components/Select/index.js.map +1 -1
  38. package/lib/module/components/Select/styles.js +0 -39
  39. package/lib/module/components/Select/styles.js.map +1 -1
  40. package/lib/module/components/Slider/getSliderPopupPosition.js +16 -6
  41. package/lib/module/components/Slider/getSliderPopupPosition.js.map +1 -1
  42. package/lib/module/components/Slider/index.js +20 -8
  43. package/lib/module/components/Slider/index.js.map +1 -1
  44. package/lib/module/components/Toast/index.js +1 -1
  45. package/lib/module/components/Toast/index.js.map +1 -1
  46. package/lib/module/components/index.js +1 -0
  47. package/lib/module/components/index.js.map +1 -1
  48. package/lib/module/components/picker/surfaces.js +18 -11
  49. package/lib/module/components/picker/surfaces.js.map +1 -1
  50. package/lib/module/figma/Menu.Item.figma.js +46 -0
  51. package/lib/module/figma/Menu.Item.figma.js.map +1 -0
  52. package/lib/module/figma/Menu.figma.js +23 -0
  53. package/lib/module/figma/Menu.figma.js.map +1 -0
  54. package/lib/module/providers/forge-design/provider.js +26 -12
  55. package/lib/module/providers/forge-design/provider.js.map +1 -1
  56. package/lib/module/providers/toast/insets-container.js +5 -2
  57. package/lib/module/providers/toast/insets-container.js.map +1 -1
  58. package/lib/module/providers/toast/provider.js +126 -45
  59. package/lib/module/providers/toast/provider.js.map +1 -1
  60. package/lib/module/providers/toast/toast-item-renderer.js +2 -1
  61. package/lib/module/providers/toast/toast-item-renderer.js.map +1 -1
  62. package/lib/typescript/components/Menu/index.d.ts +119 -0
  63. package/lib/typescript/components/Menu/index.d.ts.map +1 -0
  64. package/lib/typescript/components/Menu/styles.d.ts +24 -0
  65. package/lib/typescript/components/Menu/styles.d.ts.map +1 -0
  66. package/lib/typescript/components/Select/index.d.ts.map +1 -1
  67. package/lib/typescript/components/Select/styles.d.ts +0 -7
  68. package/lib/typescript/components/Select/styles.d.ts.map +1 -1
  69. package/lib/typescript/components/Slider/getSliderPopupPosition.d.ts +1 -1
  70. package/lib/typescript/components/Slider/getSliderPopupPosition.d.ts.map +1 -1
  71. package/lib/typescript/components/Slider/index.d.ts.map +1 -1
  72. package/lib/typescript/components/Toast/types.d.ts +2 -2
  73. package/lib/typescript/components/Toast/types.d.ts.map +1 -1
  74. package/lib/typescript/components/index.d.ts +1 -0
  75. package/lib/typescript/components/index.d.ts.map +1 -1
  76. package/lib/typescript/components/picker/surfaces.d.ts.map +1 -1
  77. package/lib/typescript/figma/Menu.Item.figma.d.ts +8 -0
  78. package/lib/typescript/figma/Menu.Item.figma.d.ts.map +1 -0
  79. package/lib/typescript/figma/Menu.figma.d.ts +8 -0
  80. package/lib/typescript/figma/Menu.figma.d.ts.map +1 -0
  81. package/lib/typescript/providers/forge-design/provider.d.ts.map +1 -1
  82. package/lib/typescript/providers/toast/insets-container.d.ts.map +1 -1
  83. package/lib/typescript/providers/toast/lifecycle.context.d.ts +2 -2
  84. package/lib/typescript/providers/toast/lifecycle.context.d.ts.map +1 -1
  85. package/lib/typescript/providers/toast/provider.d.ts.map +1 -1
  86. package/lib/typescript/providers/toast/toast-item-renderer.d.ts.map +1 -1
  87. package/lib/typescript/providers/toast/types.d.ts +22 -8
  88. package/lib/typescript/providers/toast/types.d.ts.map +1 -1
  89. package/package.json +5 -5
  90. package/src/components/Menu/index.tsx +654 -0
  91. package/src/components/Menu/styles.ts +153 -0
  92. package/src/components/Select/index.tsx +58 -23
  93. package/src/components/Select/styles.ts +0 -59
  94. package/src/components/Slider/getSliderPopupPosition.ts +19 -7
  95. package/src/components/Slider/index.tsx +25 -9
  96. package/src/components/Toast/index.tsx +1 -1
  97. package/src/components/Toast/types.ts +2 -2
  98. package/src/components/index.ts +1 -0
  99. package/src/components/picker/surfaces.tsx +13 -9
  100. package/src/providers/forge-design/provider.tsx +35 -16
  101. package/src/providers/toast/insets-container.tsx +3 -2
  102. package/src/providers/toast/lifecycle.context.tsx +2 -2
  103. package/src/providers/toast/provider.tsx +150 -51
  104. package/src/providers/toast/toast-item-renderer.tsx +5 -1
  105. package/src/providers/toast/types.ts +22 -8
@@ -1,7 +1,13 @@
1
1
  import React from 'react';
2
2
  import { SafeAreaListener } from 'react-native-safe-area-context';
3
3
  import { Uniwind } from 'uniwind';
4
- import { PortalHost } from '@cdx-ui/primitives';
4
+ import {
5
+ OverlayInsetsProvider,
6
+ OverlayStackProvider,
7
+ PortalHost,
8
+ PortalProvider,
9
+ type EdgeInsets,
10
+ } from '@cdx-ui/primitives';
5
11
  import { GlobalAnimationSettingsProvider } from '../animation-settings';
6
12
  import { TextComponentProvider } from '../text-component/provider';
7
13
  import { ToastProvider } from '../toast/provider';
@@ -28,6 +34,12 @@ import type { ForgeDesignProviderProps } from './types';
28
34
  */
29
35
  const ForgeDesignProvider: React.FC<ForgeDesignProviderProps> = ({ children, config = {} }) => {
30
36
  const { textProps, toast, animation } = config;
37
+ const [overlayInsets, setOverlayInsets] = React.useState<EdgeInsets>({
38
+ top: 0,
39
+ right: 0,
40
+ bottom: 0,
41
+ left: 0,
42
+ });
31
43
 
32
44
  // Determine if toast should be enabled and get props
33
45
  const isToastEnabled = toast !== false && toast !== 'disabled';
@@ -37,23 +49,30 @@ const ForgeDesignProvider: React.FC<ForgeDesignProviderProps> = ({ children, con
37
49
  <SafeAreaListener
38
50
  onChange={({ insets }) => {
39
51
  Uniwind.updateInsets(insets);
52
+ setOverlayInsets(insets);
40
53
  }}
41
54
  >
42
- <GlobalAnimationSettingsProvider animation={animation}>
43
- <TextComponentProvider value={{ textProps }}>
44
- {isToastEnabled ? (
45
- <ToastProvider {...toastProps}>
46
- {children}
47
- <PortalHost />
48
- </ToastProvider>
49
- ) : (
50
- <>
51
- {children}
52
- <PortalHost />
53
- </>
54
- )}
55
- </TextComponentProvider>
56
- </GlobalAnimationSettingsProvider>
55
+ <PortalProvider>
56
+ <OverlayStackProvider>
57
+ <OverlayInsetsProvider value={overlayInsets}>
58
+ <GlobalAnimationSettingsProvider animation={animation}>
59
+ <TextComponentProvider value={{ textProps }}>
60
+ {isToastEnabled ? (
61
+ <ToastProvider {...toastProps}>
62
+ {children}
63
+ <PortalHost />
64
+ </ToastProvider>
65
+ ) : (
66
+ <>
67
+ {children}
68
+ <PortalHost />
69
+ </>
70
+ )}
71
+ </TextComponentProvider>
72
+ </GlobalAnimationSettingsProvider>
73
+ </OverlayInsetsProvider>
74
+ </OverlayStackProvider>
75
+ </PortalProvider>
57
76
  </SafeAreaListener>
58
77
  );
59
78
  };
@@ -2,7 +2,7 @@ import type { ReactNode } from 'react';
2
2
  import { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
3
3
  import { Platform, View } from 'react-native';
4
4
  import { SafeAreaInsetsContext, type EdgeInsets } from 'react-native-safe-area-context';
5
- import { FullWindowOverlay } from '@cdx-ui/primitives';
5
+ import { FullWindowOverlay, NOTIFICATION_OVERLAY_Z_INDEX } from '@cdx-ui/primitives';
6
6
  import type { ToastInsets } from './types';
7
7
 
8
8
  const ZERO_INSETS: EdgeInsets = { top: 0, bottom: 0, left: 0, right: 0 };
@@ -199,7 +199,7 @@ export function InsetsContainer({
199
199
  const content = (
200
200
  <View
201
201
  ref={containerRef}
202
- className="absolute inset-0 pointer-events-box-none"
202
+ className="absolute web:fixed inset-0 pointer-events-box-none"
203
203
  role="region"
204
204
  aria-label={announceLabel}
205
205
  {...(Platform.OS === 'web' && {
@@ -207,6 +207,7 @@ export function InsetsContainer({
207
207
  'aria-atomic': false,
208
208
  })}
209
209
  style={{
210
+ ...(Platform.OS === 'web' ? { zIndex: NOTIFICATION_OVERLAY_Z_INDEX } : {}),
210
211
  paddingTop: finalInsets.top,
211
212
  paddingBottom: finalInsets.bottom,
212
213
  paddingLeft: finalInsets.left,
@@ -18,8 +18,8 @@ export interface ToastLifecycleContextValue {
18
18
  remove: (id: string) => void;
19
19
  /** IDs of toasts that have been marked closed but are still mounted. */
20
20
  pendingRemovalIds: ReadonlySet<string>;
21
- /** ID of the front-most (last non-pending) toast, or null if none. */
22
- frontmostId: string | null;
21
+ /** IDs of the front-most non-pending toast in each placement stack. */
22
+ frontmostIds: ReadonlySet<string>;
23
23
  }
24
24
 
25
25
  export const ToastLifecycleContext = createContext<ToastLifecycleContextValue | null>(null);
@@ -9,7 +9,9 @@ import {
9
9
  } from 'react';
10
10
  import { Platform, View } from 'react-native';
11
11
  import { useSharedValue } from 'react-native-reanimated';
12
+ import { createPortalFn, useOverlayStackRegistration } from '@cdx-ui/primitives';
12
13
  import { DefaultToast } from '../../components/Toast';
14
+ import type { ToastPlacement } from '../../components/Toast/types';
13
15
  import { InsetsContainer } from './insets-container';
14
16
  import { ToastLifecycleContext, type ToastLifecycleContextValue } from './lifecycle.context';
15
17
  import { toastReducer } from './reducer';
@@ -26,6 +28,54 @@ import type {
26
28
  } from './types';
27
29
 
28
30
  const DEFAULT_DURATION = 4000;
31
+ const TOAST_PLACEMENTS: readonly ToastPlacement[] = [
32
+ 'top-start',
33
+ 'top',
34
+ 'top-end',
35
+ 'bottom-start',
36
+ 'bottom',
37
+ 'bottom-end',
38
+ ];
39
+
40
+ function useToastPlacementStacks() {
41
+ const topStartTotal = useSharedValue(0);
42
+ const topStartHeights = useSharedValue<Record<string, number>>({});
43
+ const topTotal = useSharedValue(0);
44
+ const topHeights = useSharedValue<Record<string, number>>({});
45
+ const topEndTotal = useSharedValue(0);
46
+ const topEndHeights = useSharedValue<Record<string, number>>({});
47
+ const bottomStartTotal = useSharedValue(0);
48
+ const bottomStartHeights = useSharedValue<Record<string, number>>({});
49
+ const bottomTotal = useSharedValue(0);
50
+ const bottomHeights = useSharedValue<Record<string, number>>({});
51
+ const bottomEndTotal = useSharedValue(0);
52
+ const bottomEndHeights = useSharedValue<Record<string, number>>({});
53
+
54
+ return useMemo(
55
+ () => ({
56
+ 'top-start': { total: topStartTotal, heights: topStartHeights },
57
+ top: { total: topTotal, heights: topHeights },
58
+ 'top-end': { total: topEndTotal, heights: topEndHeights },
59
+ 'bottom-start': { total: bottomStartTotal, heights: bottomStartHeights },
60
+ bottom: { total: bottomTotal, heights: bottomHeights },
61
+ 'bottom-end': { total: bottomEndTotal, heights: bottomEndHeights },
62
+ }),
63
+ [
64
+ bottomEndHeights,
65
+ bottomEndTotal,
66
+ bottomHeights,
67
+ bottomStartHeights,
68
+ bottomStartTotal,
69
+ bottomTotal,
70
+ topEndHeights,
71
+ topEndTotal,
72
+ topHeights,
73
+ topStartHeights,
74
+ topStartTotal,
75
+ topTotal,
76
+ ],
77
+ );
78
+ }
29
79
 
30
80
  /**
31
81
  * Context for toast manager
@@ -137,19 +187,30 @@ export function ToastProvider({
137
187
 
138
188
  const isToastVisible = toasts.length > 0;
139
189
 
140
- const heights = useSharedValue<Record<string, number>>({});
141
-
142
- const total = useSharedValue<number>(0);
190
+ const placementStacks = useToastPlacementStacks();
143
191
 
144
192
  /**
145
- * Derive total from toasts.length so the animated opacity/scale/translateY
146
- * interpolations always use the real count. Manual increment/decrement
147
- * was prone to drift when hide + show ran in the same tick or when
148
- * auto-dismiss raced with a manual hide (stale-closure mismatch).
193
+ * Each placement owns independent animation metrics. A toast at bottom-end
194
+ * must not compress or fade because unrelated toasts are open at top-start.
149
195
  */
150
196
  useEffect(() => {
151
- total.set(toasts.length);
152
- }, [toasts.length, total]);
197
+ const totals: Record<ToastPlacement, number> = {
198
+ 'top-start': 0,
199
+ top: 0,
200
+ 'top-end': 0,
201
+ 'bottom-start': 0,
202
+ bottom: 0,
203
+ 'bottom-end': 0,
204
+ };
205
+
206
+ for (const toast of toasts) {
207
+ totals[toast.placement] += 1;
208
+ }
209
+
210
+ for (const placement of TOAST_PLACEMENTS) {
211
+ placementStacks[placement].total.set(totals[placement]);
212
+ }
213
+ }, [placementStacks, toasts]);
153
214
 
154
215
  const idCounter = useRef(0);
155
216
 
@@ -199,17 +260,21 @@ export function ToastProvider({
199
260
 
200
261
  dispatch({ type: 'REMOVE', payload: { ids: idsArray } });
201
262
 
202
- heights.modify(<T extends Record<string, number>>(value: T): T => {
203
- 'worklet';
204
- const result = { ...value };
205
- for (const id of idsArray) {
206
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
207
- delete result[id];
208
- }
209
- return result;
210
- });
263
+ for (const placement of TOAST_PLACEMENTS) {
264
+ placementStacks[placement].heights.modify(
265
+ <T extends Record<string, number>>(value: T): T => {
266
+ 'worklet';
267
+ const result = { ...value };
268
+ for (const id of idsArray) {
269
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
270
+ delete result[id];
271
+ }
272
+ return result;
273
+ },
274
+ );
275
+ }
211
276
  },
212
- [heights],
277
+ [placementStacks],
213
278
  );
214
279
 
215
280
  /**
@@ -358,18 +423,18 @@ export function ToastProvider({
358
423
 
359
424
  const lifecycleContextValue = useMemo<ToastLifecycleContextValue>(() => {
360
425
  const pending = new Set<string>();
361
- let frontmostId: string | null = null;
426
+ const frontmostByPlacement = new Map<ToastPlacement, string>();
362
427
  for (const toast of toasts) {
363
428
  if (toast.pendingRemoval) {
364
429
  pending.add(toast.id);
365
430
  } else {
366
- frontmostId = toast.id;
431
+ frontmostByPlacement.set(toast.placement, toast.id);
367
432
  }
368
433
  }
369
434
  return {
370
435
  remove: (id: string) => remove([id]),
371
436
  pendingRemovalIds: pending,
372
- frontmostId,
437
+ frontmostIds: new Set(frontmostByPlacement.values()),
373
438
  };
374
439
  }, [remove, toasts]);
375
440
 
@@ -391,6 +456,7 @@ export function ToastProvider({
391
456
  normalizedOptions = {
392
457
  id: undefined,
393
458
  component: createStringToastComponent(options, globalConfig),
459
+ placement: globalConfig?.placement,
394
460
  duration: DEFAULT_DURATION,
395
461
  };
396
462
  duration = DEFAULT_DURATION;
@@ -404,6 +470,7 @@ export function ToastProvider({
404
470
  normalizedOptions = {
405
471
  id: config.id,
406
472
  component: createConfigToastComponent(config, globalConfig),
473
+ placement: config.placement,
407
474
  duration,
408
475
  onShow: config.onShow,
409
476
  onHide: config.onHide,
@@ -418,6 +485,7 @@ export function ToastProvider({
418
485
 
419
486
  const id =
420
487
  normalizedOptions.id ?? `toast-${String(Date.now())}-${String(idCounter.current++)}`;
488
+ const placement = normalizedOptions.placement ?? globalConfig?.placement ?? 'bottom';
421
489
 
422
490
  // If an explicit ID was provided, check if a toast with that ID already exists
423
491
  // If it exists, skip adding a new toast and return the existing ID
@@ -432,6 +500,7 @@ export function ToastProvider({
432
500
  type: 'SHOW',
433
501
  payload: {
434
502
  id,
503
+ placement,
435
504
  component: normalizedOptions.component,
436
505
  duration,
437
506
  onShow: normalizedOptions.onShow,
@@ -497,40 +566,70 @@ export function ToastProvider({
497
566
  [show, hide, pauseAll, resumeAll, isToastVisible],
498
567
  );
499
568
 
569
+ useOverlayStackRegistration({
570
+ active: toasts.length > 0,
571
+ capabilities: [],
572
+ id: 'forge-ui-toast-viewport',
573
+ groupId: 'forge-ui-notifications',
574
+ layer: 'notification',
575
+ });
576
+
577
+ const placementIndexes: Record<ToastPlacement, number> = {
578
+ 'top-start': 0,
579
+ top: 0,
580
+ 'top-end': 0,
581
+ 'bottom-start': 0,
582
+ bottom: 0,
583
+ 'bottom-end': 0,
584
+ };
585
+
586
+ const toastViewport =
587
+ toasts.length > 0 ? (
588
+ <InsetsContainer
589
+ insets={insets}
590
+ announceLabel={announceLabel}
591
+ contentWrapper={contentWrapper}
592
+ disableFullWindowOverlay={disableFullWindowOverlay}
593
+ unstable_accessibilityContainerViewIsModal={unstable_accessibilityContainerViewIsModal}
594
+ pauseAll={pauseAll}
595
+ resumeAll={resumeAll}
596
+ hide={hide}
597
+ >
598
+ <View className="flex-1">
599
+ {toasts.map((toastItem) => {
600
+ const placement = toastItem.placement;
601
+ const index = placementIndexes[placement];
602
+ const stack = placementStacks[placement];
603
+ placementIndexes[placement] += 1;
604
+
605
+ return (
606
+ <ToastItemRenderer
607
+ key={toastItem.id}
608
+ toastItem={toastItem}
609
+ show={show}
610
+ hide={hide}
611
+ index={index}
612
+ total={stack.total}
613
+ heights={stack.heights}
614
+ maxVisibleToasts={maxVisibleToasts}
615
+ />
616
+ );
617
+ })}
618
+ </View>
619
+ </InsetsContainer>
620
+ ) : null;
621
+
622
+ const renderedToastViewport =
623
+ Platform.OS === 'web' && toastViewport && createPortalFn && typeof document !== 'undefined'
624
+ ? createPortalFn(toastViewport, document.body)
625
+ : toastViewport;
626
+
500
627
  return (
501
628
  <ToastConfigContext.Provider value={globalConfig}>
502
629
  <ToasterContext.Provider value={contextValue}>
503
630
  <ToastLifecycleContext.Provider value={lifecycleContextValue}>
504
631
  {children}
505
- {toasts.length > 0 && (
506
- <InsetsContainer
507
- insets={insets}
508
- announceLabel={announceLabel}
509
- contentWrapper={contentWrapper}
510
- disableFullWindowOverlay={disableFullWindowOverlay}
511
- unstable_accessibilityContainerViewIsModal={
512
- unstable_accessibilityContainerViewIsModal
513
- }
514
- pauseAll={pauseAll}
515
- resumeAll={resumeAll}
516
- hide={hide}
517
- >
518
- <View className="flex-1">
519
- {toasts.map((toastItem, index) => (
520
- <ToastItemRenderer
521
- key={toastItem.id}
522
- toastItem={toastItem}
523
- show={show}
524
- hide={hide}
525
- index={index}
526
- total={total}
527
- heights={heights}
528
- maxVisibleToasts={maxVisibleToasts}
529
- />
530
- ))}
531
- </View>
532
- </InsetsContainer>
533
- )}
632
+ {renderedToastViewport}
534
633
  </ToastLifecycleContext.Provider>
535
634
  </ToasterContext.Provider>
536
635
  </ToastConfigContext.Provider>
@@ -13,6 +13,7 @@ export const ToastItemRenderer = memo(
13
13
 
14
14
  const content = toastItem.component({
15
15
  id: toastItem.id,
16
+ placement: toastItem.placement,
16
17
  index,
17
18
  total,
18
19
  heights,
@@ -33,8 +34,11 @@ export const ToastItemRenderer = memo(
33
34
  return (
34
35
  prevProps.toastItem.id === nextProps.toastItem.id &&
35
36
  prevProps.toastItem.component === nextProps.toastItem.component &&
37
+ prevProps.toastItem.placement === nextProps.toastItem.placement &&
36
38
  prevProps.toastItem.pendingRemoval === nextProps.toastItem.pendingRemoval &&
37
- prevProps.index === nextProps.index
39
+ prevProps.index === nextProps.index &&
40
+ prevProps.total === nextProps.total &&
41
+ prevProps.heights === nextProps.heights
38
42
  );
39
43
  },
40
44
  );
@@ -1,5 +1,5 @@
1
1
  import type { SharedValue } from 'react-native-reanimated';
2
- import type { ToastProps } from '../../components/Toast/types';
2
+ import type { ToastPlacement, ToastProps } from '../../components/Toast/types';
3
3
 
4
4
  /**
5
5
  * Global toast configuration
@@ -120,15 +120,19 @@ export interface ToastComponentProps {
120
120
  */
121
121
  id: string;
122
122
  /**
123
- * The index of the toast in the array (0-based)
123
+ * Placement used to isolate this toast's stack metrics.
124
+ */
125
+ placement: ToastPlacement;
126
+ /**
127
+ * The index of the toast within its placement stack (0-based).
124
128
  */
125
129
  index: number;
126
130
  /**
127
- * The total number of toasts currently displayed
131
+ * The total number of toasts currently displayed at this placement.
128
132
  */
129
133
  total: SharedValue<number>;
130
134
  /**
131
- * Heights of all toast items, keyed by toast ID
135
+ * Heights of toast items at this placement, keyed by toast ID.
132
136
  */
133
137
  heights: SharedValue<Record<string, number>>;
134
138
  /**
@@ -225,6 +229,11 @@ export interface ToastShowOptionsWithComponent {
225
229
  * A function that receives toast props and returns a React element
226
230
  */
227
231
  component: (props: ToastComponentProps) => React.ReactElement;
232
+ /**
233
+ * Placement used for stack isolation and passed to the custom component.
234
+ * @default ToastProvider default placement, then 'bottom'
235
+ */
236
+ placement?: ToastPlacement;
228
237
  /**
229
238
  * Duration in milliseconds before the toast automatically disappears
230
239
  * Set to `'persistent'` to prevent auto-hide (toast will remain until manually dismissed)
@@ -243,7 +252,7 @@ export interface ToastShowOptionsWithComponent {
243
252
 
244
253
  /**
245
254
  * Conditional type for toast show options
246
- * - If component is provided: only id, component, onShow, onHide are allowed
255
+ * - If component is provided: lifecycle options plus placement are allowed
247
256
  * - If component is NOT provided: all config props are available
248
257
  */
249
258
  export type ToastShowOptions =
@@ -258,6 +267,11 @@ export interface ToastItem {
258
267
  * Unique identifier for the toast
259
268
  */
260
269
  id: string;
270
+ /**
271
+ * Resolved placement used to isolate stack animation metrics.
272
+ * @internal
273
+ */
274
+ placement: ToastPlacement;
261
275
  /**
262
276
  * A function that receives toast props and returns a React element
263
277
  */
@@ -388,15 +402,15 @@ export interface ToastItemRendererProps {
388
402
  */
389
403
  toastItem: ToastItem;
390
404
  /**
391
- * The index of the toast in the array (0-based)
405
+ * The index of the toast within its placement stack (0-based).
392
406
  */
393
407
  index: number;
394
408
  /**
395
- * The total number of toasts currently displayed
409
+ * The total number of toasts currently displayed at this placement.
396
410
  */
397
411
  total: SharedValue<number>;
398
412
  /**
399
- * Heights of all toast items, keyed by toast ID
413
+ * Heights of toast items at this placement, keyed by toast ID.
400
414
  */
401
415
  heights: SharedValue<Record<string, number>>;
402
416
  /**