@umituz/react-native-design-system 2.6.56 → 2.6.58

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": "@umituz/react-native-design-system",
3
- "version": "2.6.56",
3
+ "version": "2.6.58",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -287,6 +287,11 @@ export {
287
287
  type AvatarShape,
288
288
  type AvatarConfig,
289
289
  type AvatarType,
290
+ // Media Card
291
+ MediaCard,
292
+ type MediaCardProps,
293
+ type MediaCardSize,
294
+ type MediaCardOverlayPosition,
290
295
  // Bottom Sheet
291
296
  BottomSheet,
292
297
  BottomSheetModal,
@@ -8,6 +8,14 @@ export * from './avatar';
8
8
  export * from './bottom-sheet';
9
9
  export { FormField, type FormFieldProps } from './FormField';
10
10
  export { ListItem, type ListItemProps } from './ListItem';
11
+
12
+ // Media Card
13
+ export { MediaCard } from './media-card';
14
+ export type {
15
+ MediaCardProps,
16
+ MediaCardSize,
17
+ MediaCardOverlayPosition,
18
+ } from './media-card';
11
19
  export { SearchBar, type SearchBarProps } from './SearchBar';
12
20
  export { IconContainer } from './IconContainer';
13
21
  export { BaseModal, type BaseModalProps } from './BaseModal';
@@ -0,0 +1,160 @@
1
+ /**
2
+ * AtomicMediaCard - Reusable media card component
3
+ *
4
+ * Displays an image with optional overlay text, badge, and selection state.
5
+ * Used for grids of images, memes, templates, etc.
6
+ */
7
+
8
+ import React from 'react';
9
+ import {
10
+ View,
11
+ StyleSheet,
12
+ TouchableOpacity,
13
+ Image,
14
+ type ImageStyle,
15
+ } from 'react-native';
16
+ import { AtomicText, AtomicIcon } from '../../atoms';
17
+ import { useAppDesignTokens } from '../../theme';
18
+
19
+ import type { MediaCardProps } from './types';
20
+
21
+ export const MediaCard: React.FC<MediaCardProps> = ({
22
+ uri,
23
+ title,
24
+ subtitle,
25
+ badge,
26
+ selected = false,
27
+ size = 'md',
28
+ aspectRatio = 0.8,
29
+ overlayPosition = 'bottom',
30
+ showOverlay = true,
31
+ onPress,
32
+ width,
33
+ testID,
34
+ }) => {
35
+ const tokens = useAppDesignTokens();
36
+
37
+ const sizeConfig = {
38
+ sm: { width: width || 120, borderRadius: tokens.radius.sm },
39
+ md: { width: width || 144, borderRadius: tokens.radius.md },
40
+ lg: { width: width || 160, borderRadius: tokens.radius.lg },
41
+ };
42
+
43
+ const config = sizeConfig[size];
44
+ const height = config.width / aspectRatio;
45
+
46
+ const styles = StyleSheet.create({
47
+ container: {
48
+ width: config.width,
49
+ height,
50
+ borderRadius: config.borderRadius,
51
+ overflow: 'hidden',
52
+ backgroundColor: tokens.colors.surfaceVariant,
53
+ borderWidth: selected ? 2 : 1,
54
+ borderColor: selected ? tokens.colors.primary : 'rgba(255,255,255,0.1)',
55
+ },
56
+ image: {
57
+ width: '100%',
58
+ height: '100%',
59
+ } as ImageStyle,
60
+ badge: {
61
+ position: 'absolute',
62
+ top: tokens.spacing.sm,
63
+ right: tokens.spacing.sm,
64
+ backgroundColor: tokens.colors.primary,
65
+ paddingHorizontal: tokens.spacing.sm,
66
+ paddingVertical: 2,
67
+ borderRadius: 999,
68
+ zIndex: 10,
69
+ },
70
+ badgeText: {
71
+ color: tokens.colors.onPrimary,
72
+ fontSize: 10,
73
+ fontWeight: 'bold',
74
+ },
75
+ overlay: {
76
+ ...StyleSheet.absoluteFillObject,
77
+ backgroundColor: 'rgba(0,0,0,0.3)',
78
+ justifyContent:
79
+ overlayPosition === 'center' ? 'center' : 'flex-end',
80
+ alignItems: 'center',
81
+ paddingBottom:
82
+ overlayPosition === 'bottom' ? tokens.spacing.md : 0,
83
+ },
84
+ textContainer: {
85
+ paddingHorizontal: tokens.spacing.md,
86
+ width: '100%',
87
+ },
88
+ title: {
89
+ fontSize: 14,
90
+ fontWeight: 'bold',
91
+ color: tokens.colors.textInverse,
92
+ textShadowColor: 'rgba(0,0,0,0.75)',
93
+ textShadowOffset: { width: 0, height: 1 },
94
+ textShadowRadius: 3,
95
+ textAlign: 'center',
96
+ },
97
+ subtitle: {
98
+ fontSize: 12,
99
+ color: tokens.colors.textSecondary,
100
+ textShadowColor: 'rgba(0,0,0,0.75)',
101
+ textShadowOffset: { width: 0, height: 1 },
102
+ textShadowRadius: 3,
103
+ textAlign: 'center',
104
+ },
105
+ checkCircle: {
106
+ backgroundColor: 'rgba(0,0,0,0.6)',
107
+ borderRadius: 999,
108
+ padding: tokens.spacing.sm,
109
+ },
110
+ });
111
+
112
+ const CardWrapper = onPress ? TouchableOpacity : View;
113
+ const wrapperProps = onPress
114
+ ? {
115
+ onPress,
116
+ activeOpacity: 0.8,
117
+ testID,
118
+ }
119
+ : { testID };
120
+
121
+ return (
122
+ <CardWrapper style={styles.container} {...wrapperProps}>
123
+ {badge && (
124
+ <View style={styles.badge}>
125
+ <AtomicText style={styles.badgeText}>{badge}</AtomicText>
126
+ </View>
127
+ )}
128
+ <Image source={{ uri }} style={styles.image} resizeMode="cover" />
129
+
130
+ {showOverlay && (title || subtitle) && (
131
+ <View style={styles.overlay}>
132
+ <View style={styles.textContainer}>
133
+ {title && (
134
+ <AtomicText style={styles.title} numberOfLines={1}>
135
+ {title}
136
+ </AtomicText>
137
+ )}
138
+ {subtitle && (
139
+ <AtomicText style={styles.subtitle} numberOfLines={1}>
140
+ {subtitle}
141
+ </AtomicText>
142
+ )}
143
+ </View>
144
+ </View>
145
+ )}
146
+
147
+ {selected && (
148
+ <View
149
+ style={[
150
+ styles.overlay,
151
+ ]}
152
+ >
153
+ <View style={styles.checkCircle}>
154
+ <AtomicIcon name="checkmark-outline" size="md" color="primary" />
155
+ </View>
156
+ </View>
157
+ )}
158
+ </CardWrapper>
159
+ );
160
+ };
@@ -0,0 +1,2 @@
1
+ export { MediaCard } from './MediaCard';
2
+ export type { MediaCardProps, MediaCardSize, MediaCardOverlayPosition } from './types';
@@ -0,0 +1,40 @@
1
+ export type MediaCardSize = 'sm' | 'md' | 'lg';
2
+ export type MediaCardOverlayPosition = 'bottom' | 'center';
3
+
4
+ export interface MediaCardProps {
5
+ /** Image URI to display */
6
+ uri: string;
7
+
8
+ /** Optional title text */
9
+ title?: string;
10
+
11
+ /** Optional subtitle text (uses count, etc.) */
12
+ subtitle?: string;
13
+
14
+ /** Optional badge text (NEW, etc.) */
15
+ badge?: string;
16
+
17
+ /** Whether the card is selected */
18
+ selected?: boolean;
19
+
20
+ /** Card size */
21
+ size?: MediaCardSize;
22
+
23
+ /** Aspect ratio (width/height) */
24
+ aspectRatio?: number;
25
+
26
+ /** Overlay position */
27
+ overlayPosition?: MediaCardOverlayPosition;
28
+
29
+ /** Whether to show overlay */
30
+ showOverlay?: boolean;
31
+
32
+ /** Press handler */
33
+ onPress?: () => void;
34
+
35
+ /** Custom width */
36
+ width?: number;
37
+
38
+ /** Test ID */
39
+ testID?: string;
40
+ }