@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.
Files changed (49) hide show
  1. package/dist/index.js +70 -7
  2. package/package.json +7 -5
  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,283 @@
1
+ /**
2
+ * Image
3
+ *
4
+ * An enhanced image component with loading skeleton, error fallback,
5
+ * and optional blur-up placeholder effect.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Basic usage
10
+ * <Image
11
+ * source={{ uri: 'https://example.com/image.jpg' }}
12
+ * style={{ width: 200, height: 200 }}
13
+ * />
14
+ *
15
+ * // With blur-up placeholder
16
+ * <Image
17
+ * source={{ uri: 'https://example.com/image.jpg' }}
18
+ * placeholder={{ uri: 'https://example.com/image-blur.jpg' }}
19
+ * style={{ width: '100%', aspectRatio: 16 / 9 }}
20
+ * />
21
+ *
22
+ * // With error fallback
23
+ * <Image
24
+ * source={{ uri: 'https://example.com/image.jpg' }}
25
+ * fallback={<Text>Failed to load</Text>}
26
+ * style={{ width: 100, height: 100, borderRadius: 8 }}
27
+ * />
28
+ * ```
29
+ */
30
+
31
+ import React, { useState, useCallback } from 'react';
32
+ import {
33
+ View,
34
+ Image as RNImage,
35
+ ImageProps as RNImageProps,
36
+ ImageSourcePropType,
37
+ StyleSheet,
38
+ ViewStyle,
39
+ ImageStyle,
40
+ } from 'react-native';
41
+ import Animated, {
42
+ useSharedValue,
43
+ useAnimatedStyle,
44
+ withTiming,
45
+ interpolate,
46
+ Easing,
47
+ } from 'react-native-reanimated';
48
+ import Svg, { Path, Rect } from 'react-native-svg';
49
+ import { useTheme } from '@nativeui/core';
50
+
51
+ const AnimatedImage = Animated.createAnimatedComponent(RNImage);
52
+
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ // Types
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+
57
+ export interface ImageProps extends Omit<RNImageProps, 'source' | 'style'> {
58
+ /** Image source */
59
+ source: ImageSourcePropType;
60
+ /** Low-res placeholder for blur-up effect */
61
+ placeholder?: ImageSourcePropType;
62
+ /** Fallback content when image fails to load */
63
+ fallback?: React.ReactNode;
64
+ /** Show skeleton while loading */
65
+ showSkeleton?: boolean;
66
+ /** Container/image style */
67
+ style?: ImageStyle | ViewStyle;
68
+ /** Fade-in duration in ms */
69
+ fadeDuration?: number;
70
+ }
71
+
72
+ // ─────────────────────────────────────────────────────────────────────────────
73
+ // Default Fallback Icon
74
+ // ─────────────────────────────────────────────────────────────────────────────
75
+
76
+ function ImageFallbackIcon({ color, size = 48 }: { color: string; size?: number }) {
77
+ return (
78
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
79
+ <Rect
80
+ x="3"
81
+ y="3"
82
+ width="18"
83
+ height="18"
84
+ rx="2"
85
+ stroke={color}
86
+ strokeWidth="2"
87
+ />
88
+ <Path
89
+ d="M3 16l5-5 4 4 4-4 5 5"
90
+ stroke={color}
91
+ strokeWidth="2"
92
+ strokeLinecap="round"
93
+ strokeLinejoin="round"
94
+ />
95
+ <Path
96
+ d="M8.5 10a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"
97
+ fill={color}
98
+ />
99
+ </Svg>
100
+ );
101
+ }
102
+
103
+ // ─────────────────────────────────────────────────────────────────────────────
104
+ // Skeleton Component
105
+ // ─────────────────────────────────────────────────────────────────────────────
106
+
107
+ function ImageSkeleton({ style }: { style?: ViewStyle }) {
108
+ const { colors } = useTheme();
109
+ const shimmer = useSharedValue(0);
110
+
111
+ React.useEffect(() => {
112
+ shimmer.value = withTiming(1, {
113
+ duration: 1500,
114
+ easing: Easing.inOut(Easing.ease),
115
+ });
116
+
117
+ const interval = setInterval(() => {
118
+ shimmer.value = 0;
119
+ shimmer.value = withTiming(1, {
120
+ duration: 1500,
121
+ easing: Easing.inOut(Easing.ease),
122
+ });
123
+ }, 1500);
124
+
125
+ return () => clearInterval(interval);
126
+ }, []);
127
+
128
+ const animatedStyle = useAnimatedStyle(() => ({
129
+ opacity: interpolate(shimmer.value, [0, 0.5, 1], [0.3, 0.6, 0.3]),
130
+ }));
131
+
132
+ return (
133
+ <Animated.View
134
+ style={[
135
+ StyleSheet.absoluteFill,
136
+ { backgroundColor: colors.backgroundMuted },
137
+ animatedStyle,
138
+ style,
139
+ ]}
140
+ />
141
+ );
142
+ }
143
+
144
+ // ─────────────────────────────────────────────────────────────────────────────
145
+ // Component
146
+ // ─────────────────────────────────────────────────────────────────────────────
147
+
148
+ export function Image({
149
+ source,
150
+ placeholder,
151
+ fallback,
152
+ showSkeleton = true,
153
+ style,
154
+ fadeDuration = 300,
155
+ onLoad,
156
+ onError,
157
+ ...props
158
+ }: ImageProps) {
159
+ const { colors, radius } = useTheme();
160
+ const [isLoading, setIsLoading] = useState(true);
161
+ const [hasError, setHasError] = useState(false);
162
+ const opacity = useSharedValue(0);
163
+
164
+ const handleLoad = useCallback(
165
+ (e: any) => {
166
+ setIsLoading(false);
167
+ opacity.value = withTiming(1, {
168
+ duration: fadeDuration,
169
+ easing: Easing.out(Easing.ease),
170
+ });
171
+ onLoad?.(e);
172
+ },
173
+ [fadeDuration, onLoad]
174
+ );
175
+
176
+ const handleError = useCallback(
177
+ (e: any) => {
178
+ setIsLoading(false);
179
+ setHasError(true);
180
+ onError?.(e);
181
+ },
182
+ [onError]
183
+ );
184
+
185
+ const imageAnimatedStyle = useAnimatedStyle(() => ({
186
+ opacity: opacity.value,
187
+ }));
188
+
189
+ // Extract dimensions from style
190
+ const flatStyle = StyleSheet.flatten(style) || {};
191
+ const { width, height, aspectRatio, borderRadius, ...containerStyle } = flatStyle as ImageStyle & ViewStyle;
192
+
193
+ const imageStyle: ImageStyle = {
194
+ width: width || '100%',
195
+ height: height || '100%',
196
+ aspectRatio,
197
+ borderRadius: borderRadius ?? radius.md,
198
+ };
199
+
200
+ // Error state
201
+ if (hasError) {
202
+ return (
203
+ <View
204
+ style={[
205
+ styles.container,
206
+ {
207
+ width,
208
+ height,
209
+ aspectRatio,
210
+ borderRadius: borderRadius ?? radius.md,
211
+ backgroundColor: colors.backgroundMuted,
212
+ },
213
+ containerStyle,
214
+ ]}
215
+ accessibilityRole="image"
216
+ accessibilityLabel="Image failed to load"
217
+ >
218
+ {fallback || (
219
+ <View style={styles.fallbackContent}>
220
+ <ImageFallbackIcon color={colors.foregroundMuted} />
221
+ </View>
222
+ )}
223
+ </View>
224
+ );
225
+ }
226
+
227
+ return (
228
+ <View
229
+ style={[
230
+ styles.container,
231
+ {
232
+ width,
233
+ height,
234
+ aspectRatio,
235
+ borderRadius: borderRadius ?? radius.md,
236
+ overflow: 'hidden',
237
+ },
238
+ containerStyle,
239
+ ]}
240
+ >
241
+ {/* Skeleton/Placeholder */}
242
+ {isLoading && (
243
+ <>
244
+ {placeholder ? (
245
+ <RNImage
246
+ source={placeholder}
247
+ style={[StyleSheet.absoluteFill, { opacity: 0.5 }]}
248
+ blurRadius={10}
249
+ />
250
+ ) : showSkeleton ? (
251
+ <ImageSkeleton />
252
+ ) : null}
253
+ </>
254
+ )}
255
+
256
+ {/* Main Image */}
257
+ <AnimatedImage
258
+ source={source}
259
+ style={[imageStyle, imageAnimatedStyle]}
260
+ onLoad={handleLoad}
261
+ onError={handleError}
262
+ accessibilityRole="image"
263
+ {...props}
264
+ />
265
+ </View>
266
+ );
267
+ }
268
+
269
+ // ─────────────────────────────────────────────────────────────────────────────
270
+ // Styles
271
+ // ─────────────────────────────────────────────────────────────────────────────
272
+
273
+ const styles = StyleSheet.create({
274
+ container: {
275
+ justifyContent: 'center',
276
+ alignItems: 'center',
277
+ },
278
+ fallbackContent: {
279
+ ...StyleSheet.absoluteFillObject,
280
+ justifyContent: 'center',
281
+ alignItems: 'center',
282
+ },
283
+ });
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Input
3
+ *
4
+ * Text input with label, placeholder, error states, and animated focus.
5
+ * Uses design tokens for consistent styling.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <Input label="Email" placeholder="you@example.com" />
10
+ * <Input label="Password" secureTextEntry error="Invalid password" />
11
+ * <Input size="lg" label="Name" icon={<Icon name="user" />} />
12
+ * ```
13
+ */
14
+
15
+ import React, { forwardRef, useCallback } from 'react';
16
+ import {
17
+ View,
18
+ Text,
19
+ TextInput,
20
+ StyleSheet,
21
+ ViewStyle,
22
+ TextStyle,
23
+ TextInputProps,
24
+ } from 'react-native';
25
+ import Animated, {
26
+ useSharedValue,
27
+ useAnimatedStyle,
28
+ withTiming,
29
+ interpolateColor,
30
+ } from 'react-native-reanimated';
31
+ import { useTheme } from '@nativeui/core';
32
+ import { haptic } from '@nativeui/core';
33
+
34
+ const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
35
+
36
+ export type InputSize = 'sm' | 'md' | 'lg';
37
+
38
+ export interface InputProps extends Omit<TextInputProps, 'style'> {
39
+ /** Label text above input */
40
+ label?: string;
41
+ /** Error message below input */
42
+ error?: string;
43
+ /** Helper text below input (hidden when error is present) */
44
+ helperText?: string;
45
+ /** Size variant */
46
+ size?: InputSize;
47
+ /** Icon element (left side) */
48
+ icon?: React.ReactNode;
49
+ /** Icon element (right side) */
50
+ iconRight?: React.ReactNode;
51
+ /** Container style */
52
+ containerStyle?: ViewStyle;
53
+ /** Input field style */
54
+ style?: TextStyle;
55
+ /** Label style */
56
+ labelStyle?: TextStyle;
57
+ }
58
+
59
+ export const Input = forwardRef<TextInput, InputProps>(
60
+ (
61
+ {
62
+ label,
63
+ error,
64
+ helperText,
65
+ size = 'md',
66
+ icon,
67
+ iconRight,
68
+ containerStyle,
69
+ style,
70
+ labelStyle,
71
+ editable = true,
72
+ onFocus,
73
+ onBlur,
74
+ ...props
75
+ },
76
+ ref
77
+ ) => {
78
+ const { colors, components, timing, spacing } = useTheme();
79
+ const tokens = components.input[size];
80
+ const focusProgress = useSharedValue(0);
81
+
82
+ const hasError = !!error;
83
+ const isDisabled = editable === false;
84
+
85
+ const handleFocus = useCallback(
86
+ (e: any) => {
87
+ focusProgress.value = withTiming(1, timing.default);
88
+ haptic('selection');
89
+ onFocus?.(e);
90
+ },
91
+ [onFocus, timing.default]
92
+ );
93
+
94
+ const handleBlur = useCallback(
95
+ (e: any) => {
96
+ focusProgress.value = withTiming(0, timing.default);
97
+ onBlur?.(e);
98
+ },
99
+ [onBlur, timing.default]
100
+ );
101
+
102
+ const animatedBorderStyle = useAnimatedStyle(() => {
103
+ // Error state takes priority
104
+ if (hasError) {
105
+ return {
106
+ borderColor: colors.destructive,
107
+ borderWidth: 2,
108
+ };
109
+ }
110
+
111
+ return {
112
+ borderColor: interpolateColor(
113
+ focusProgress.value,
114
+ [0, 1],
115
+ [colors.border, colors.primary]
116
+ ),
117
+ borderWidth: focusProgress.value > 0.5 ? 2 : tokens.borderWidth,
118
+ };
119
+ }, [hasError, colors, tokens.borderWidth]);
120
+
121
+ return (
122
+ <View style={[styles.container, containerStyle]}>
123
+ {label && (
124
+ <Text
125
+ style={[
126
+ styles.label,
127
+ {
128
+ fontSize: tokens.labelFontSize,
129
+ marginBottom: spacing[1.5],
130
+ color: hasError ? colors.destructive : colors.foreground,
131
+ },
132
+ labelStyle,
133
+ ]}
134
+ >
135
+ {label}
136
+ </Text>
137
+ )}
138
+
139
+ <View style={styles.inputWrapper}>
140
+ {icon && (
141
+ <View
142
+ style={[
143
+ styles.iconContainer,
144
+ styles.iconLeft,
145
+ { marginLeft: tokens.paddingHorizontal },
146
+ ]}
147
+ >
148
+ {icon}
149
+ </View>
150
+ )}
151
+
152
+ <AnimatedTextInput
153
+ ref={ref}
154
+ style={[
155
+ styles.input,
156
+ {
157
+ minHeight: tokens.height,
158
+ paddingHorizontal: tokens.paddingHorizontal,
159
+ paddingVertical: tokens.paddingVertical,
160
+ borderRadius: tokens.borderRadius,
161
+ fontSize: tokens.fontSize,
162
+ backgroundColor: isDisabled ? colors.backgroundMuted : colors.background,
163
+ color: isDisabled ? colors.foregroundMuted : colors.foreground,
164
+ paddingLeft: icon ? tokens.paddingHorizontal + tokens.iconSize + spacing[2] : tokens.paddingHorizontal,
165
+ paddingRight: iconRight ? tokens.paddingHorizontal + tokens.iconSize + spacing[2] : tokens.paddingHorizontal,
166
+ },
167
+ animatedBorderStyle,
168
+ style,
169
+ ]}
170
+ placeholderTextColor={colors.foregroundMuted}
171
+ editable={editable}
172
+ onFocus={handleFocus}
173
+ onBlur={handleBlur}
174
+ accessibilityLabel={label}
175
+ accessibilityState={{ disabled: isDisabled }}
176
+ {...props}
177
+ />
178
+
179
+ {iconRight && (
180
+ <View
181
+ style={[
182
+ styles.iconContainer,
183
+ styles.iconRight,
184
+ { marginRight: tokens.paddingHorizontal },
185
+ ]}
186
+ >
187
+ {iconRight}
188
+ </View>
189
+ )}
190
+ </View>
191
+
192
+ {(error || helperText) && (
193
+ <Text
194
+ style={[
195
+ styles.helperText,
196
+ {
197
+ fontSize: tokens.helperFontSize,
198
+ marginTop: spacing[1],
199
+ color: hasError ? colors.destructive : colors.foregroundMuted,
200
+ },
201
+ ]}
202
+ >
203
+ {error || helperText}
204
+ </Text>
205
+ )}
206
+ </View>
207
+ );
208
+ }
209
+ );
210
+
211
+ Input.displayName = 'Input';
212
+
213
+ const styles = StyleSheet.create({
214
+ container: {
215
+ width: '100%',
216
+ },
217
+ inputWrapper: {
218
+ position: 'relative',
219
+ justifyContent: 'center',
220
+ },
221
+ label: {
222
+ fontWeight: '500',
223
+ },
224
+ input: {
225
+ // Dynamic styles applied inline
226
+ },
227
+ iconContainer: {
228
+ position: 'absolute',
229
+ zIndex: 1,
230
+ justifyContent: 'center',
231
+ alignItems: 'center',
232
+ },
233
+ iconLeft: {
234
+ left: 0,
235
+ },
236
+ iconRight: {
237
+ right: 0,
238
+ },
239
+ helperText: {
240
+ // Dynamic styles applied inline
241
+ },
242
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Label
3
+ *
4
+ * A text label component for form inputs with consistent styling.
5
+ * Supports required indicator and disabled state.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <Label>Email</Label>
10
+ * <Label required>Password</Label>
11
+ * <Label disabled>Disabled Field</Label>
12
+ * <Label size="lg" htmlFor="name">Name</Label>
13
+ * ```
14
+ */
15
+
16
+ import React from 'react';
17
+ import {
18
+ Text,
19
+ StyleSheet,
20
+ TextStyle,
21
+ TextProps,
22
+ } from 'react-native';
23
+ import { useTheme } from '@nativeui/core';
24
+
25
+ export type LabelSize = 'sm' | 'md' | 'lg';
26
+
27
+ export interface LabelProps extends Omit<TextProps, 'style'> {
28
+ /** Label content */
29
+ children: React.ReactNode;
30
+ /** Size variant */
31
+ size?: LabelSize;
32
+ /** Show required asterisk */
33
+ required?: boolean;
34
+ /** Disabled state (muted color) */
35
+ disabled?: boolean;
36
+ /** Error state (red color) */
37
+ error?: boolean;
38
+ /** Associated input id (for web compatibility) */
39
+ htmlFor?: string;
40
+ /** Additional text styles */
41
+ style?: TextStyle;
42
+ }
43
+
44
+ const sizeTokens: Record<LabelSize, { fontSize: number; lineHeight: number }> = {
45
+ sm: { fontSize: 12, lineHeight: 16 },
46
+ md: { fontSize: 14, lineHeight: 20 },
47
+ lg: { fontSize: 16, lineHeight: 24 },
48
+ };
49
+
50
+ export function Label({
51
+ children,
52
+ size = 'md',
53
+ required = false,
54
+ disabled = false,
55
+ error = false,
56
+ style,
57
+ ...props
58
+ }: LabelProps) {
59
+ const { colors } = useTheme();
60
+ const tokens = sizeTokens[size];
61
+
62
+ const textColor = error
63
+ ? colors.destructive
64
+ : disabled
65
+ ? colors.foregroundMuted
66
+ : colors.foreground;
67
+
68
+ return (
69
+ <Text
70
+ style={[
71
+ styles.label,
72
+ {
73
+ fontSize: tokens.fontSize,
74
+ lineHeight: tokens.lineHeight,
75
+ color: textColor,
76
+ },
77
+ style,
78
+ ]}
79
+ accessibilityRole="text"
80
+ {...props}
81
+ >
82
+ {children}
83
+ {required && (
84
+ <Text style={[styles.required, { color: colors.destructive }]}>
85
+ {' *'}
86
+ </Text>
87
+ )}
88
+ </Text>
89
+ );
90
+ }
91
+
92
+ const styles = StyleSheet.create({
93
+ label: {
94
+ fontWeight: '500',
95
+ },
96
+ required: {
97
+ fontWeight: '400',
98
+ },
99
+ });