@dloizides/ui-motion 1.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## 1.0.1
4
+
5
+ Initial release — the shared motion layer of the dloizides.com RN-web UI kit.
6
+
7
+ The kit was almost entirely static: only `ui-feedback` (ToastHost fade/slide, PageSkeleton
8
+ opacity pulse) and `ui-layout` (Accordion chevron) animated, each re-declaring durations and
9
+ curves inline. This package centralises reusable motion primitives, all built on RN `Animated`
10
+ (no reanimated/moti/framer), all driven by `@dloizides/design-tokens` motion tokens, and all
11
+ gated on `@dloizides/rn-web-hooks` `useReducedMotion` so every animation collapses to instant
12
+ under the user's reduced-motion preference.
13
+
14
+ ### New
15
+
16
+ - **Tokens** — `motionDurations`, `motionEasings` (RN `EasingFunction`s built from the
17
+ design-token cubic-bezier tuples), `motionEasingTokens`, and a re-export of `DEFAULT_MOTION`.
18
+ Web and native animate on the same curves.
19
+ - **`useReducedMotion` / `prefersReducedMotion`** — re-exported from `@dloizides/rn-web-hooks`
20
+ (canonical gate, not forked) so motion consumers get the gate and the primitives from one place.
21
+ - **`useEnterExit({ visible, duration?, translateY?, fromScale? })`** — the reusable enter/exit
22
+ pattern promoted from ToastHost. Returns an animated `style` + a `mounted` flag so callers keep
23
+ the node in the tree through its exit animation.
24
+ - **`<FadeIn>`** — fade (+ small translateY) on mount.
25
+ - **`<Collapse open>`** — animated height expand/collapse that WORKS ON WEB (measures content
26
+ height, animates a real `Animated.Value` height; not `LayoutAnimation`, a native-only no-op on
27
+ RN-web). Children stay mounted, overflow clipped.
28
+ - **`<PressableScale>`** — a `Pressable` that scales down on press-in and back on press-out;
29
+ forwards all Pressable props + accessibility. Replaces bare `activeOpacity` in ui-buttons.
30
+ - **`<Skeleton>`** — a moving-shimmer placeholder block (upgrades the opacity-pulse approach);
31
+ `width` / `height` / `borderRadius` props.
32
+ - Pure helpers `resolveDuration`, `shouldUseNativeDriver`, `collapseTargetHeight`, `resolveScale`,
33
+ `shimmerRange`, and `MOTION_TEST_IDS`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 dloizides
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @dloizides/ui-motion
2
+
3
+ Themable, brand-agnostic **React Native (RN-web)** motion primitives — the shared
4
+ animation layer of the dloizides.com UI kit.
5
+
6
+ - Built on RN `Animated` only. **No** reanimated / moti / framer.
7
+ - Native driver on native, JS driver on web (`useNativeDriver: Platform.OS !== 'web'`).
8
+ - **Every animation collapses to instant when the user prefers reduced motion**
9
+ (`useReducedMotion()`, re-exported from `@dloizides/rn-web-hooks`).
10
+ - Motion values (durations + easings) come from `@dloizides/design-tokens`, so web
11
+ (CSS `cubic-bezier`) and native (`Easing.bezier`) animate on the **same curves**.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @dloizides/ui-motion
17
+ ```
18
+
19
+ Peer deps: `react >=18`, `react-native >=0.74`. Runtime deps `@dloizides/design-tokens`
20
+ and `@dloizides/rn-web-hooks` are installed automatically.
21
+
22
+ ## Tokens
23
+
24
+ ```ts
25
+ import { motionDurations, motionEasings, DEFAULT_MOTION } from '@dloizides/ui-motion';
26
+
27
+ motionDurations; // { instant: 0, fast: 140, base: 200, slow: 300 } (ms)
28
+ motionEasings.standard; // RN EasingFunction built from cubic-bezier(0.2,0,0,1)
29
+ motionEasings.decelerate; // entering elements
30
+ motionEasings.accelerate; // leaving elements
31
+ DEFAULT_MOTION.easings.standard.css; // 'cubic-bezier(0.2,0,0,1)' for web/CSS consumers
32
+ ```
33
+
34
+ ## Hooks
35
+
36
+ ### `useReducedMotion()` / `prefersReducedMotion()`
37
+
38
+ Re-exported from `@dloizides/rn-web-hooks` (the canonical gate — not forked). Web reads
39
+ `matchMedia('(prefers-reduced-motion: reduce)')`; native returns `false`.
40
+
41
+ ### `useEnterExit({ visible, duration?, translateY?, fromScale? })`
42
+
43
+ The reusable enter/exit pattern (fade + optional translate/scale) promoted from
44
+ `ui-feedback`'s ToastHost. Returns an animated `style` **and** a `mounted` flag so the
45
+ node stays in the tree until its exit animation finishes.
46
+
47
+ ```tsx
48
+ const { style, mounted } = useEnterExit({ visible: open, translateY: -6 });
49
+ if (!mounted) return null;
50
+ return <Animated.View style={style}>{children}</Animated.View>;
51
+ ```
52
+
53
+ ## Components
54
+
55
+ ### `<FadeIn duration? delay? translateY? style? testID>`
56
+
57
+ Fades (and slightly lifts) its children in once, on mount.
58
+
59
+ ```tsx
60
+ <FadeIn><Card /></FadeIn>
61
+ ```
62
+
63
+ ### `<Collapse open duration? style? testID>`
64
+
65
+ Animated height expand/collapse that **works on web** — it measures the content's
66
+ natural height and animates a real `Animated.Value` height (unlike `LayoutAnimation`,
67
+ a native-only no-op on RN-web). Children stay mounted; overflow is clipped.
68
+
69
+ ```tsx
70
+ <Collapse open={expanded}><Details /></Collapse>
71
+ ```
72
+
73
+ ### `<PressableScale pressedScale? innerStyle? ...PressableProps>`
74
+
75
+ A `Pressable` that scales down (default `0.96`) on press-in and springs back on
76
+ press-out. Forwards **all** Pressable props and accessibility. Designed to replace bare
77
+ `activeOpacity` in `@dloizides/ui-buttons`.
78
+
79
+ ```tsx
80
+ <PressableScale accessibilityLabel="Save" accessibilityHint="Saves the form" onPress={save}>
81
+ <Text>Save</Text>
82
+ </PressableScale>
83
+ ```
84
+
85
+ ### `<Skeleton width? height? borderRadius? duration? backgroundColor? highlightColor? style? testID>`
86
+
87
+ A rounded placeholder block with a highlight band that sweeps across it — a stronger
88
+ loading signal than the opacity pulse in `ui-feedback`'s PageSkeleton.
89
+
90
+ ```tsx
91
+ <Skeleton width="60%" height={20} borderRadius={6} />
92
+ ```
93
+
94
+ ## Reduced motion
95
+
96
+ Every primitive honours the OS "reduce motion" setting: `FadeIn` shows content at rest,
97
+ `Collapse` snaps to the target height, `PressableScale` keeps scale `1`, `Skeleton`
98
+ shows a static block, and `useEnterExit` collapses its duration to `0`.
99
+
100
+ ## Testing philosophy
101
+
102
+ Unit tests cover **logic** (reduced-motion collapse, mount/exit lifecycle, token
103
+ derivation, prop forwarding), not animation frames — the frame-driven paths are
104
+ verified in the app E2E suites.
105
+
106
+ ## License
107
+
108
+ MIT © dloizides
@@ -0,0 +1,206 @@
1
+ export { prefersReducedMotion, useReducedMotion } from '@dloizides/rn-web-hooks';
2
+ import { EasingFunction, Animated, StyleProp, ViewStyle, PressableProps } from 'react-native';
3
+ import { MotionEasings, MotionDurations } from '@dloizides/design-tokens';
4
+ export { DEFAULT_MOTION, Motion, MotionDurations, MotionEasings } from '@dloizides/design-tokens';
5
+ import React from 'react';
6
+
7
+ /**
8
+ * motionTokens — the RN-ready motion scale, DERIVED from `@dloizides/design-tokens`.
9
+ *
10
+ * design-tokens ships framework-agnostic VALUES (durations in ms + easings as a
11
+ * `css` string plus `[x1,y1,x2,y2]` bezier control points). This module turns the
12
+ * bezier tuples into RN `Easing.bezier(...)` functions ONCE, so every primitive in
13
+ * this package animates on the same curves the web/CSS side uses — one source, two
14
+ * runtimes. Nothing here is app-specific; motion is uniform across portals.
15
+ */
16
+
17
+ /** Durations in ms (numbers). `instant` (0) is the reduced-motion collapse target. */
18
+ declare const motionDurations: MotionDurations;
19
+ /** The easing token group in its raw (`css` + `bezier`) form. */
20
+ declare const motionEasingTokens: MotionEasings;
21
+ /** The named RN easing curves, one per {@link MotionEasings} key. */
22
+ type MotionEasingName = keyof MotionEasings;
23
+ /**
24
+ * RN `EasingFunction`s built from the design-token bezier tuples. Built once at
25
+ * module load; the same three functions are reused by every primitive.
26
+ */
27
+ declare const motionEasings: Record<MotionEasingName, EasingFunction>;
28
+
29
+ /**
30
+ * The animation duration a primitive should actually use. Collapses to
31
+ * `instant` (0) under reduced-motion — the single source of the "every animation
32
+ * collapses to instant" rule the whole package promises. `0` means "jump to the
33
+ * end state on the next frame", i.e. no visible motion.
34
+ */
35
+ declare function resolveDuration(duration: number, reduced: boolean): number;
36
+ /**
37
+ * Whether RN `Animated` should use the native driver. Layout props (height) can
38
+ * NEVER be native-driven, so callers animating height must pass `false` regardless
39
+ * — this helper is for opacity/transform animations, which are native-drivable on
40
+ * native and must fall back to the JS driver on web.
41
+ */
42
+ declare function shouldUseNativeDriver(): boolean;
43
+ /**
44
+ * The target height for a {@link Collapse}: the measured content height when open,
45
+ * `0` when closed. `undefined` while the content has not been measured yet — the
46
+ * caller leaves height `auto` for that first layout pass so the content can report
47
+ * its natural size.
48
+ */
49
+ declare function collapseTargetHeight(open: boolean, measured: number | null): number | undefined;
50
+ /**
51
+ * The scale a {@link PressableScale} should animate to. `1` (no scale) whenever
52
+ * reduced-motion is on OR the control is not pressed; the pressed scale otherwise.
53
+ */
54
+ declare function resolveScale(pressed: boolean, reduced: boolean, pressedScale: number): number;
55
+ /**
56
+ * The `[from, to]` translateX range for a {@link Skeleton} shimmer sweep across a
57
+ * block of the given pixel width — the highlight travels from just off the left
58
+ * edge to just off the right edge.
59
+ */
60
+ declare function shimmerRange(width: number): readonly [number, number];
61
+
62
+ /**
63
+ * Default testID strings for the motion primitives. Consumers can override each
64
+ * via the component's `testID` prop; these are the fallbacks so Playwright/RTL
65
+ * selectors have a stable default.
66
+ */
67
+ declare const MOTION_TEST_IDS: {
68
+ readonly fadeIn: "fade-in";
69
+ readonly collapse: "collapse";
70
+ readonly pressableScale: "pressable-scale";
71
+ readonly skeleton: "skeleton";
72
+ };
73
+ /** Default press-in scale for {@link PressableScale}. */
74
+ declare const DEFAULT_PRESSED_SCALE = 0.96;
75
+ /** Default enter translate-Y (px) for {@link FadeIn}. */
76
+ declare const DEFAULT_FADE_TRANSLATE_Y = 8;
77
+ /** Default shimmer sweep duration (ms) for {@link Skeleton}. */
78
+ declare const DEFAULT_SKELETON_DURATION_MS = 1200;
79
+
80
+ interface UseEnterExitOptions {
81
+ /** Whether the element should be visible (entered). */
82
+ visible: boolean;
83
+ /** Enter/exit duration in ms. Defaults to the `base` motion duration (200). */
84
+ duration?: number;
85
+ /**
86
+ * Initial translate-Y offset (px) the element enters from / exits to. `0`
87
+ * (default) disables the translate, leaving a pure fade.
88
+ */
89
+ translateY?: number;
90
+ /**
91
+ * Initial scale the element enters from / exits to. `1` (default) disables the
92
+ * scale, leaving a pure fade.
93
+ */
94
+ fromScale?: number;
95
+ }
96
+ interface UseEnterExitResult {
97
+ /** Animated style to spread onto an `Animated.View`. */
98
+ style: {
99
+ opacity: Animated.Value;
100
+ transform: Animated.WithAnimatedArray<{
101
+ translateY: Animated.AnimatedInterpolation<number>;
102
+ } | {
103
+ scale: Animated.AnimatedInterpolation<number>;
104
+ }>;
105
+ };
106
+ /** True while the element must remain in the tree (entered OR exiting). */
107
+ mounted: boolean;
108
+ }
109
+ declare function useEnterExit(options: UseEnterExitOptions): UseEnterExitResult;
110
+
111
+ /**
112
+ * FadeIn — fades its children in (with a small upward translate) once, on mount.
113
+ * The lightweight, mount-only sibling of {@link useEnterExit} (which handles the
114
+ * full enter/exit lifecycle). Under reduced-motion it renders its children at rest
115
+ * immediately — no fade, no translate.
116
+ */
117
+
118
+ interface FadeInProps {
119
+ children: React.ReactNode;
120
+ /** Fade duration in ms. Defaults to the `base` motion duration (200). */
121
+ duration?: number;
122
+ /** Delay before the fade starts, in ms. Default 0. */
123
+ delay?: number;
124
+ /** Enter translate-Y offset (px). `0` disables the translate. Default 8. */
125
+ translateY?: number;
126
+ style?: StyleProp<ViewStyle>;
127
+ testID?: string;
128
+ }
129
+ declare const FadeIn: ({ children, duration, delay, translateY, style, testID, }: FadeInProps) => React.ReactElement;
130
+
131
+ /**
132
+ * Collapse — an animated height expand/collapse that WORKS ON WEB.
133
+ *
134
+ * WHY NOT LayoutAnimation: RN's `LayoutAnimation` (what ui-layout's Accordion uses)
135
+ * is a NATIVE-only no-op under react-native-web, so an app that relied on it got no
136
+ * animation on the web build. Collapse instead measures the content's natural height
137
+ * via `onLayout` and animates a real `Animated.Value` height — the same code path on
138
+ * web and native. Height is a layout prop, so the native driver can never be used
139
+ * here (`useNativeDriver: false`).
140
+ *
141
+ * The children stay MOUNTED at all times (only clipped by `overflow: hidden`), so
142
+ * their state is preserved across open/close and screen readers still reach them.
143
+ * Reduced-motion snaps to the target height with no animation.
144
+ */
145
+
146
+ interface CollapseProps {
147
+ /** Whether the content is expanded. */
148
+ open: boolean;
149
+ children: React.ReactNode;
150
+ /** Expand/collapse duration in ms. Defaults to the `base` motion duration (200). */
151
+ duration?: number;
152
+ style?: StyleProp<ViewStyle>;
153
+ testID?: string;
154
+ }
155
+ declare const Collapse: ({ open, children, duration, style, testID, }: CollapseProps) => React.ReactElement;
156
+
157
+ /**
158
+ * PressableScale — a `Pressable` that scales down on press-in and springs back on
159
+ * press-out, giving tactile feedback without the flat opacity dip of a bare
160
+ * `activeOpacity`. Built to replace `activeOpacity` usage in `@dloizides/ui-buttons`.
161
+ *
162
+ * It forwards EVERY `Pressable` prop (onPress, accessibility*, disabled, hitSlop,
163
+ * …) so it is a drop-in wrapper; the scale is applied to an inner `Animated.View`
164
+ * so the outer `Pressable` keeps its full press/a11y behaviour. Under reduced-motion
165
+ * the scale is suppressed entirely (the element stays at scale 1).
166
+ */
167
+
168
+ interface PressableScaleProps extends PressableProps {
169
+ /** Scale to shrink to on press-in. Default 0.96. */
170
+ pressedScale?: number;
171
+ /** Style applied to the inner animated wrapper that carries the scale transform. */
172
+ innerStyle?: StyleProp<ViewStyle>;
173
+ }
174
+ declare const PressableScale: ({ pressedScale, innerStyle, onPressIn, onPressOut, children, testID, ...rest }: PressableScaleProps) => React.ReactElement;
175
+
176
+ /**
177
+ * Skeleton — a rounded placeholder block with a highlight band that sweeps across
178
+ * it, upgrading the opacity-pulse skeleton in `@dloizides/ui-feedback` to a moving
179
+ * shimmer (a stronger "content is loading" signal).
180
+ *
181
+ * The block measures its own pixel width via `onLayout`, then loops a highlight
182
+ * `Animated.View` from just off the left edge to just off the right edge (transform
183
+ * translateX — native-drivable on native, JS driver on web). Under reduced-motion
184
+ * the sweep is suppressed and a static block is shown, so it never animates against
185
+ * the user's OS preference.
186
+ */
187
+
188
+ interface SkeletonProps {
189
+ /** Block width. Number (px) or percentage string. Default '100%'. */
190
+ width?: number | string;
191
+ /** Block height in px. Default 16. */
192
+ height?: number;
193
+ /** Corner radius in px. Default 4. */
194
+ borderRadius?: number;
195
+ /** Shimmer sweep duration in ms. Default 1200. */
196
+ duration?: number;
197
+ /** Base block colour. */
198
+ backgroundColor?: string;
199
+ /** Sweeping highlight colour. */
200
+ highlightColor?: string;
201
+ style?: StyleProp<ViewStyle>;
202
+ testID?: string;
203
+ }
204
+ declare const Skeleton: ({ width, height, borderRadius, duration, backgroundColor, highlightColor, style, testID, }: SkeletonProps) => React.ReactElement;
205
+
206
+ export { Collapse, type CollapseProps, DEFAULT_FADE_TRANSLATE_Y, DEFAULT_PRESSED_SCALE, DEFAULT_SKELETON_DURATION_MS, FadeIn, type FadeInProps, MOTION_TEST_IDS, type MotionEasingName, PressableScale, type PressableScaleProps, Skeleton, type SkeletonProps, type UseEnterExitOptions, type UseEnterExitResult, collapseTargetHeight, motionDurations, motionEasingTokens, motionEasings, resolveDuration, resolveScale, shimmerRange, shouldUseNativeDriver, useEnterExit };
@@ -0,0 +1,206 @@
1
+ export { prefersReducedMotion, useReducedMotion } from '@dloizides/rn-web-hooks';
2
+ import { EasingFunction, Animated, StyleProp, ViewStyle, PressableProps } from 'react-native';
3
+ import { MotionEasings, MotionDurations } from '@dloizides/design-tokens';
4
+ export { DEFAULT_MOTION, Motion, MotionDurations, MotionEasings } from '@dloizides/design-tokens';
5
+ import React from 'react';
6
+
7
+ /**
8
+ * motionTokens — the RN-ready motion scale, DERIVED from `@dloizides/design-tokens`.
9
+ *
10
+ * design-tokens ships framework-agnostic VALUES (durations in ms + easings as a
11
+ * `css` string plus `[x1,y1,x2,y2]` bezier control points). This module turns the
12
+ * bezier tuples into RN `Easing.bezier(...)` functions ONCE, so every primitive in
13
+ * this package animates on the same curves the web/CSS side uses — one source, two
14
+ * runtimes. Nothing here is app-specific; motion is uniform across portals.
15
+ */
16
+
17
+ /** Durations in ms (numbers). `instant` (0) is the reduced-motion collapse target. */
18
+ declare const motionDurations: MotionDurations;
19
+ /** The easing token group in its raw (`css` + `bezier`) form. */
20
+ declare const motionEasingTokens: MotionEasings;
21
+ /** The named RN easing curves, one per {@link MotionEasings} key. */
22
+ type MotionEasingName = keyof MotionEasings;
23
+ /**
24
+ * RN `EasingFunction`s built from the design-token bezier tuples. Built once at
25
+ * module load; the same three functions are reused by every primitive.
26
+ */
27
+ declare const motionEasings: Record<MotionEasingName, EasingFunction>;
28
+
29
+ /**
30
+ * The animation duration a primitive should actually use. Collapses to
31
+ * `instant` (0) under reduced-motion — the single source of the "every animation
32
+ * collapses to instant" rule the whole package promises. `0` means "jump to the
33
+ * end state on the next frame", i.e. no visible motion.
34
+ */
35
+ declare function resolveDuration(duration: number, reduced: boolean): number;
36
+ /**
37
+ * Whether RN `Animated` should use the native driver. Layout props (height) can
38
+ * NEVER be native-driven, so callers animating height must pass `false` regardless
39
+ * — this helper is for opacity/transform animations, which are native-drivable on
40
+ * native and must fall back to the JS driver on web.
41
+ */
42
+ declare function shouldUseNativeDriver(): boolean;
43
+ /**
44
+ * The target height for a {@link Collapse}: the measured content height when open,
45
+ * `0` when closed. `undefined` while the content has not been measured yet — the
46
+ * caller leaves height `auto` for that first layout pass so the content can report
47
+ * its natural size.
48
+ */
49
+ declare function collapseTargetHeight(open: boolean, measured: number | null): number | undefined;
50
+ /**
51
+ * The scale a {@link PressableScale} should animate to. `1` (no scale) whenever
52
+ * reduced-motion is on OR the control is not pressed; the pressed scale otherwise.
53
+ */
54
+ declare function resolveScale(pressed: boolean, reduced: boolean, pressedScale: number): number;
55
+ /**
56
+ * The `[from, to]` translateX range for a {@link Skeleton} shimmer sweep across a
57
+ * block of the given pixel width — the highlight travels from just off the left
58
+ * edge to just off the right edge.
59
+ */
60
+ declare function shimmerRange(width: number): readonly [number, number];
61
+
62
+ /**
63
+ * Default testID strings for the motion primitives. Consumers can override each
64
+ * via the component's `testID` prop; these are the fallbacks so Playwright/RTL
65
+ * selectors have a stable default.
66
+ */
67
+ declare const MOTION_TEST_IDS: {
68
+ readonly fadeIn: "fade-in";
69
+ readonly collapse: "collapse";
70
+ readonly pressableScale: "pressable-scale";
71
+ readonly skeleton: "skeleton";
72
+ };
73
+ /** Default press-in scale for {@link PressableScale}. */
74
+ declare const DEFAULT_PRESSED_SCALE = 0.96;
75
+ /** Default enter translate-Y (px) for {@link FadeIn}. */
76
+ declare const DEFAULT_FADE_TRANSLATE_Y = 8;
77
+ /** Default shimmer sweep duration (ms) for {@link Skeleton}. */
78
+ declare const DEFAULT_SKELETON_DURATION_MS = 1200;
79
+
80
+ interface UseEnterExitOptions {
81
+ /** Whether the element should be visible (entered). */
82
+ visible: boolean;
83
+ /** Enter/exit duration in ms. Defaults to the `base` motion duration (200). */
84
+ duration?: number;
85
+ /**
86
+ * Initial translate-Y offset (px) the element enters from / exits to. `0`
87
+ * (default) disables the translate, leaving a pure fade.
88
+ */
89
+ translateY?: number;
90
+ /**
91
+ * Initial scale the element enters from / exits to. `1` (default) disables the
92
+ * scale, leaving a pure fade.
93
+ */
94
+ fromScale?: number;
95
+ }
96
+ interface UseEnterExitResult {
97
+ /** Animated style to spread onto an `Animated.View`. */
98
+ style: {
99
+ opacity: Animated.Value;
100
+ transform: Animated.WithAnimatedArray<{
101
+ translateY: Animated.AnimatedInterpolation<number>;
102
+ } | {
103
+ scale: Animated.AnimatedInterpolation<number>;
104
+ }>;
105
+ };
106
+ /** True while the element must remain in the tree (entered OR exiting). */
107
+ mounted: boolean;
108
+ }
109
+ declare function useEnterExit(options: UseEnterExitOptions): UseEnterExitResult;
110
+
111
+ /**
112
+ * FadeIn — fades its children in (with a small upward translate) once, on mount.
113
+ * The lightweight, mount-only sibling of {@link useEnterExit} (which handles the
114
+ * full enter/exit lifecycle). Under reduced-motion it renders its children at rest
115
+ * immediately — no fade, no translate.
116
+ */
117
+
118
+ interface FadeInProps {
119
+ children: React.ReactNode;
120
+ /** Fade duration in ms. Defaults to the `base` motion duration (200). */
121
+ duration?: number;
122
+ /** Delay before the fade starts, in ms. Default 0. */
123
+ delay?: number;
124
+ /** Enter translate-Y offset (px). `0` disables the translate. Default 8. */
125
+ translateY?: number;
126
+ style?: StyleProp<ViewStyle>;
127
+ testID?: string;
128
+ }
129
+ declare const FadeIn: ({ children, duration, delay, translateY, style, testID, }: FadeInProps) => React.ReactElement;
130
+
131
+ /**
132
+ * Collapse — an animated height expand/collapse that WORKS ON WEB.
133
+ *
134
+ * WHY NOT LayoutAnimation: RN's `LayoutAnimation` (what ui-layout's Accordion uses)
135
+ * is a NATIVE-only no-op under react-native-web, so an app that relied on it got no
136
+ * animation on the web build. Collapse instead measures the content's natural height
137
+ * via `onLayout` and animates a real `Animated.Value` height — the same code path on
138
+ * web and native. Height is a layout prop, so the native driver can never be used
139
+ * here (`useNativeDriver: false`).
140
+ *
141
+ * The children stay MOUNTED at all times (only clipped by `overflow: hidden`), so
142
+ * their state is preserved across open/close and screen readers still reach them.
143
+ * Reduced-motion snaps to the target height with no animation.
144
+ */
145
+
146
+ interface CollapseProps {
147
+ /** Whether the content is expanded. */
148
+ open: boolean;
149
+ children: React.ReactNode;
150
+ /** Expand/collapse duration in ms. Defaults to the `base` motion duration (200). */
151
+ duration?: number;
152
+ style?: StyleProp<ViewStyle>;
153
+ testID?: string;
154
+ }
155
+ declare const Collapse: ({ open, children, duration, style, testID, }: CollapseProps) => React.ReactElement;
156
+
157
+ /**
158
+ * PressableScale — a `Pressable` that scales down on press-in and springs back on
159
+ * press-out, giving tactile feedback without the flat opacity dip of a bare
160
+ * `activeOpacity`. Built to replace `activeOpacity` usage in `@dloizides/ui-buttons`.
161
+ *
162
+ * It forwards EVERY `Pressable` prop (onPress, accessibility*, disabled, hitSlop,
163
+ * …) so it is a drop-in wrapper; the scale is applied to an inner `Animated.View`
164
+ * so the outer `Pressable` keeps its full press/a11y behaviour. Under reduced-motion
165
+ * the scale is suppressed entirely (the element stays at scale 1).
166
+ */
167
+
168
+ interface PressableScaleProps extends PressableProps {
169
+ /** Scale to shrink to on press-in. Default 0.96. */
170
+ pressedScale?: number;
171
+ /** Style applied to the inner animated wrapper that carries the scale transform. */
172
+ innerStyle?: StyleProp<ViewStyle>;
173
+ }
174
+ declare const PressableScale: ({ pressedScale, innerStyle, onPressIn, onPressOut, children, testID, ...rest }: PressableScaleProps) => React.ReactElement;
175
+
176
+ /**
177
+ * Skeleton — a rounded placeholder block with a highlight band that sweeps across
178
+ * it, upgrading the opacity-pulse skeleton in `@dloizides/ui-feedback` to a moving
179
+ * shimmer (a stronger "content is loading" signal).
180
+ *
181
+ * The block measures its own pixel width via `onLayout`, then loops a highlight
182
+ * `Animated.View` from just off the left edge to just off the right edge (transform
183
+ * translateX — native-drivable on native, JS driver on web). Under reduced-motion
184
+ * the sweep is suppressed and a static block is shown, so it never animates against
185
+ * the user's OS preference.
186
+ */
187
+
188
+ interface SkeletonProps {
189
+ /** Block width. Number (px) or percentage string. Default '100%'. */
190
+ width?: number | string;
191
+ /** Block height in px. Default 16. */
192
+ height?: number;
193
+ /** Corner radius in px. Default 4. */
194
+ borderRadius?: number;
195
+ /** Shimmer sweep duration in ms. Default 1200. */
196
+ duration?: number;
197
+ /** Base block colour. */
198
+ backgroundColor?: string;
199
+ /** Sweeping highlight colour. */
200
+ highlightColor?: string;
201
+ style?: StyleProp<ViewStyle>;
202
+ testID?: string;
203
+ }
204
+ declare const Skeleton: ({ width, height, borderRadius, duration, backgroundColor, highlightColor, style, testID, }: SkeletonProps) => React.ReactElement;
205
+
206
+ export { Collapse, type CollapseProps, DEFAULT_FADE_TRANSLATE_Y, DEFAULT_PRESSED_SCALE, DEFAULT_SKELETON_DURATION_MS, FadeIn, type FadeInProps, MOTION_TEST_IDS, type MotionEasingName, PressableScale, type PressableScaleProps, Skeleton, type SkeletonProps, type UseEnterExitOptions, type UseEnterExitResult, collapseTargetHeight, motionDurations, motionEasingTokens, motionEasings, resolveDuration, resolveScale, shimmerRange, shouldUseNativeDriver, useEnterExit };