@metacells/mcellui-mcp-server 0.1.1 → 0.1.3

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.
Files changed (49) hide show
  1. package/dist/index.js +14 -3
  2. package/package.json +5 -3
  3. package/registry/registry.json +717 -0
  4. package/registry/ui/accordion.tsx +416 -0
  5. package/registry/ui/action-sheet.tsx +396 -0
  6. package/registry/ui/alert-dialog.tsx +355 -0
  7. package/registry/ui/avatar-stack.tsx +278 -0
  8. package/registry/ui/avatar.tsx +116 -0
  9. package/registry/ui/badge.tsx +125 -0
  10. package/registry/ui/button.tsx +240 -0
  11. package/registry/ui/card.tsx +675 -0
  12. package/registry/ui/carousel.tsx +431 -0
  13. package/registry/ui/checkbox.tsx +252 -0
  14. package/registry/ui/chip.tsx +271 -0
  15. package/registry/ui/column.tsx +133 -0
  16. package/registry/ui/datetime-picker.tsx +578 -0
  17. package/registry/ui/dialog.tsx +292 -0
  18. package/registry/ui/fab.tsx +225 -0
  19. package/registry/ui/form.tsx +323 -0
  20. package/registry/ui/horizontal-list.tsx +200 -0
  21. package/registry/ui/icon-button.tsx +244 -0
  22. package/registry/ui/image-gallery.tsx +455 -0
  23. package/registry/ui/image.tsx +283 -0
  24. package/registry/ui/input.tsx +242 -0
  25. package/registry/ui/label.tsx +99 -0
  26. package/registry/ui/list.tsx +519 -0
  27. package/registry/ui/progress.tsx +168 -0
  28. package/registry/ui/pull-to-refresh.tsx +231 -0
  29. package/registry/ui/radio-group.tsx +294 -0
  30. package/registry/ui/rating.tsx +311 -0
  31. package/registry/ui/row.tsx +136 -0
  32. package/registry/ui/screen.tsx +153 -0
  33. package/registry/ui/search-input.tsx +281 -0
  34. package/registry/ui/section-header.tsx +258 -0
  35. package/registry/ui/segmented-control.tsx +229 -0
  36. package/registry/ui/select.tsx +311 -0
  37. package/registry/ui/separator.tsx +74 -0
  38. package/registry/ui/sheet.tsx +362 -0
  39. package/registry/ui/skeleton.tsx +156 -0
  40. package/registry/ui/slider.tsx +307 -0
  41. package/registry/ui/spinner.tsx +100 -0
  42. package/registry/ui/stepper.tsx +314 -0
  43. package/registry/ui/stories.tsx +463 -0
  44. package/registry/ui/swipeable-row.tsx +362 -0
  45. package/registry/ui/switch.tsx +246 -0
  46. package/registry/ui/tabs.tsx +348 -0
  47. package/registry/ui/textarea.tsx +265 -0
  48. package/registry/ui/toast.tsx +316 -0
  49. 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
+ });