@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,463 @@
1
+ /**
2
+ * Stories
3
+ *
4
+ * Instagram-style circular avatar with gradient ring indicator.
5
+ * Shows unseen story status with animated gradient border.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Single story avatar
10
+ * <StoryAvatar
11
+ * source={{ uri: 'https://...' }}
12
+ * name="John"
13
+ * hasUnseenStory
14
+ * onPress={() => openStory(user.id)}
15
+ * />
16
+ *
17
+ * // Stories row
18
+ * <StoriesRow>
19
+ * <StoryAvatar source={...} name="Add" isAddStory onPress={addStory} />
20
+ * {users.map(user => (
21
+ * <StoryAvatar
22
+ * key={user.id}
23
+ * source={{ uri: user.avatar }}
24
+ * name={user.name}
25
+ * hasUnseenStory={user.hasNewStory}
26
+ * onPress={() => openStory(user.id)}
27
+ * />
28
+ * ))}
29
+ * </StoriesRow>
30
+ * ```
31
+ */
32
+
33
+ import React from 'react';
34
+ import {
35
+ View,
36
+ Text,
37
+ Image,
38
+ Pressable,
39
+ ScrollView,
40
+ StyleSheet,
41
+ ViewStyle,
42
+ ImageSourcePropType,
43
+ } from 'react-native';
44
+ import Animated, {
45
+ useAnimatedStyle,
46
+ useSharedValue,
47
+ withSpring,
48
+ withRepeat,
49
+ withTiming,
50
+ Easing,
51
+ } from 'react-native-reanimated';
52
+ import { LinearGradient } from 'expo-linear-gradient';
53
+ import { useTheme, haptic } from '@nativeui/core';
54
+
55
+ const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
56
+
57
+ // ─────────────────────────────────────────────────────────────────────────────
58
+ // Types
59
+ // ─────────────────────────────────────────────────────────────────────────────
60
+
61
+ export type StoryAvatarSize = 'sm' | 'md' | 'lg';
62
+
63
+ export interface StoryAvatarProps {
64
+ /** Avatar image source */
65
+ source?: ImageSourcePropType;
66
+ /** User name (shown below avatar) */
67
+ name?: string;
68
+ /** Whether user has unseen stories */
69
+ hasUnseenStory?: boolean;
70
+ /** Whether this is the "Add Story" button */
71
+ isAddStory?: boolean;
72
+ /** Press handler */
73
+ onPress?: () => void;
74
+ /** Size preset */
75
+ size?: StoryAvatarSize;
76
+ /** Custom gradient colors for ring */
77
+ gradientColors?: [string, string, ...string[]];
78
+ /** Whether story has been seen (gray ring) */
79
+ seen?: boolean;
80
+ /** Container style */
81
+ style?: ViewStyle;
82
+ }
83
+
84
+ export interface StoriesRowProps {
85
+ /** Story avatars */
86
+ children: React.ReactNode;
87
+ /** Container style */
88
+ style?: ViewStyle;
89
+ /** Content container style */
90
+ contentContainerStyle?: ViewStyle;
91
+ }
92
+
93
+ // ─────────────────────────────────────────────────────────────────────────────
94
+ // Size configs
95
+ // ─────────────────────────────────────────────────────────────────────────────
96
+
97
+ const SIZE_CONFIG = {
98
+ sm: {
99
+ outer: 56,
100
+ inner: 50,
101
+ avatar: 46,
102
+ borderWidth: 3,
103
+ fontSize: 10,
104
+ plusSize: 14,
105
+ },
106
+ md: {
107
+ outer: 72,
108
+ inner: 64,
109
+ avatar: 60,
110
+ borderWidth: 3,
111
+ fontSize: 11,
112
+ plusSize: 18,
113
+ },
114
+ lg: {
115
+ outer: 88,
116
+ inner: 80,
117
+ avatar: 74,
118
+ borderWidth: 4,
119
+ fontSize: 12,
120
+ plusSize: 22,
121
+ },
122
+ };
123
+
124
+ // Default Instagram-like gradient
125
+ const DEFAULT_GRADIENT: [string, string, ...string[]] = ['#F58529', '#DD2A7B', '#8134AF', '#515BD4'];
126
+ const SEEN_GRADIENT: [string, string, ...string[]] = ['#DBDBDB', '#DBDBDB'];
127
+
128
+ // ─────────────────────────────────────────────────────────────────────────────
129
+ // Helper functions
130
+ // ─────────────────────────────────────────────────────────────────────────────
131
+
132
+ function getInitials(name?: string): string {
133
+ if (!name) return '?';
134
+ const parts = name.trim().split(/\s+/);
135
+ const first = parts[0] ?? '';
136
+ return first.charAt(0).toUpperCase();
137
+ }
138
+
139
+ // ─────────────────────────────────────────────────────────────────────────────
140
+ // StoryAvatar Component
141
+ // ─────────────────────────────────────────────────────────────────────────────
142
+
143
+ export function StoryAvatar({
144
+ source,
145
+ name,
146
+ hasUnseenStory = false,
147
+ isAddStory = false,
148
+ onPress,
149
+ size = 'md',
150
+ gradientColors,
151
+ seen = false,
152
+ style,
153
+ }: StoryAvatarProps) {
154
+ const { colors, fontWeight } = useTheme();
155
+ const config = SIZE_CONFIG[size];
156
+
157
+ const scale = useSharedValue(1);
158
+
159
+ const animatedStyle = useAnimatedStyle(() => ({
160
+ transform: [{ scale: scale.value }],
161
+ }));
162
+
163
+ const handlePressIn = () => {
164
+ scale.value = withSpring(0.95, { damping: 15, stiffness: 400 });
165
+ };
166
+
167
+ const handlePressOut = () => {
168
+ scale.value = withSpring(1, { damping: 15, stiffness: 400 });
169
+ };
170
+
171
+ const handlePress = () => {
172
+ haptic('light');
173
+ onPress?.();
174
+ };
175
+
176
+ const showRing = hasUnseenStory || seen;
177
+ const ringColors = seen
178
+ ? SEEN_GRADIENT
179
+ : gradientColors || DEFAULT_GRADIENT;
180
+
181
+ const initials = getInitials(name);
182
+
183
+ return (
184
+ <View style={[styles.container, style]}>
185
+ <AnimatedPressable
186
+ onPress={handlePress}
187
+ onPressIn={handlePressIn}
188
+ onPressOut={handlePressOut}
189
+ style={[styles.avatarContainer, animatedStyle]}
190
+ accessibilityRole="button"
191
+ accessibilityLabel={isAddStory ? 'Add story' : `View ${name}'s story`}
192
+ >
193
+ {/* Gradient ring */}
194
+ {showRing ? (
195
+ <LinearGradient
196
+ colors={ringColors}
197
+ start={{ x: 0, y: 0 }}
198
+ end={{ x: 1, y: 1 }}
199
+ style={[
200
+ styles.gradientRing,
201
+ {
202
+ width: config.outer,
203
+ height: config.outer,
204
+ borderRadius: config.outer / 2,
205
+ },
206
+ ]}
207
+ >
208
+ {/* White inner border */}
209
+ <View
210
+ style={[
211
+ styles.innerBorder,
212
+ {
213
+ width: config.inner,
214
+ height: config.inner,
215
+ borderRadius: config.inner / 2,
216
+ backgroundColor: colors.background,
217
+ },
218
+ ]}
219
+ >
220
+ {/* Avatar */}
221
+ {source ? (
222
+ <Image
223
+ source={source}
224
+ style={[
225
+ styles.avatar,
226
+ {
227
+ width: config.avatar,
228
+ height: config.avatar,
229
+ borderRadius: config.avatar / 2,
230
+ },
231
+ ]}
232
+ />
233
+ ) : (
234
+ <View
235
+ style={[
236
+ styles.avatarFallback,
237
+ {
238
+ width: config.avatar,
239
+ height: config.avatar,
240
+ borderRadius: config.avatar / 2,
241
+ backgroundColor: colors.secondary,
242
+ },
243
+ ]}
244
+ >
245
+ <Text
246
+ style={[
247
+ styles.initials,
248
+ {
249
+ fontSize: config.avatar * 0.4,
250
+ fontWeight: fontWeight.semibold,
251
+ color: colors.secondaryForeground,
252
+ },
253
+ ]}
254
+ >
255
+ {initials}
256
+ </Text>
257
+ </View>
258
+ )}
259
+ </View>
260
+ </LinearGradient>
261
+ ) : (
262
+ /* No ring - just avatar with border */
263
+ <View
264
+ style={[
265
+ styles.noRingContainer,
266
+ {
267
+ width: config.outer,
268
+ height: config.outer,
269
+ borderRadius: config.outer / 2,
270
+ borderWidth: 1,
271
+ borderColor: colors.border,
272
+ backgroundColor: colors.background,
273
+ },
274
+ ]}
275
+ >
276
+ {source ? (
277
+ <Image
278
+ source={source}
279
+ style={[
280
+ styles.avatar,
281
+ {
282
+ width: config.avatar,
283
+ height: config.avatar,
284
+ borderRadius: config.avatar / 2,
285
+ },
286
+ ]}
287
+ />
288
+ ) : (
289
+ <View
290
+ style={[
291
+ styles.avatarFallback,
292
+ {
293
+ width: config.avatar,
294
+ height: config.avatar,
295
+ borderRadius: config.avatar / 2,
296
+ backgroundColor: colors.secondary,
297
+ },
298
+ ]}
299
+ >
300
+ <Text
301
+ style={[
302
+ styles.initials,
303
+ {
304
+ fontSize: config.avatar * 0.4,
305
+ fontWeight: fontWeight.semibold,
306
+ color: colors.secondaryForeground,
307
+ },
308
+ ]}
309
+ >
310
+ {initials}
311
+ </Text>
312
+ </View>
313
+ )}
314
+ </View>
315
+ )}
316
+
317
+ {/* Add story plus icon */}
318
+ {isAddStory && (
319
+ <View
320
+ style={[
321
+ styles.addBadge,
322
+ {
323
+ width: config.plusSize + 4,
324
+ height: config.plusSize + 4,
325
+ borderRadius: (config.plusSize + 4) / 2,
326
+ backgroundColor: colors.background,
327
+ bottom: 0,
328
+ right: 0,
329
+ },
330
+ ]}
331
+ >
332
+ <View
333
+ style={[
334
+ styles.addBadgeInner,
335
+ {
336
+ width: config.plusSize,
337
+ height: config.plusSize,
338
+ borderRadius: config.plusSize / 2,
339
+ backgroundColor: colors.primary,
340
+ },
341
+ ]}
342
+ >
343
+ <Text
344
+ style={[
345
+ styles.plusIcon,
346
+ {
347
+ fontSize: config.plusSize * 0.7,
348
+ color: colors.primaryForeground,
349
+ },
350
+ ]}
351
+ >
352
+ +
353
+ </Text>
354
+ </View>
355
+ </View>
356
+ )}
357
+ </AnimatedPressable>
358
+
359
+ {/* Name */}
360
+ {name && (
361
+ <Text
362
+ style={[
363
+ styles.name,
364
+ {
365
+ fontSize: config.fontSize,
366
+ color: colors.foreground,
367
+ maxWidth: config.outer + 8,
368
+ },
369
+ ]}
370
+ numberOfLines={1}
371
+ >
372
+ {isAddStory ? 'Your story' : name}
373
+ </Text>
374
+ )}
375
+ </View>
376
+ );
377
+ }
378
+
379
+ // ─────────────────────────────────────────────────────────────────────────────
380
+ // StoriesRow Component
381
+ // ─────────────────────────────────────────────────────────────────────────────
382
+
383
+ export function StoriesRow({
384
+ children,
385
+ style,
386
+ contentContainerStyle,
387
+ }: StoriesRowProps) {
388
+ const { spacing } = useTheme();
389
+
390
+ return (
391
+ <ScrollView
392
+ horizontal
393
+ showsHorizontalScrollIndicator={false}
394
+ style={[styles.row, style]}
395
+ contentContainerStyle={[
396
+ styles.rowContent,
397
+ { paddingHorizontal: spacing[4], gap: spacing[3] },
398
+ contentContainerStyle,
399
+ ]}
400
+ >
401
+ {children}
402
+ </ScrollView>
403
+ );
404
+ }
405
+
406
+ // ─────────────────────────────────────────────────────────────────────────────
407
+ // Styles
408
+ // ─────────────────────────────────────────────────────────────────────────────
409
+
410
+ const styles = StyleSheet.create({
411
+ container: {
412
+ alignItems: 'center',
413
+ },
414
+ avatarContainer: {
415
+ position: 'relative',
416
+ },
417
+ gradientRing: {
418
+ alignItems: 'center',
419
+ justifyContent: 'center',
420
+ },
421
+ innerBorder: {
422
+ alignItems: 'center',
423
+ justifyContent: 'center',
424
+ },
425
+ noRingContainer: {
426
+ alignItems: 'center',
427
+ justifyContent: 'center',
428
+ },
429
+ avatar: {
430
+ resizeMode: 'cover',
431
+ },
432
+ avatarFallback: {
433
+ alignItems: 'center',
434
+ justifyContent: 'center',
435
+ },
436
+ initials: {
437
+ textAlign: 'center',
438
+ },
439
+ addBadge: {
440
+ position: 'absolute',
441
+ alignItems: 'center',
442
+ justifyContent: 'center',
443
+ },
444
+ addBadgeInner: {
445
+ alignItems: 'center',
446
+ justifyContent: 'center',
447
+ },
448
+ plusIcon: {
449
+ fontWeight: '600',
450
+ marginTop: -1,
451
+ },
452
+ name: {
453
+ marginTop: 4,
454
+ textAlign: 'center',
455
+ },
456
+ row: {
457
+ flexGrow: 0,
458
+ },
459
+ rowContent: {
460
+ flexDirection: 'row',
461
+ alignItems: 'flex-start',
462
+ },
463
+ });