@limonify/zest-ui 0.1.2 → 0.1.4

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 (51) hide show
  1. package/lib/accordion/index.d.ts +1 -0
  2. package/lib/accordion/index.d.ts.map +1 -1
  3. package/lib/accordion/index.js +3 -1
  4. package/lib/accordion/index.js.map +1 -1
  5. package/lib/accordion/panel/AccordionPanel.d.ts +19 -1
  6. package/lib/accordion/panel/AccordionPanel.d.ts.map +1 -1
  7. package/lib/accordion/panel/AccordionPanel.js +18 -2
  8. package/lib/accordion/panel/AccordionPanel.js.map +1 -1
  9. package/lib/collapsible/index.d.ts +1 -0
  10. package/lib/collapsible/index.d.ts.map +1 -1
  11. package/lib/collapsible/index.js +3 -1
  12. package/lib/collapsible/index.js.map +1 -1
  13. package/lib/collapsible/panel/CollapsiblePanel.d.ts +19 -1
  14. package/lib/collapsible/panel/CollapsiblePanel.d.ts.map +1 -1
  15. package/lib/collapsible/panel/CollapsiblePanel.js +18 -2
  16. package/lib/collapsible/panel/CollapsiblePanel.js.map +1 -1
  17. package/lib/collapsible/panel/CollapsiblePanelContext.d.ts +21 -0
  18. package/lib/collapsible/panel/CollapsiblePanelContext.d.ts.map +1 -0
  19. package/lib/collapsible/panel/CollapsiblePanelContext.js +48 -0
  20. package/lib/collapsible/panel/CollapsiblePanelContext.js.map +1 -0
  21. package/lib/collapsible/panel/useCollapsiblePanel.d.ts +17 -0
  22. package/lib/collapsible/panel/useCollapsiblePanel.d.ts.map +1 -1
  23. package/lib/collapsible/panel/useCollapsiblePanel.js +4 -2
  24. package/lib/collapsible/panel/useCollapsiblePanel.js.map +1 -1
  25. package/lib/internals/useTransitionStatus.d.ts.map +1 -1
  26. package/lib/internals/useTransitionStatus.js +12 -10
  27. package/lib/internals/useTransitionStatus.js.map +1 -1
  28. package/lib/switch/thumb/SwitchThumb.d.ts +19 -2
  29. package/lib/switch/thumb/SwitchThumb.d.ts.map +1 -1
  30. package/lib/switch/thumb/SwitchThumb.js +21 -3
  31. package/lib/switch/thumb/SwitchThumb.js.map +1 -1
  32. package/lib/toast/root/ToastRoot.d.ts +20 -8
  33. package/lib/toast/root/ToastRoot.d.ts.map +1 -1
  34. package/lib/toast/root/ToastRoot.js +28 -8
  35. package/lib/toast/root/ToastRoot.js.map +1 -1
  36. package/lib/toast/store.js +1 -1
  37. package/lib/toast/store.js.map +1 -1
  38. package/lib/toast/useToastManager.d.ts +7 -0
  39. package/lib/toast/useToastManager.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/accordion/index.ts +1 -0
  42. package/src/accordion/panel/AccordionPanel.tsx +40 -2
  43. package/src/collapsible/index.ts +1 -0
  44. package/src/collapsible/panel/CollapsiblePanel.tsx +40 -2
  45. package/src/collapsible/panel/CollapsiblePanelContext.ts +34 -0
  46. package/src/collapsible/panel/useCollapsiblePanel.tsx +27 -3
  47. package/src/internals/useTransitionStatus.ts +10 -12
  48. package/src/switch/thumb/SwitchThumb.tsx +40 -5
  49. package/src/toast/root/ToastRoot.tsx +37 -9
  50. package/src/toast/store.ts +1 -1
  51. package/src/toast/useToastManager.ts +7 -0
@@ -6,6 +6,7 @@ import { useToastProviderContext } from '../provider/ToastProviderContext';
6
6
  import { useRenderElement } from '../../use-render/useRenderElement';
7
7
  import { useIsoLayoutEffect } from '../../hooks/useIsoLayoutEffect';
8
8
  import { useStableCallback } from '../../hooks/useStableCallback';
9
+ import { AnimationFrame } from '../../hooks/useAnimationFrame';
9
10
  import type { ZestUIComponentProps } from '../../types';
10
11
  import type { ToastObject } from '../useToastManager';
11
12
  import { ToastRootContext } from './ToastRootContext';
@@ -23,12 +24,17 @@ export type ToastSwipeDirection = 'up' | 'down' | 'left' | 'right';
23
24
  * `transitionStatus`, its stacking `offsetY`/`index`, and how far a swipe has
24
25
  * travelled, and the consumer turns those into a transform.
25
26
  *
26
- * Nothing in React Native reports that a closing animation finished, so a closed
27
- * toast would otherwise sit in the list forever. `removeOnClose` (on by default)
28
- * drops it the moment it closes, which is what a consumer who does not animate
29
- * wants. To animate the exit, set `removeOnClose={false}`, drive the exit from
30
- * `state.transitionStatus === 'ending'`, and call `useToastManager().remove(id)`
31
- * when it finishes. This is the same lever as `Collapsible.Panel`'s `keepMounted`.
27
+ * **Enter:** `transitionStatus` starts as `'starting'` (auto-clears to
28
+ * `undefined` after one frame). Key off `state.transitionStatus === 'starting'`
29
+ * to trigger a slide-in or fade-in animation.
30
+ *
31
+ * **Exit:** `removeOnClose` (on by default) drops the toast the moment it
32
+ * closes the right default for a consumer who does not animate. To animate
33
+ * the exit, set `removeOnClose={false}`, drive the exit from
34
+ * `state.transitionStatus === 'ending'` using `state.measuredHeight` as the
35
+ * starting value (the animation target is `0`), and call
36
+ * `useToastManager().remove(id)` when the animation finishes.
37
+ * This is the same lever as `Collapsible.Panel`'s `keepMounted`.
32
38
  */
33
39
  export function ToastRoot(componentProps: ToastRoot.Props) {
34
40
  const {
@@ -64,13 +70,26 @@ export function ToastRoot(componentProps: ToastRoot.Props) {
64
70
  }
65
71
  }, [removeOnClose, toast.transitionStatus, toast.id, store]);
66
72
 
73
+ // `transitionStatus` starts as `'starting'` and stays that way forever.
74
+ // Auto-clear it after one frame so the consumer can react to `'starting'`
75
+ // for an enter animation without repeatedly re-triggering.
76
+ useIsoLayoutEffect(() => {
77
+ if (toast.transitionStatus === 'starting') {
78
+ const frame = AnimationFrame.request(() => {
79
+ store.updateToastInternal(toast.id, { transitionStatus: undefined });
80
+ });
81
+ return () => AnimationFrame.cancel(frame);
82
+ }
83
+ return undefined;
84
+ }, [toast.transitionStatus, toast.id, store]);
85
+
67
86
  const handleLayout = useStableCallback((event: LayoutChangeEvent) => {
68
87
  const { height } = event.nativeEvent.layout;
69
88
 
70
89
  // A closing toast reports a height of 0 through the store; measuring it again
71
90
  // here would undo that and leave a gap in the stack.
72
91
  if (toast.transitionStatus !== 'ending' && height !== toast.height) {
73
- store.updateToastInternal(toast.id, { height });
92
+ store.updateToastInternal(toast.id, { height, measuredHeight: height });
74
93
  }
75
94
  });
76
95
 
@@ -124,6 +143,7 @@ export function ToastRoot(componentProps: ToastRoot.Props) {
124
143
  visibleIndex,
125
144
  offsetY,
126
145
  height: toast.height ?? 0,
146
+ measuredHeight: toast.measuredHeight ?? toast.height ?? 0,
127
147
  swiping,
128
148
  swipeMovement,
129
149
  swipeDirection,
@@ -137,6 +157,7 @@ export function ToastRoot(componentProps: ToastRoot.Props) {
137
157
  visibleIndex,
138
158
  offsetY,
139
159
  toast.height,
160
+ toast.measuredHeight,
140
161
  swiping,
141
162
  swipeMovement,
142
163
  swipeDirection,
@@ -192,8 +213,9 @@ function getDisplacement(
192
213
 
193
214
  export interface ToastRootState {
194
215
  /**
195
- * The toast's transition status: `'starting'` as it arrives, `'ending'` once it
196
- * is closing.
216
+ * The toast's transition status: `'starting'` as it arrives (auto-clears to
217
+ * `undefined` after one frame for enter animation), `'ending'` once it is
218
+ * closing.
197
219
  */
198
220
  transitionStatus: 'starting' | 'ending' | undefined;
199
221
  /**
@@ -227,6 +249,12 @@ export interface ToastRootState {
227
249
  * The toast's measured height.
228
250
  */
229
251
  height: number;
252
+ /**
253
+ * The last measured height before the toast closed, preserved for the
254
+ * consumer to use as the starting value for an exit animation.
255
+ * Falls back to `height` when no close has occurred yet.
256
+ */
257
+ measuredHeight: number;
230
258
  /**
231
259
  * Whether a swipe is currently in progress.
232
260
  */
@@ -301,7 +301,7 @@ export class ToastStore extends ReactStore<State, {}, typeof selectors> {
301
301
 
302
302
  const endingToasts = toasts.map((item) =>
303
303
  closeAll || item.id === toastId
304
- ? { ...item, transitionStatus: 'ending' as const, height: 0 }
304
+ ? { ...item, transitionStatus: 'ending' as const, height: 0, measuredHeight: item.height }
305
305
  : item,
306
306
  );
307
307
  const newToasts = applyLimited(endingToasts, limit);
@@ -70,6 +70,13 @@ export interface ToastObject<Data extends object> {
70
70
  * The height of the toast.
71
71
  */
72
72
  height?: number | undefined;
73
+ /**
74
+ * The last measured height of the toast, captured at the moment it closes.
75
+ * Unlike `height` (which is set to `0` on close for stack collapse), this
76
+ * preserves the value so the consumer can use it as the starting point for
77
+ * an exit animation.
78
+ */
79
+ measuredHeight?: number | undefined;
73
80
  /**
74
81
  * Callback function to be called when the toast is closed.
75
82
  */