rnnovaui 0.2.1 → 0.3.0

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": "rnnovaui",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -0,0 +1,482 @@
1
+ import React, { memo, useCallback, useMemo, useRef } from "react";
2
+
3
+ import {
4
+ Animated,
5
+ Easing,
6
+ Image,
7
+ Pressable,
8
+ StyleSheet,
9
+ View,
10
+ } from "react-native";
11
+
12
+ import { useUITheme } from "../../theme";
13
+
14
+ const CARD_VARIANTS = {
15
+ default: "default",
16
+ elevated: "elevated",
17
+ outlined: "outlined",
18
+ filled: "filled",
19
+ };
20
+
21
+ const CARD_RADIUS = {
22
+ none: 0,
23
+ sm: 8,
24
+ md: 12,
25
+ lg: 16,
26
+ xl: 20,
27
+ };
28
+
29
+ const FALLBACK_COLORS = {
30
+ background: "#FFFFFF",
31
+ surface: "#F8F8F8",
32
+ card: "#FFFFFF",
33
+ border: "#E5E5E5",
34
+ shadow: "#000000",
35
+ };
36
+
37
+ function UICardComponent({
38
+ children,
39
+
40
+ header = null,
41
+
42
+ content = null,
43
+
44
+ footer = null,
45
+
46
+ image = null,
47
+
48
+ imageSource = null,
49
+
50
+ imageHeight = 180,
51
+
52
+ variant = "default",
53
+
54
+ radius = "lg",
55
+
56
+ padding = 16,
57
+
58
+ gap = 12,
59
+
60
+ style,
61
+
62
+ headerStyle,
63
+
64
+ imageStyle,
65
+
66
+ contentStyle,
67
+
68
+ footerStyle,
69
+
70
+ disabled = false,
71
+
72
+ loading = false,
73
+
74
+ pressable = false,
75
+
76
+ onPress,
77
+
78
+ onLongPress,
79
+
80
+ onPressIn,
81
+
82
+ onPressOut,
83
+
84
+ pressAnimation = true,
85
+
86
+ pressScale = 0.985,
87
+
88
+ pressAnimationDuration = 120,
89
+
90
+ testID,
91
+
92
+ accessibilityLabel,
93
+
94
+ accessibilityHint,
95
+
96
+ ...props
97
+ }) {
98
+ const { theme } = useUITheme();
99
+
100
+ const colors = theme?.colors || {};
101
+
102
+ const scale = useRef(new Animated.Value(1)).current;
103
+
104
+ const animationRef = useRef(null);
105
+
106
+ const isDisabled = disabled || loading;
107
+
108
+ const safeVariant = CARD_VARIANTS[variant] ? variant : CARD_VARIANTS.default;
109
+
110
+ const resolvedRadius = CARD_RADIUS[radius] ?? CARD_RADIUS.lg;
111
+
112
+ const cardColors = useMemo(
113
+ () => getCardColors(safeVariant, colors),
114
+ [safeVariant, colors],
115
+ );
116
+
117
+ const stopAnimation = useCallback(() => {
118
+ if (animationRef.current) {
119
+ animationRef.current.stop();
120
+
121
+ animationRef.current = null;
122
+ }
123
+ }, []);
124
+
125
+ const animateScale = useCallback(
126
+ (targetScale) => {
127
+ if (!pressAnimation || !pressable || isDisabled) {
128
+ return;
129
+ }
130
+
131
+ stopAnimation();
132
+
133
+ const animation = Animated.timing(scale, {
134
+ toValue: targetScale,
135
+
136
+ duration: pressAnimationDuration,
137
+
138
+ easing: Easing.out(Easing.quad),
139
+
140
+ useNativeDriver: true,
141
+ });
142
+
143
+ animationRef.current = animation;
144
+
145
+ animation.start(() => {
146
+ animationRef.current = null;
147
+ });
148
+ },
149
+ [
150
+ pressAnimation,
151
+ pressable,
152
+ isDisabled,
153
+ stopAnimation,
154
+ scale,
155
+ pressAnimationDuration,
156
+ ],
157
+ );
158
+
159
+ const handlePressIn = useCallback(
160
+ (event) => {
161
+ if (!pressable || isDisabled) {
162
+ return;
163
+ }
164
+
165
+ animateScale(pressScale);
166
+
167
+ onPressIn?.(event);
168
+ },
169
+ [pressable, isDisabled, animateScale, pressScale, onPressIn],
170
+ );
171
+
172
+ const handlePressOut = useCallback(
173
+ (event) => {
174
+ if (!pressable || isDisabled) {
175
+ return;
176
+ }
177
+
178
+ animateScale(1);
179
+
180
+ onPressOut?.(event);
181
+ },
182
+ [pressable, isDisabled, animateScale, onPressOut],
183
+ );
184
+
185
+ const handlePress = useCallback(
186
+ (event) => {
187
+ if (!pressable || isDisabled) {
188
+ return;
189
+ }
190
+
191
+ onPress?.(event);
192
+ },
193
+ [pressable, isDisabled, onPress],
194
+ );
195
+
196
+ const handleLongPress = useCallback(
197
+ (event) => {
198
+ if (!pressable || isDisabled) {
199
+ return;
200
+ }
201
+
202
+ onLongPress?.(event);
203
+ },
204
+ [pressable, isDisabled, onLongPress],
205
+ );
206
+
207
+ const cardStyle = useMemo(
208
+ () => [
209
+ styles.card,
210
+
211
+ {
212
+ backgroundColor: cardColors.background,
213
+
214
+ borderColor: cardColors.border,
215
+
216
+ borderWidth: cardColors.borderWidth,
217
+
218
+ borderRadius: resolvedRadius,
219
+
220
+ padding,
221
+ },
222
+
223
+ cardColors.shadow,
224
+
225
+ {
226
+ opacity: isDisabled ? 0.55 : 1,
227
+
228
+ transform: [
229
+ {
230
+ scale,
231
+ },
232
+ ],
233
+ },
234
+
235
+ style,
236
+ ],
237
+ [cardColors, resolvedRadius, padding, isDisabled, scale, style],
238
+ );
239
+
240
+ const cardContent = (
241
+ <>
242
+ {image || imageSource ? (
243
+ <View
244
+ style={[
245
+ styles.imageWrapper,
246
+ {
247
+ height: imageHeight,
248
+
249
+ borderTopLeftRadius: resolvedRadius,
250
+
251
+ borderTopRightRadius: resolvedRadius,
252
+ },
253
+ ]}
254
+ >
255
+ {image || (
256
+ <Image
257
+ source={imageSource}
258
+ resizeMode="cover"
259
+ style={[styles.image, imageStyle]}
260
+ />
261
+ )}
262
+ </View>
263
+ ) : null}
264
+
265
+ {header ? (
266
+ <View
267
+ style={[
268
+ styles.header,
269
+ {
270
+ marginTop: image || imageSource ? gap : 0,
271
+ },
272
+ headerStyle,
273
+ ]}
274
+ >
275
+ {header}
276
+ </View>
277
+ ) : null}
278
+
279
+ {content ? (
280
+ <View
281
+ style={[
282
+ styles.content,
283
+ {
284
+ marginTop: header ? gap : 0,
285
+ },
286
+ contentStyle,
287
+ ]}
288
+ >
289
+ {content}
290
+ </View>
291
+ ) : null}
292
+
293
+ {children ? (
294
+ <View
295
+ style={[
296
+ styles.content,
297
+ {
298
+ marginTop: header || content ? gap : 0,
299
+ },
300
+ contentStyle,
301
+ ]}
302
+ >
303
+ {children}
304
+ </View>
305
+ ) : null}
306
+
307
+ {footer ? (
308
+ <View
309
+ style={[
310
+ styles.footer,
311
+ {
312
+ marginTop: header || content || children ? gap : 0,
313
+ },
314
+ footerStyle,
315
+ ]}
316
+ >
317
+ {footer}
318
+ </View>
319
+ ) : null}
320
+ </>
321
+ );
322
+
323
+ if (!pressable) {
324
+ return (
325
+ <Animated.View
326
+ {...props}
327
+ testID={testID}
328
+ style={cardStyle}
329
+ accessibilityLabel={accessibilityLabel}
330
+ >
331
+ {cardContent}
332
+ </Animated.View>
333
+ );
334
+ }
335
+
336
+ return (
337
+ <Animated.View {...props} testID={testID} style={cardStyle}>
338
+ <Pressable
339
+ disabled={isDisabled}
340
+ onPress={handlePress}
341
+ onLongPress={handleLongPress}
342
+ onPressIn={handlePressIn}
343
+ onPressOut={handlePressOut}
344
+ accessibilityRole="button"
345
+ accessibilityLabel={accessibilityLabel}
346
+ accessibilityHint={accessibilityHint}
347
+ accessibilityState={{
348
+ disabled: isDisabled,
349
+
350
+ busy: loading,
351
+ }}
352
+ style={styles.pressable}
353
+ >
354
+ {cardContent}
355
+ </Pressable>
356
+ </Animated.View>
357
+ );
358
+ }
359
+
360
+ function getCardColors(variant, colors) {
361
+ const background = colors.card || colors.surface || FALLBACK_COLORS.card;
362
+
363
+ const border = colors.border || FALLBACK_COLORS.border;
364
+
365
+ switch (variant) {
366
+ case CARD_VARIANTS.elevated:
367
+ return {
368
+ background,
369
+
370
+ border: "transparent",
371
+
372
+ borderWidth: 0,
373
+
374
+ shadow: styles.elevated,
375
+ };
376
+
377
+ case CARD_VARIANTS.outlined:
378
+ return {
379
+ background,
380
+
381
+ border,
382
+
383
+ borderWidth: 1,
384
+
385
+ shadow: styles.noShadow,
386
+ };
387
+
388
+ case CARD_VARIANTS.filled:
389
+ return {
390
+ background: colors.surface || FALLBACK_COLORS.surface,
391
+
392
+ border,
393
+
394
+ borderWidth: 0,
395
+
396
+ shadow: styles.noShadow,
397
+ };
398
+
399
+ case CARD_VARIANTS.default:
400
+ default:
401
+ return {
402
+ background,
403
+
404
+ border,
405
+
406
+ borderWidth: 0,
407
+
408
+ shadow: styles.noShadow,
409
+ };
410
+ }
411
+ }
412
+
413
+ const styles = StyleSheet.create({
414
+ card: {
415
+ width: "100%",
416
+
417
+ overflow: "hidden",
418
+ },
419
+
420
+ pressable: {
421
+ width: "100%",
422
+ },
423
+
424
+ imageWrapper: {
425
+ width: "100%",
426
+
427
+ overflow: "hidden",
428
+
429
+ marginHorizontal: -16,
430
+
431
+ marginTop: -16,
432
+
433
+ width: "calc(100% + 32px)",
434
+ },
435
+
436
+ image: {
437
+ width: "100%",
438
+
439
+ height: "100%",
440
+ },
441
+
442
+ header: {
443
+ width: "100%",
444
+ },
445
+
446
+ content: {
447
+ width: "100%",
448
+ },
449
+
450
+ footer: {
451
+ width: "100%",
452
+ },
453
+
454
+ elevated: {
455
+ shadowColor: FALLBACK_COLORS.shadow,
456
+
457
+ shadowOffset: {
458
+ width: 0,
459
+ height: 4,
460
+ },
461
+
462
+ shadowOpacity: 0.12,
463
+
464
+ shadowRadius: 10,
465
+
466
+ elevation: 4,
467
+ },
468
+
469
+ noShadow: {
470
+ shadowOpacity: 0,
471
+
472
+ elevation: 0,
473
+ },
474
+ });
475
+
476
+ export const UICard = memo(UICardComponent);
477
+
478
+ UICard.displayName = "UICard";
479
+
480
+ export { CARD_VARIANTS as UICardVariants, CARD_RADIUS as UICardRadius };
481
+
482
+ export default UICard;
@@ -0,0 +1 @@
1
+ export { UICard, UICardVariants, UICardRadius } from "./UICard";
@@ -6,3 +6,4 @@ export {
6
6
  UIIconButtonSizes,
7
7
  UIIconButtonShapes,
8
8
  } from "./IconButton";
9
+ export { UICard, UICardVariants, UICardRadius } from "./Card";
package/src/index.js CHANGED
@@ -24,4 +24,7 @@ export {
24
24
  UIIconButtonVariants,
25
25
  UIIconButtonSizes,
26
26
  UIIconButtonShapes,
27
+ UICard,
28
+ UICardVariants,
29
+ UICardRadius,
27
30
  } from "./components";