@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 +33 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/index.d.mts +206 -0
- package/dist/index.d.ts +206 -0
- package/dist/index.js +320 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +292 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +113 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|