@metacells/mcellui-mcp-server 0.1.0 → 0.1.2
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/dist/index.js +70 -7
- package/package.json +7 -5
- package/registry/registry.json +717 -0
- package/registry/ui/accordion.tsx +416 -0
- package/registry/ui/action-sheet.tsx +396 -0
- package/registry/ui/alert-dialog.tsx +355 -0
- package/registry/ui/avatar-stack.tsx +278 -0
- package/registry/ui/avatar.tsx +116 -0
- package/registry/ui/badge.tsx +125 -0
- package/registry/ui/button.tsx +240 -0
- package/registry/ui/card.tsx +675 -0
- package/registry/ui/carousel.tsx +431 -0
- package/registry/ui/checkbox.tsx +252 -0
- package/registry/ui/chip.tsx +271 -0
- package/registry/ui/column.tsx +133 -0
- package/registry/ui/datetime-picker.tsx +578 -0
- package/registry/ui/dialog.tsx +292 -0
- package/registry/ui/fab.tsx +225 -0
- package/registry/ui/form.tsx +323 -0
- package/registry/ui/horizontal-list.tsx +200 -0
- package/registry/ui/icon-button.tsx +244 -0
- package/registry/ui/image-gallery.tsx +455 -0
- package/registry/ui/image.tsx +283 -0
- package/registry/ui/input.tsx +242 -0
- package/registry/ui/label.tsx +99 -0
- package/registry/ui/list.tsx +519 -0
- package/registry/ui/progress.tsx +168 -0
- package/registry/ui/pull-to-refresh.tsx +231 -0
- package/registry/ui/radio-group.tsx +294 -0
- package/registry/ui/rating.tsx +311 -0
- package/registry/ui/row.tsx +136 -0
- package/registry/ui/screen.tsx +153 -0
- package/registry/ui/search-input.tsx +281 -0
- package/registry/ui/section-header.tsx +258 -0
- package/registry/ui/segmented-control.tsx +229 -0
- package/registry/ui/select.tsx +311 -0
- package/registry/ui/separator.tsx +74 -0
- package/registry/ui/sheet.tsx +362 -0
- package/registry/ui/skeleton.tsx +156 -0
- package/registry/ui/slider.tsx +307 -0
- package/registry/ui/spinner.tsx +100 -0
- package/registry/ui/stepper.tsx +314 -0
- package/registry/ui/stories.tsx +463 -0
- package/registry/ui/swipeable-row.tsx +362 -0
- package/registry/ui/switch.tsx +246 -0
- package/registry/ui/tabs.tsx +348 -0
- package/registry/ui/textarea.tsx +265 -0
- package/registry/ui/toast.tsx +316 -0
- package/registry/ui/tooltip.tsx +369 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card
|
|
3
|
+
*
|
|
4
|
+
* A container component with rounded corners, shadow, and dark mode support.
|
|
5
|
+
* Uses design tokens for consistent styling.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // Basic Card
|
|
10
|
+
* <Card>
|
|
11
|
+
* <CardHeader>
|
|
12
|
+
* <CardTitle>Title</CardTitle>
|
|
13
|
+
* <CardDescription>Description</CardDescription>
|
|
14
|
+
* </CardHeader>
|
|
15
|
+
* <CardContent>
|
|
16
|
+
* <Text>Content goes here</Text>
|
|
17
|
+
* </CardContent>
|
|
18
|
+
* <CardFooter>
|
|
19
|
+
* <Button>Action</Button>
|
|
20
|
+
* </CardFooter>
|
|
21
|
+
* </Card>
|
|
22
|
+
*
|
|
23
|
+
* // Card with Image
|
|
24
|
+
* <Card>
|
|
25
|
+
* <CardImage source={{ uri: 'https://...' }} />
|
|
26
|
+
* <CardHeader>
|
|
27
|
+
* <CardTitle>Title</CardTitle>
|
|
28
|
+
* </CardHeader>
|
|
29
|
+
* </Card>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import React, { useCallback } from 'react';
|
|
34
|
+
import {
|
|
35
|
+
View,
|
|
36
|
+
Text,
|
|
37
|
+
Image,
|
|
38
|
+
ImageSourcePropType,
|
|
39
|
+
Pressable,
|
|
40
|
+
StyleSheet,
|
|
41
|
+
ViewStyle,
|
|
42
|
+
TextStyle,
|
|
43
|
+
ImageStyle,
|
|
44
|
+
PressableProps,
|
|
45
|
+
} from 'react-native';
|
|
46
|
+
import Animated, {
|
|
47
|
+
useSharedValue,
|
|
48
|
+
useAnimatedStyle,
|
|
49
|
+
withSpring,
|
|
50
|
+
} from 'react-native-reanimated';
|
|
51
|
+
import { useTheme, haptic } from '@nativeui/core';
|
|
52
|
+
|
|
53
|
+
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
|
54
|
+
|
|
55
|
+
export interface CardProps {
|
|
56
|
+
children: React.ReactNode;
|
|
57
|
+
/** Make card pressable */
|
|
58
|
+
onPress?: PressableProps['onPress'];
|
|
59
|
+
/** Disable press interaction */
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
/** Additional container styles */
|
|
62
|
+
style?: ViewStyle;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function Card({ children, onPress, disabled, style }: CardProps) {
|
|
66
|
+
const { colors, components, platformShadow, springs } = useTheme();
|
|
67
|
+
const tokens = components.card;
|
|
68
|
+
const scale = useSharedValue(1);
|
|
69
|
+
const isInteractive = !!onPress && !disabled;
|
|
70
|
+
|
|
71
|
+
const handlePressIn = useCallback(() => {
|
|
72
|
+
if (isInteractive) {
|
|
73
|
+
scale.value = withSpring(0.98, springs.snappy);
|
|
74
|
+
}
|
|
75
|
+
}, [isInteractive, springs.snappy]);
|
|
76
|
+
|
|
77
|
+
const handlePressOut = useCallback(() => {
|
|
78
|
+
if (isInteractive) {
|
|
79
|
+
scale.value = withSpring(1, springs.snappy);
|
|
80
|
+
}
|
|
81
|
+
}, [isInteractive, springs.snappy]);
|
|
82
|
+
|
|
83
|
+
const handlePress = useCallback(
|
|
84
|
+
(e: any) => {
|
|
85
|
+
if (isInteractive) {
|
|
86
|
+
haptic('light');
|
|
87
|
+
onPress?.(e);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
[isInteractive, onPress]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
94
|
+
transform: [{ scale: scale.value }],
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
const cardStyle = [
|
|
98
|
+
styles.card,
|
|
99
|
+
{
|
|
100
|
+
borderRadius: tokens.borderRadius,
|
|
101
|
+
borderWidth: tokens.borderWidth,
|
|
102
|
+
backgroundColor: colors.card,
|
|
103
|
+
borderColor: colors.border,
|
|
104
|
+
},
|
|
105
|
+
platformShadow('sm'),
|
|
106
|
+
style,
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
if (isInteractive) {
|
|
110
|
+
return (
|
|
111
|
+
<AnimatedPressable
|
|
112
|
+
style={[cardStyle, animatedStyle]}
|
|
113
|
+
onPressIn={handlePressIn}
|
|
114
|
+
onPressOut={handlePressOut}
|
|
115
|
+
onPress={handlePress}
|
|
116
|
+
disabled={disabled}
|
|
117
|
+
accessibilityRole="button"
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</AnimatedPressable>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return <View style={cardStyle}>{children}</View>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// CardImage - Image header for cards
|
|
129
|
+
// =============================================================================
|
|
130
|
+
|
|
131
|
+
export interface CardImageProps {
|
|
132
|
+
/** Image source */
|
|
133
|
+
source: ImageSourcePropType;
|
|
134
|
+
/** Image height (default: 200) */
|
|
135
|
+
height?: number;
|
|
136
|
+
/** Aspect ratio (overrides height) */
|
|
137
|
+
aspectRatio?: number;
|
|
138
|
+
/** Image resize mode */
|
|
139
|
+
resizeMode?: 'cover' | 'contain' | 'stretch' | 'center';
|
|
140
|
+
/** Overlay gradient for text readability */
|
|
141
|
+
overlay?: boolean;
|
|
142
|
+
/** Additional image styles */
|
|
143
|
+
style?: ImageStyle;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function CardImage({
|
|
147
|
+
source,
|
|
148
|
+
height = 200,
|
|
149
|
+
aspectRatio,
|
|
150
|
+
resizeMode = 'cover',
|
|
151
|
+
overlay = false,
|
|
152
|
+
style,
|
|
153
|
+
}: CardImageProps) {
|
|
154
|
+
const { colors, components } = useTheme();
|
|
155
|
+
const tokens = components.card;
|
|
156
|
+
// Calculate inner border radius (outer - border width)
|
|
157
|
+
const innerRadius = tokens.borderRadius - tokens.borderWidth;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<View style={[styles.imageContainer, { borderTopLeftRadius: innerRadius, borderTopRightRadius: innerRadius }]}>
|
|
161
|
+
<Image
|
|
162
|
+
source={source}
|
|
163
|
+
style={[
|
|
164
|
+
styles.image,
|
|
165
|
+
aspectRatio ? { aspectRatio } : { height },
|
|
166
|
+
style,
|
|
167
|
+
]}
|
|
168
|
+
resizeMode={resizeMode}
|
|
169
|
+
/>
|
|
170
|
+
{overlay && (
|
|
171
|
+
<View
|
|
172
|
+
style={[
|
|
173
|
+
styles.imageOverlay,
|
|
174
|
+
{ backgroundColor: colors.scrim },
|
|
175
|
+
]}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
</View>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface CardHeaderProps {
|
|
183
|
+
children: React.ReactNode;
|
|
184
|
+
style?: ViewStyle;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function CardHeader({ children, style }: CardHeaderProps) {
|
|
188
|
+
const { components, spacing } = useTheme();
|
|
189
|
+
const tokens = components.card;
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<View
|
|
193
|
+
style={[
|
|
194
|
+
styles.header,
|
|
195
|
+
{
|
|
196
|
+
padding: tokens.headerPadding,
|
|
197
|
+
paddingBottom: spacing[2],
|
|
198
|
+
},
|
|
199
|
+
style,
|
|
200
|
+
]}
|
|
201
|
+
>
|
|
202
|
+
{children}
|
|
203
|
+
</View>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface CardTitleProps {
|
|
208
|
+
children: React.ReactNode;
|
|
209
|
+
style?: TextStyle;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function CardTitle({ children, style }: CardTitleProps) {
|
|
213
|
+
const { colors, components } = useTheme();
|
|
214
|
+
const tokens = components.card;
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<Text
|
|
218
|
+
style={[
|
|
219
|
+
styles.title,
|
|
220
|
+
{
|
|
221
|
+
fontSize: tokens.titleFontSize,
|
|
222
|
+
fontWeight: tokens.titleFontWeight,
|
|
223
|
+
color: colors.cardForeground,
|
|
224
|
+
},
|
|
225
|
+
style,
|
|
226
|
+
]}
|
|
227
|
+
accessibilityRole="header"
|
|
228
|
+
>
|
|
229
|
+
{children}
|
|
230
|
+
</Text>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface CardDescriptionProps {
|
|
235
|
+
children: React.ReactNode;
|
|
236
|
+
style?: TextStyle;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function CardDescription({ children, style }: CardDescriptionProps) {
|
|
240
|
+
const { colors, components, spacing } = useTheme();
|
|
241
|
+
const tokens = components.card;
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<Text
|
|
245
|
+
style={[
|
|
246
|
+
styles.description,
|
|
247
|
+
{
|
|
248
|
+
fontSize: tokens.descriptionFontSize,
|
|
249
|
+
marginTop: spacing[1],
|
|
250
|
+
color: colors.foregroundMuted,
|
|
251
|
+
},
|
|
252
|
+
style,
|
|
253
|
+
]}
|
|
254
|
+
>
|
|
255
|
+
{children}
|
|
256
|
+
</Text>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface CardContentProps {
|
|
261
|
+
children: React.ReactNode;
|
|
262
|
+
style?: ViewStyle;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function CardContent({ children, style }: CardContentProps) {
|
|
266
|
+
const { components } = useTheme();
|
|
267
|
+
const tokens = components.card;
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<View
|
|
271
|
+
style={[
|
|
272
|
+
styles.content,
|
|
273
|
+
{
|
|
274
|
+
padding: tokens.contentPadding,
|
|
275
|
+
paddingTop: 0,
|
|
276
|
+
},
|
|
277
|
+
style,
|
|
278
|
+
]}
|
|
279
|
+
>
|
|
280
|
+
{children}
|
|
281
|
+
</View>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface CardFooterProps {
|
|
286
|
+
children: React.ReactNode;
|
|
287
|
+
style?: ViewStyle;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function CardFooter({ children, style }: CardFooterProps) {
|
|
291
|
+
const { components, spacing } = useTheme();
|
|
292
|
+
const tokens = components.card;
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<View
|
|
296
|
+
style={[
|
|
297
|
+
styles.footer,
|
|
298
|
+
{
|
|
299
|
+
padding: tokens.footerPadding,
|
|
300
|
+
paddingTop: 0,
|
|
301
|
+
gap: spacing[2],
|
|
302
|
+
},
|
|
303
|
+
style,
|
|
304
|
+
]}
|
|
305
|
+
>
|
|
306
|
+
{children}
|
|
307
|
+
</View>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// =============================================================================
|
|
312
|
+
// ImageCard - Full image card with overlay content
|
|
313
|
+
// =============================================================================
|
|
314
|
+
|
|
315
|
+
export interface ImageCardProps {
|
|
316
|
+
/** Image source */
|
|
317
|
+
source: ImageSourcePropType;
|
|
318
|
+
/** Card title */
|
|
319
|
+
title?: string;
|
|
320
|
+
/** Card subtitle/description */
|
|
321
|
+
subtitle?: string;
|
|
322
|
+
/** Image height (default: 280) */
|
|
323
|
+
height?: number;
|
|
324
|
+
/** Aspect ratio (overrides height) */
|
|
325
|
+
aspectRatio?: number;
|
|
326
|
+
/** Make card pressable */
|
|
327
|
+
onPress?: PressableProps['onPress'];
|
|
328
|
+
/** Position of text overlay */
|
|
329
|
+
textPosition?: 'top' | 'bottom';
|
|
330
|
+
/** Additional container styles */
|
|
331
|
+
style?: ViewStyle;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function ImageCard({
|
|
335
|
+
source,
|
|
336
|
+
title,
|
|
337
|
+
subtitle,
|
|
338
|
+
height = 280,
|
|
339
|
+
aspectRatio,
|
|
340
|
+
onPress,
|
|
341
|
+
textPosition = 'bottom',
|
|
342
|
+
style,
|
|
343
|
+
}: ImageCardProps) {
|
|
344
|
+
const { colors, components, platformShadow, springs, spacing } = useTheme();
|
|
345
|
+
const tokens = components.card;
|
|
346
|
+
const scale = useSharedValue(1);
|
|
347
|
+
const isInteractive = !!onPress;
|
|
348
|
+
|
|
349
|
+
const handlePressIn = useCallback(() => {
|
|
350
|
+
if (isInteractive) {
|
|
351
|
+
scale.value = withSpring(0.97, springs.snappy);
|
|
352
|
+
}
|
|
353
|
+
}, [isInteractive, springs.snappy]);
|
|
354
|
+
|
|
355
|
+
const handlePressOut = useCallback(() => {
|
|
356
|
+
if (isInteractive) {
|
|
357
|
+
scale.value = withSpring(1, springs.snappy);
|
|
358
|
+
}
|
|
359
|
+
}, [isInteractive, springs.snappy]);
|
|
360
|
+
|
|
361
|
+
const handlePress = useCallback(
|
|
362
|
+
(e: any) => {
|
|
363
|
+
if (isInteractive) {
|
|
364
|
+
haptic('light');
|
|
365
|
+
onPress?.(e);
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
[isInteractive, onPress]
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
372
|
+
transform: [{ scale: scale.value }],
|
|
373
|
+
}));
|
|
374
|
+
|
|
375
|
+
const content = (
|
|
376
|
+
<>
|
|
377
|
+
<Image
|
|
378
|
+
source={source}
|
|
379
|
+
style={[
|
|
380
|
+
styles.imageCardImage,
|
|
381
|
+
aspectRatio ? { aspectRatio } : { height },
|
|
382
|
+
]}
|
|
383
|
+
resizeMode="cover"
|
|
384
|
+
/>
|
|
385
|
+
{/* Gradient overlay */}
|
|
386
|
+
<View
|
|
387
|
+
style={[
|
|
388
|
+
styles.imageCardGradient,
|
|
389
|
+
textPosition === 'top' ? styles.gradientTop : styles.gradientBottom,
|
|
390
|
+
]}
|
|
391
|
+
/>
|
|
392
|
+
{/* Text content */}
|
|
393
|
+
{(title || subtitle) && (
|
|
394
|
+
<View
|
|
395
|
+
style={[
|
|
396
|
+
styles.imageCardContent,
|
|
397
|
+
{ padding: spacing[4] },
|
|
398
|
+
textPosition === 'top' ? styles.contentTop : styles.contentBottom,
|
|
399
|
+
]}
|
|
400
|
+
>
|
|
401
|
+
{title && (
|
|
402
|
+
<Text style={[styles.imageCardTitle, { color: '#ffffff' }]}>
|
|
403
|
+
{title}
|
|
404
|
+
</Text>
|
|
405
|
+
)}
|
|
406
|
+
{subtitle && (
|
|
407
|
+
<Text style={[styles.imageCardSubtitle, { color: 'rgba(255,255,255,0.8)', marginTop: spacing[1] }]}>
|
|
408
|
+
{subtitle}
|
|
409
|
+
</Text>
|
|
410
|
+
)}
|
|
411
|
+
</View>
|
|
412
|
+
)}
|
|
413
|
+
</>
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
const cardStyle = [
|
|
417
|
+
styles.imageCard,
|
|
418
|
+
{ borderRadius: tokens.borderRadius + 4 },
|
|
419
|
+
platformShadow('md'),
|
|
420
|
+
style,
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
if (isInteractive) {
|
|
424
|
+
return (
|
|
425
|
+
<AnimatedPressable
|
|
426
|
+
style={[cardStyle, animatedStyle]}
|
|
427
|
+
onPressIn={handlePressIn}
|
|
428
|
+
onPressOut={handlePressOut}
|
|
429
|
+
onPress={handlePress}
|
|
430
|
+
accessibilityRole="button"
|
|
431
|
+
accessibilityLabel={title}
|
|
432
|
+
>
|
|
433
|
+
{content}
|
|
434
|
+
</AnimatedPressable>
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return <View style={cardStyle}>{content}</View>;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// =============================================================================
|
|
442
|
+
// MediaCard - Image with text below (outside card)
|
|
443
|
+
// =============================================================================
|
|
444
|
+
|
|
445
|
+
export interface MediaCardProps {
|
|
446
|
+
/** Image source */
|
|
447
|
+
source: ImageSourcePropType;
|
|
448
|
+
/** Card title */
|
|
449
|
+
title: string;
|
|
450
|
+
/** Card description */
|
|
451
|
+
description?: string;
|
|
452
|
+
/** Category/tag label */
|
|
453
|
+
category?: string;
|
|
454
|
+
/** Image height (default: 180) */
|
|
455
|
+
height?: number;
|
|
456
|
+
/** Aspect ratio (overrides height) */
|
|
457
|
+
aspectRatio?: number;
|
|
458
|
+
/** Make card pressable */
|
|
459
|
+
onPress?: PressableProps['onPress'];
|
|
460
|
+
/** Additional container styles */
|
|
461
|
+
style?: ViewStyle;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function MediaCard({
|
|
465
|
+
source,
|
|
466
|
+
title,
|
|
467
|
+
description,
|
|
468
|
+
category,
|
|
469
|
+
height = 180,
|
|
470
|
+
aspectRatio,
|
|
471
|
+
onPress,
|
|
472
|
+
style,
|
|
473
|
+
}: MediaCardProps) {
|
|
474
|
+
const { colors, components, platformShadow, springs, spacing } = useTheme();
|
|
475
|
+
const tokens = components.card;
|
|
476
|
+
const scale = useSharedValue(1);
|
|
477
|
+
const isInteractive = !!onPress;
|
|
478
|
+
|
|
479
|
+
const handlePressIn = useCallback(() => {
|
|
480
|
+
if (isInteractive) {
|
|
481
|
+
scale.value = withSpring(0.98, springs.snappy);
|
|
482
|
+
}
|
|
483
|
+
}, [isInteractive, springs.snappy]);
|
|
484
|
+
|
|
485
|
+
const handlePressOut = useCallback(() => {
|
|
486
|
+
if (isInteractive) {
|
|
487
|
+
scale.value = withSpring(1, springs.snappy);
|
|
488
|
+
}
|
|
489
|
+
}, [isInteractive, springs.snappy]);
|
|
490
|
+
|
|
491
|
+
const handlePress = useCallback(
|
|
492
|
+
(e: any) => {
|
|
493
|
+
if (isInteractive) {
|
|
494
|
+
haptic('light');
|
|
495
|
+
onPress?.(e);
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
[isInteractive, onPress]
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
502
|
+
transform: [{ scale: scale.value }],
|
|
503
|
+
}));
|
|
504
|
+
|
|
505
|
+
const content = (
|
|
506
|
+
<>
|
|
507
|
+
{/* Image container */}
|
|
508
|
+
<View style={[styles.mediaCardImageContainer, { borderRadius: tokens.borderRadius }, platformShadow('sm')]}>
|
|
509
|
+
<Image
|
|
510
|
+
source={source}
|
|
511
|
+
style={[
|
|
512
|
+
styles.mediaCardImage,
|
|
513
|
+
aspectRatio ? { aspectRatio } : { height },
|
|
514
|
+
]}
|
|
515
|
+
resizeMode="cover"
|
|
516
|
+
/>
|
|
517
|
+
</View>
|
|
518
|
+
{/* Text content (outside card) */}
|
|
519
|
+
<View style={[styles.mediaCardContent, { gap: spacing[1] }]}>
|
|
520
|
+
{category && (
|
|
521
|
+
<Text style={[styles.mediaCardCategory, { color: colors.primary }]}>
|
|
522
|
+
{category.toUpperCase()}
|
|
523
|
+
</Text>
|
|
524
|
+
)}
|
|
525
|
+
<Text
|
|
526
|
+
style={[styles.mediaCardTitle, { color: colors.foreground }]}
|
|
527
|
+
numberOfLines={2}
|
|
528
|
+
>
|
|
529
|
+
{title}
|
|
530
|
+
</Text>
|
|
531
|
+
{description && (
|
|
532
|
+
<Text
|
|
533
|
+
style={[styles.mediaCardDescription, { color: colors.foregroundMuted }]}
|
|
534
|
+
numberOfLines={2}
|
|
535
|
+
>
|
|
536
|
+
{description}
|
|
537
|
+
</Text>
|
|
538
|
+
)}
|
|
539
|
+
</View>
|
|
540
|
+
</>
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
if (isInteractive) {
|
|
544
|
+
return (
|
|
545
|
+
<AnimatedPressable
|
|
546
|
+
style={[styles.mediaCard, { gap: spacing[3] }, animatedStyle, style]}
|
|
547
|
+
onPressIn={handlePressIn}
|
|
548
|
+
onPressOut={handlePressOut}
|
|
549
|
+
onPress={handlePress}
|
|
550
|
+
accessibilityRole="button"
|
|
551
|
+
accessibilityLabel={title}
|
|
552
|
+
>
|
|
553
|
+
{content}
|
|
554
|
+
</AnimatedPressable>
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return <View style={[styles.mediaCard, { gap: spacing[3] }, style]}>{content}</View>;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// =============================================================================
|
|
562
|
+
// Styles
|
|
563
|
+
// =============================================================================
|
|
564
|
+
|
|
565
|
+
const styles = StyleSheet.create({
|
|
566
|
+
// Base Card
|
|
567
|
+
card: {
|
|
568
|
+
overflow: 'hidden',
|
|
569
|
+
},
|
|
570
|
+
header: {
|
|
571
|
+
// Dynamic styles applied inline
|
|
572
|
+
},
|
|
573
|
+
title: {
|
|
574
|
+
// Dynamic styles applied inline
|
|
575
|
+
},
|
|
576
|
+
description: {
|
|
577
|
+
// Dynamic styles applied inline
|
|
578
|
+
},
|
|
579
|
+
content: {
|
|
580
|
+
// Dynamic styles applied inline
|
|
581
|
+
},
|
|
582
|
+
footer: {
|
|
583
|
+
flexDirection: 'row',
|
|
584
|
+
alignItems: 'center',
|
|
585
|
+
justifyContent: 'flex-end',
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
// CardImage
|
|
589
|
+
imageContainer: {
|
|
590
|
+
position: 'relative',
|
|
591
|
+
overflow: 'hidden',
|
|
592
|
+
},
|
|
593
|
+
image: {
|
|
594
|
+
width: '100%',
|
|
595
|
+
},
|
|
596
|
+
imageOverlay: {
|
|
597
|
+
...StyleSheet.absoluteFillObject,
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
// ImageCard
|
|
601
|
+
imageCard: {
|
|
602
|
+
overflow: 'hidden',
|
|
603
|
+
position: 'relative',
|
|
604
|
+
},
|
|
605
|
+
imageCardImage: {
|
|
606
|
+
width: '100%',
|
|
607
|
+
},
|
|
608
|
+
imageCardGradient: {
|
|
609
|
+
position: 'absolute',
|
|
610
|
+
left: 0,
|
|
611
|
+
right: 0,
|
|
612
|
+
height: '50%',
|
|
613
|
+
},
|
|
614
|
+
gradientTop: {
|
|
615
|
+
top: 0,
|
|
616
|
+
backgroundColor: 'transparent',
|
|
617
|
+
},
|
|
618
|
+
gradientBottom: {
|
|
619
|
+
bottom: 0,
|
|
620
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
621
|
+
},
|
|
622
|
+
imageCardContent: {
|
|
623
|
+
position: 'absolute',
|
|
624
|
+
left: 0,
|
|
625
|
+
right: 0,
|
|
626
|
+
},
|
|
627
|
+
contentTop: {
|
|
628
|
+
top: 0,
|
|
629
|
+
},
|
|
630
|
+
contentBottom: {
|
|
631
|
+
bottom: 0,
|
|
632
|
+
},
|
|
633
|
+
imageCardTitle: {
|
|
634
|
+
fontSize: 20,
|
|
635
|
+
fontWeight: '700',
|
|
636
|
+
textShadowColor: 'rgba(0,0,0,0.3)',
|
|
637
|
+
textShadowOffset: { width: 0, height: 1 },
|
|
638
|
+
textShadowRadius: 3,
|
|
639
|
+
},
|
|
640
|
+
imageCardSubtitle: {
|
|
641
|
+
fontSize: 14,
|
|
642
|
+
fontWeight: '500',
|
|
643
|
+
textShadowColor: 'rgba(0,0,0,0.3)',
|
|
644
|
+
textShadowOffset: { width: 0, height: 1 },
|
|
645
|
+
textShadowRadius: 2,
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
// MediaCard
|
|
649
|
+
mediaCard: {
|
|
650
|
+
// Dynamic styles applied inline
|
|
651
|
+
},
|
|
652
|
+
mediaCardImageContainer: {
|
|
653
|
+
overflow: 'hidden',
|
|
654
|
+
},
|
|
655
|
+
mediaCardImage: {
|
|
656
|
+
width: '100%',
|
|
657
|
+
},
|
|
658
|
+
mediaCardContent: {
|
|
659
|
+
// Dynamic styles applied inline
|
|
660
|
+
},
|
|
661
|
+
mediaCardCategory: {
|
|
662
|
+
fontSize: 11,
|
|
663
|
+
fontWeight: '700',
|
|
664
|
+
letterSpacing: 0.5,
|
|
665
|
+
},
|
|
666
|
+
mediaCardTitle: {
|
|
667
|
+
fontSize: 16,
|
|
668
|
+
fontWeight: '600',
|
|
669
|
+
lineHeight: 22,
|
|
670
|
+
},
|
|
671
|
+
mediaCardDescription: {
|
|
672
|
+
fontSize: 14,
|
|
673
|
+
lineHeight: 20,
|
|
674
|
+
},
|
|
675
|
+
});
|