@idealyst/animate 1.2.38 → 1.2.40
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/animate",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.40",
|
|
4
4
|
"description": "Cross-platform animation hooks for React and React Native",
|
|
5
5
|
"readme": "README.md",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"publish:npm": "npm publish"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@idealyst/theme": "^1.2.
|
|
33
|
+
"@idealyst/theme": "^1.2.40",
|
|
34
34
|
"react": ">=16.8.0",
|
|
35
35
|
"react-native": ">=0.60.0",
|
|
36
36
|
"react-native-reanimated": ">=3.0.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@idealyst/theme": "^1.2.
|
|
51
|
+
"@idealyst/theme": "^1.2.40",
|
|
52
52
|
"@types/react": "^19.1.0",
|
|
53
53
|
"react": "^19.1.0",
|
|
54
54
|
"react-native": "^0.80.1",
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animate Package Examples
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the @idealyst/animate hooks:
|
|
5
|
+
* - useAnimatedStyle: State-driven style animations
|
|
6
|
+
* - useAnimatedValue: Numeric value animations with interpolation
|
|
7
|
+
* - usePresence: Mount/unmount animations
|
|
8
|
+
* - useGradientBorder: Animated gradient borders
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useState } from 'react';
|
|
12
|
+
import { Screen, View, Text, Button, Card, Slider, Divider, Switch } from '@idealyst/components';
|
|
13
|
+
import { useAnimatedStyle, useAnimatedValue, usePresence, useGradientBorder } from '../index';
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// useAnimatedStyle Demo
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
const AnimatedStyleDemo: React.FC = () => {
|
|
20
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
21
|
+
const [isHighlighted, setIsHighlighted] = useState(false);
|
|
22
|
+
|
|
23
|
+
const boxStyle = useAnimatedStyle(
|
|
24
|
+
{
|
|
25
|
+
opacity: isExpanded ? 1 : 0.6,
|
|
26
|
+
backgroundColor: isHighlighted ? '#6366f1' : '#e5e7eb',
|
|
27
|
+
borderRadius: isExpanded ? 16 : 8,
|
|
28
|
+
},
|
|
29
|
+
{ duration: 'normal', easing: 'easeOut' }
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Card padding="md" gap="md">
|
|
34
|
+
<Text typography="h4" weight="semibold">useAnimatedStyle</Text>
|
|
35
|
+
<Text color="secondary">
|
|
36
|
+
Animate styles based on state changes. Supports opacity, colors, border radius, and more.
|
|
37
|
+
</Text>
|
|
38
|
+
|
|
39
|
+
<View
|
|
40
|
+
style={{
|
|
41
|
+
height: 120,
|
|
42
|
+
justifyContent: 'center',
|
|
43
|
+
alignItems: 'center',
|
|
44
|
+
...boxStyle,
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<Text weight="semibold" style={{ color: isHighlighted ? '#fff' : '#374151' }}>
|
|
48
|
+
{isExpanded ? 'Expanded!' : 'Normal'}
|
|
49
|
+
</Text>
|
|
50
|
+
</View>
|
|
51
|
+
|
|
52
|
+
<View direction="row" gap="sm">
|
|
53
|
+
<Button
|
|
54
|
+
type={isExpanded ? 'contained' : 'outlined'}
|
|
55
|
+
onPress={() => setIsExpanded(!isExpanded)}
|
|
56
|
+
size="sm"
|
|
57
|
+
>
|
|
58
|
+
{isExpanded ? 'Collapse' : 'Expand'}
|
|
59
|
+
</Button>
|
|
60
|
+
<Button
|
|
61
|
+
type={isHighlighted ? 'contained' : 'outlined'}
|
|
62
|
+
intent={isHighlighted ? 'primary' : undefined}
|
|
63
|
+
onPress={() => setIsHighlighted(!isHighlighted)}
|
|
64
|
+
size="sm"
|
|
65
|
+
>
|
|
66
|
+
{isHighlighted ? 'Remove Color' : 'Add Color'}
|
|
67
|
+
</Button>
|
|
68
|
+
</View>
|
|
69
|
+
|
|
70
|
+
<View background="secondary" padding="sm" radius="sm">
|
|
71
|
+
<Text typography="caption" style={{ fontFamily: 'monospace' }}>
|
|
72
|
+
{`const style = useAnimatedStyle({
|
|
73
|
+
opacity: isExpanded ? 1 : 0.6,
|
|
74
|
+
backgroundColor: isHighlighted ? '#6366f1' : '#e5e7eb',
|
|
75
|
+
borderRadius: isExpanded ? 16 : 8,
|
|
76
|
+
}, { duration: 'normal', easing: 'easeOut' });`}
|
|
77
|
+
</Text>
|
|
78
|
+
</View>
|
|
79
|
+
</Card>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// useAnimatedValue Demo
|
|
85
|
+
// =============================================================================
|
|
86
|
+
|
|
87
|
+
const AnimatedValueDemo: React.FC = () => {
|
|
88
|
+
const progress = useAnimatedValue(0);
|
|
89
|
+
const [targetValue, setTargetValue] = useState(0);
|
|
90
|
+
|
|
91
|
+
const handleSetProgress = (value: number) => {
|
|
92
|
+
setTargetValue(value);
|
|
93
|
+
progress.set(value, { duration: 'normal', easing: 'easeInOut' });
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const color = progress.interpolate({
|
|
97
|
+
inputRange: [0, 0.5, 1],
|
|
98
|
+
outputRange: ['#ef4444', '#eab308', '#22c55e'],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Card padding="md" gap="md">
|
|
103
|
+
<Text typography="h4" weight="semibold">useAnimatedValue</Text>
|
|
104
|
+
<Text color="secondary">
|
|
105
|
+
Animate numeric values with interpolation support. Great for progress bars and counters.
|
|
106
|
+
</Text>
|
|
107
|
+
|
|
108
|
+
<View gap="sm">
|
|
109
|
+
<View
|
|
110
|
+
style={{
|
|
111
|
+
height: 24,
|
|
112
|
+
backgroundColor: '#e5e7eb',
|
|
113
|
+
borderRadius: 12,
|
|
114
|
+
overflow: 'hidden',
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<View
|
|
118
|
+
style={{
|
|
119
|
+
width: `${progress.value * 100}%`,
|
|
120
|
+
height: '100%',
|
|
121
|
+
borderRadius: 12,
|
|
122
|
+
backgroundColor: color,
|
|
123
|
+
transition: 'width 300ms ease-out, background-color 300ms ease-out',
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
</View>
|
|
127
|
+
<Text typography="caption" color="secondary" style={{ textAlign: 'center' }}>
|
|
128
|
+
{Math.round(progress.value * 100)}%
|
|
129
|
+
</Text>
|
|
130
|
+
</View>
|
|
131
|
+
|
|
132
|
+
<View gap="xs">
|
|
133
|
+
<Text typography="caption" weight="semibold">Set Progress:</Text>
|
|
134
|
+
<Slider
|
|
135
|
+
value={targetValue}
|
|
136
|
+
onValueChange={handleSetProgress}
|
|
137
|
+
minimumValue={0}
|
|
138
|
+
maximumValue={1}
|
|
139
|
+
step={0.1}
|
|
140
|
+
/>
|
|
141
|
+
</View>
|
|
142
|
+
|
|
143
|
+
<View direction="row" gap="sm" style={{ flexWrap: 'wrap' }}>
|
|
144
|
+
<Button type="outlined" size="sm" onPress={() => handleSetProgress(0)}>0%</Button>
|
|
145
|
+
<Button type="outlined" size="sm" onPress={() => handleSetProgress(0.25)}>25%</Button>
|
|
146
|
+
<Button type="outlined" size="sm" onPress={() => handleSetProgress(0.5)}>50%</Button>
|
|
147
|
+
<Button type="outlined" size="sm" onPress={() => handleSetProgress(0.75)}>75%</Button>
|
|
148
|
+
<Button type="outlined" size="sm" onPress={() => handleSetProgress(1)}>100%</Button>
|
|
149
|
+
</View>
|
|
150
|
+
|
|
151
|
+
<View background="secondary" padding="sm" radius="sm">
|
|
152
|
+
<Text typography="caption" style={{ fontFamily: 'monospace' }}>
|
|
153
|
+
{`const progress = useAnimatedValue(0);
|
|
154
|
+
progress.set(0.75, { duration: 'normal' });
|
|
155
|
+
progress.interpolate({
|
|
156
|
+
inputRange: [0, 0.5, 1],
|
|
157
|
+
outputRange: ['red', 'yellow', 'green'],
|
|
158
|
+
});`}
|
|
159
|
+
</Text>
|
|
160
|
+
</View>
|
|
161
|
+
</Card>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// =============================================================================
|
|
166
|
+
// usePresence Demo
|
|
167
|
+
// =============================================================================
|
|
168
|
+
|
|
169
|
+
const PresenceDemo: React.FC = () => {
|
|
170
|
+
const [showModal, setShowModal] = useState(false);
|
|
171
|
+
const [showNotification, setShowNotification] = useState(false);
|
|
172
|
+
|
|
173
|
+
const { isPresent: isModalPresent, style: modalStyle } = usePresence(showModal, {
|
|
174
|
+
enter: { opacity: 1 },
|
|
175
|
+
exit: { opacity: 0 },
|
|
176
|
+
duration: 'normal',
|
|
177
|
+
easing: 'easeOut',
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const { isPresent: isNotificationPresent, style: notificationStyle } = usePresence(showNotification, {
|
|
181
|
+
enter: { opacity: 1 },
|
|
182
|
+
exit: { opacity: 0 },
|
|
183
|
+
duration: 'fast',
|
|
184
|
+
easing: 'easeInOut',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<Card padding="md" gap="md">
|
|
189
|
+
<Text typography="h4" weight="semibold">usePresence</Text>
|
|
190
|
+
<Text color="secondary">
|
|
191
|
+
Animate elements when they mount and unmount. The element stays in the DOM until the exit animation completes.
|
|
192
|
+
</Text>
|
|
193
|
+
|
|
194
|
+
<View direction="row" gap="sm">
|
|
195
|
+
<Button
|
|
196
|
+
type={showModal ? 'contained' : 'outlined'}
|
|
197
|
+
onPress={() => setShowModal(!showModal)}
|
|
198
|
+
size="sm"
|
|
199
|
+
>
|
|
200
|
+
{showModal ? 'Hide Modal' : 'Show Modal'}
|
|
201
|
+
</Button>
|
|
202
|
+
<Button
|
|
203
|
+
type={showNotification ? 'contained' : 'outlined'}
|
|
204
|
+
onPress={() => setShowNotification(!showNotification)}
|
|
205
|
+
size="sm"
|
|
206
|
+
>
|
|
207
|
+
{showNotification ? 'Hide Toast' : 'Show Toast'}
|
|
208
|
+
</Button>
|
|
209
|
+
</View>
|
|
210
|
+
|
|
211
|
+
<View style={{ minHeight: 160, position: 'relative' }}>
|
|
212
|
+
{isModalPresent && (
|
|
213
|
+
<View
|
|
214
|
+
style={{
|
|
215
|
+
position: 'absolute',
|
|
216
|
+
top: 0,
|
|
217
|
+
left: 0,
|
|
218
|
+
right: 0,
|
|
219
|
+
backgroundColor: '#fff',
|
|
220
|
+
padding: 20,
|
|
221
|
+
borderRadius: 12,
|
|
222
|
+
borderWidth: 1,
|
|
223
|
+
borderColor: '#e5e7eb',
|
|
224
|
+
shadowColor: '#000',
|
|
225
|
+
shadowOffset: { width: 0, height: 4 },
|
|
226
|
+
shadowOpacity: 0.1,
|
|
227
|
+
shadowRadius: 12,
|
|
228
|
+
elevation: 4,
|
|
229
|
+
...modalStyle,
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
<Text weight="semibold">Modal Content</Text>
|
|
233
|
+
<Text color="secondary" typography="caption">
|
|
234
|
+
This modal animates in with fade, and animates out smoothly.
|
|
235
|
+
</Text>
|
|
236
|
+
</View>
|
|
237
|
+
)}
|
|
238
|
+
|
|
239
|
+
{isNotificationPresent && (
|
|
240
|
+
<View
|
|
241
|
+
style={{
|
|
242
|
+
position: 'absolute',
|
|
243
|
+
top: 0,
|
|
244
|
+
right: 0,
|
|
245
|
+
backgroundColor: '#22c55e',
|
|
246
|
+
padding: 12,
|
|
247
|
+
borderRadius: 8,
|
|
248
|
+
...notificationStyle,
|
|
249
|
+
}}
|
|
250
|
+
>
|
|
251
|
+
<Text style={{ color: '#fff' }} weight="semibold">Success!</Text>
|
|
252
|
+
</View>
|
|
253
|
+
)}
|
|
254
|
+
</View>
|
|
255
|
+
|
|
256
|
+
<View background="secondary" padding="sm" radius="sm">
|
|
257
|
+
<Text typography="caption" style={{ fontFamily: 'monospace' }}>
|
|
258
|
+
{`const { isPresent, style } = usePresence(isOpen, {
|
|
259
|
+
enter: { opacity: 1 },
|
|
260
|
+
exit: { opacity: 0 },
|
|
261
|
+
duration: 'normal',
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
{isPresent && <View style={{...baseStyle, ...style}}>...</View>}`}
|
|
265
|
+
</Text>
|
|
266
|
+
</View>
|
|
267
|
+
</Card>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// =============================================================================
|
|
272
|
+
// useGradientBorder Demo
|
|
273
|
+
// =============================================================================
|
|
274
|
+
|
|
275
|
+
const GradientBorderDemo: React.FC = () => {
|
|
276
|
+
const [animation, setAnimation] = useState<'spin' | 'pulse' | 'wave'>('spin');
|
|
277
|
+
const [isActive, setIsActive] = useState(true);
|
|
278
|
+
|
|
279
|
+
const spinGradient = useGradientBorder({
|
|
280
|
+
colors: ['#6366f1', '#8b5cf6', '#ec4899', '#6366f1'],
|
|
281
|
+
borderWidth: 3,
|
|
282
|
+
borderRadius: 16,
|
|
283
|
+
animation: 'spin',
|
|
284
|
+
duration: 'slow',
|
|
285
|
+
active: isActive && animation === 'spin',
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const pulseGradient = useGradientBorder({
|
|
289
|
+
colors: ['#22c55e', '#10b981', '#14b8a6', '#22c55e'],
|
|
290
|
+
borderWidth: 3,
|
|
291
|
+
borderRadius: 16,
|
|
292
|
+
animation: 'pulse',
|
|
293
|
+
duration: 'normal',
|
|
294
|
+
active: isActive && animation === 'pulse',
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const waveGradient = useGradientBorder({
|
|
298
|
+
colors: ['#f59e0b', '#ef4444', '#ec4899', '#f59e0b'],
|
|
299
|
+
borderWidth: 3,
|
|
300
|
+
borderRadius: 16,
|
|
301
|
+
animation: 'wave',
|
|
302
|
+
duration: 'normal',
|
|
303
|
+
active: isActive && animation === 'wave',
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const currentGradient =
|
|
307
|
+
animation === 'spin' ? spinGradient : animation === 'pulse' ? pulseGradient : waveGradient;
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<Card padding="md" gap="md">
|
|
311
|
+
<Text typography="h4" weight="semibold">useGradientBorder</Text>
|
|
312
|
+
<Text color="secondary">
|
|
313
|
+
Create animated gradient borders with spin, pulse, or wave effects.
|
|
314
|
+
</Text>
|
|
315
|
+
|
|
316
|
+
<View style={{ alignItems: 'center' }}>
|
|
317
|
+
<View style={{ padding: 3, ...currentGradient.containerStyle }}>
|
|
318
|
+
<View
|
|
319
|
+
style={{
|
|
320
|
+
padding: 24,
|
|
321
|
+
backgroundColor: '#fff',
|
|
322
|
+
alignItems: 'center',
|
|
323
|
+
justifyContent: 'center',
|
|
324
|
+
...currentGradient.contentStyle,
|
|
325
|
+
}}
|
|
326
|
+
>
|
|
327
|
+
<Text weight="semibold">Gradient Border</Text>
|
|
328
|
+
<Text typography="caption" color="secondary">Animation: {animation}</Text>
|
|
329
|
+
</View>
|
|
330
|
+
</View>
|
|
331
|
+
</View>
|
|
332
|
+
|
|
333
|
+
<View direction="row" gap="sm" style={{ justifyContent: 'center' }}>
|
|
334
|
+
<Button
|
|
335
|
+
type={animation === 'spin' ? 'contained' : 'outlined'}
|
|
336
|
+
onPress={() => setAnimation('spin')}
|
|
337
|
+
size="sm"
|
|
338
|
+
>
|
|
339
|
+
Spin
|
|
340
|
+
</Button>
|
|
341
|
+
<Button
|
|
342
|
+
type={animation === 'pulse' ? 'contained' : 'outlined'}
|
|
343
|
+
onPress={() => setAnimation('pulse')}
|
|
344
|
+
size="sm"
|
|
345
|
+
>
|
|
346
|
+
Pulse
|
|
347
|
+
</Button>
|
|
348
|
+
<Button
|
|
349
|
+
type={animation === 'wave' ? 'contained' : 'outlined'}
|
|
350
|
+
onPress={() => setAnimation('wave')}
|
|
351
|
+
size="sm"
|
|
352
|
+
>
|
|
353
|
+
Wave
|
|
354
|
+
</Button>
|
|
355
|
+
</View>
|
|
356
|
+
|
|
357
|
+
<View direction="row" gap="sm" style={{ alignItems: 'center', justifyContent: 'center' }}>
|
|
358
|
+
<Text typography="caption">Animation Active:</Text>
|
|
359
|
+
<Switch value={isActive} onValueChange={setIsActive} />
|
|
360
|
+
</View>
|
|
361
|
+
|
|
362
|
+
<View background="secondary" padding="sm" radius="sm">
|
|
363
|
+
<Text typography="caption" style={{ fontFamily: 'monospace' }}>
|
|
364
|
+
{`const { containerStyle, contentStyle } = useGradientBorder({
|
|
365
|
+
colors: ['#6366f1', '#8b5cf6', '#ec4899'],
|
|
366
|
+
borderWidth: 3,
|
|
367
|
+
animation: 'spin',
|
|
368
|
+
active: true,
|
|
369
|
+
});`}
|
|
370
|
+
</Text>
|
|
371
|
+
</View>
|
|
372
|
+
</Card>
|
|
373
|
+
);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// =============================================================================
|
|
377
|
+
// Main Screen
|
|
378
|
+
// =============================================================================
|
|
379
|
+
|
|
380
|
+
export const AnimateExamples: React.FC = () => {
|
|
381
|
+
return (
|
|
382
|
+
<Screen background="primary" padding="lg" scrollable>
|
|
383
|
+
<View gap="lg">
|
|
384
|
+
<Text typography="h2" weight="bold" align="center">
|
|
385
|
+
Animate Examples
|
|
386
|
+
</Text>
|
|
387
|
+
<Text color="secondary" align="center">
|
|
388
|
+
Cross-platform animation hooks for React and React Native
|
|
389
|
+
</Text>
|
|
390
|
+
|
|
391
|
+
<Divider />
|
|
392
|
+
|
|
393
|
+
<AnimatedStyleDemo />
|
|
394
|
+
<AnimatedValueDemo />
|
|
395
|
+
<PresenceDemo />
|
|
396
|
+
<GradientBorderDemo />
|
|
397
|
+
|
|
398
|
+
<Card type="elevated" padding="md" gap="sm">
|
|
399
|
+
<Text weight="semibold">About @idealyst/animate</Text>
|
|
400
|
+
<Text color="secondary">
|
|
401
|
+
Uses CSS transitions on web and React Native Reanimated on native for optimal performance.
|
|
402
|
+
All animations run on the UI thread when possible.
|
|
403
|
+
</Text>
|
|
404
|
+
<View gap="xs">
|
|
405
|
+
<Text typography="caption" weight="semibold">Available Hooks:</Text>
|
|
406
|
+
<Text typography="caption" color="secondary">• useAnimatedStyle - State-driven style animations</Text>
|
|
407
|
+
<Text typography="caption" color="secondary">• useAnimatedValue - Numeric value animations</Text>
|
|
408
|
+
<Text typography="caption" color="secondary">• usePresence - Mount/unmount animations</Text>
|
|
409
|
+
<Text typography="caption" color="secondary">• useGradientBorder - Animated gradient borders</Text>
|
|
410
|
+
</View>
|
|
411
|
+
</Card>
|
|
412
|
+
</View>
|
|
413
|
+
</Screen>
|
|
414
|
+
);
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
export default AnimateExamples;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AnimateExamples } from './AnimateExamples';
|