@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,231 @@
1
+ /**
2
+ * Pull to Refresh
3
+ *
4
+ * A pull-to-refresh wrapper component with custom animated indicator.
5
+ * Works with ScrollView and FlatList.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const [refreshing, setRefreshing] = useState(false);
10
+ *
11
+ * const onRefresh = async () => {
12
+ * setRefreshing(true);
13
+ * await fetchData();
14
+ * setRefreshing(false);
15
+ * };
16
+ *
17
+ * <PullToRefresh refreshing={refreshing} onRefresh={onRefresh}>
18
+ * <ScrollView>
19
+ * {content}
20
+ * </ScrollView>
21
+ * </PullToRefresh>
22
+ * ```
23
+ */
24
+
25
+ import React, { useCallback } from 'react';
26
+ import {
27
+ View,
28
+ RefreshControl,
29
+ StyleSheet,
30
+ ViewStyle,
31
+ Platform,
32
+ } from 'react-native';
33
+ import Animated, {
34
+ useSharedValue,
35
+ useAnimatedStyle,
36
+ withRepeat,
37
+ withTiming,
38
+ Easing,
39
+ cancelAnimation,
40
+ } from 'react-native-reanimated';
41
+ import { useTheme } from '@nativeui/core';
42
+
43
+ export interface PullToRefreshProps {
44
+ /** Whether currently refreshing */
45
+ refreshing: boolean;
46
+ /** Callback when refresh is triggered */
47
+ onRefresh: () => void;
48
+ /** Children (ScrollView, FlatList, etc.) */
49
+ children: React.ReactElement;
50
+ /** Custom refresh indicator color */
51
+ indicatorColor?: string;
52
+ /** Custom background color for iOS */
53
+ backgroundColor?: string;
54
+ /** Progress view offset for Android */
55
+ progressViewOffset?: number;
56
+ /** Container style */
57
+ style?: ViewStyle;
58
+ }
59
+
60
+ export function PullToRefresh({
61
+ refreshing,
62
+ onRefresh,
63
+ children,
64
+ indicatorColor,
65
+ backgroundColor,
66
+ progressViewOffset = 0,
67
+ style,
68
+ }: PullToRefreshProps) {
69
+ const { colors } = useTheme();
70
+
71
+ const tintColor = indicatorColor ?? colors.primary;
72
+ const bgColor = backgroundColor ?? colors.backgroundSubtle;
73
+
74
+ // Clone child with RefreshControl
75
+ const childWithRefreshControl = React.cloneElement(
76
+ children as React.ReactElement<{ refreshControl?: React.ReactElement }>,
77
+ {
78
+ refreshControl: (
79
+ <RefreshControl
80
+ refreshing={refreshing}
81
+ onRefresh={onRefresh}
82
+ tintColor={tintColor}
83
+ colors={[tintColor]} // Android
84
+ progressBackgroundColor={bgColor} // Android
85
+ progressViewOffset={progressViewOffset}
86
+ style={Platform.OS === 'ios' ? { backgroundColor: bgColor } : undefined}
87
+ />
88
+ ),
89
+ }
90
+ );
91
+
92
+ return (
93
+ <View style={[styles.container, style]}>
94
+ {childWithRefreshControl}
95
+ </View>
96
+ );
97
+ }
98
+
99
+ /**
100
+ * Custom Refresh Indicator
101
+ *
102
+ * A custom animated spinner for use with pull-to-refresh.
103
+ * Can be used standalone or with custom refresh implementations.
104
+ */
105
+ export interface RefreshIndicatorProps {
106
+ /** Whether animating */
107
+ animating?: boolean;
108
+ /** Size of the indicator */
109
+ size?: number;
110
+ /** Color of the indicator */
111
+ color?: string;
112
+ /** Container style */
113
+ style?: ViewStyle;
114
+ }
115
+
116
+ export function RefreshIndicator({
117
+ animating = true,
118
+ size = 24,
119
+ color,
120
+ style,
121
+ }: RefreshIndicatorProps) {
122
+ const { colors } = useTheme();
123
+ const rotation = useSharedValue(0);
124
+
125
+ const indicatorColor = color ?? colors.primary;
126
+
127
+ React.useEffect(() => {
128
+ if (animating) {
129
+ rotation.value = withRepeat(
130
+ withTiming(360, { duration: 1000, easing: Easing.linear }),
131
+ -1,
132
+ false
133
+ );
134
+ } else {
135
+ cancelAnimation(rotation);
136
+ rotation.value = 0;
137
+ }
138
+ }, [animating]);
139
+
140
+ const animatedStyle = useAnimatedStyle(() => ({
141
+ transform: [{ rotate: `${rotation.value}deg` }],
142
+ }));
143
+
144
+ return (
145
+ <View style={[styles.indicatorContainer, { width: size, height: size }, style]}>
146
+ <Animated.View style={[styles.indicator, animatedStyle]}>
147
+ <View
148
+ style={[
149
+ styles.indicatorArc,
150
+ {
151
+ width: size,
152
+ height: size,
153
+ borderRadius: size / 2,
154
+ borderWidth: size / 8,
155
+ borderColor: indicatorColor,
156
+ borderTopColor: 'transparent',
157
+ borderRightColor: 'transparent',
158
+ },
159
+ ]}
160
+ />
161
+ </Animated.View>
162
+ </View>
163
+ );
164
+ }
165
+
166
+ /**
167
+ * RefreshContainer
168
+ *
169
+ * A container component that shows a custom refresh indicator
170
+ * at the top when pulling down. For more control than PullToRefresh.
171
+ */
172
+ export interface RefreshContainerProps {
173
+ /** Whether currently refreshing */
174
+ refreshing: boolean;
175
+ /** Callback when refresh is triggered */
176
+ onRefresh: () => void;
177
+ /** Pull threshold to trigger refresh */
178
+ threshold?: number;
179
+ /** Children */
180
+ children: React.ReactNode;
181
+ /** Container style */
182
+ style?: ViewStyle;
183
+ }
184
+
185
+ export function RefreshContainer({
186
+ refreshing,
187
+ onRefresh,
188
+ threshold = 80,
189
+ children,
190
+ style,
191
+ }: RefreshContainerProps) {
192
+ const { colors, spacing } = useTheme();
193
+
194
+ return (
195
+ <View style={[styles.refreshContainer, style]}>
196
+ {refreshing && (
197
+ <View
198
+ style={[
199
+ styles.refreshHeader,
200
+ {
201
+ backgroundColor: colors.backgroundSubtle,
202
+ paddingVertical: spacing[4],
203
+ },
204
+ ]}
205
+ >
206
+ <RefreshIndicator animating={refreshing} />
207
+ </View>
208
+ )}
209
+ {children}
210
+ </View>
211
+ );
212
+ }
213
+
214
+ const styles = StyleSheet.create({
215
+ container: {
216
+ flex: 1,
217
+ },
218
+ indicatorContainer: {
219
+ alignItems: 'center',
220
+ justifyContent: 'center',
221
+ },
222
+ indicator: {},
223
+ indicatorArc: {},
224
+ refreshContainer: {
225
+ flex: 1,
226
+ },
227
+ refreshHeader: {
228
+ alignItems: 'center',
229
+ justifyContent: 'center',
230
+ },
231
+ });
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Radio Group
3
+ *
4
+ * A set of radio buttons where only one can be selected.
5
+ * Features animated selection indicator, haptic feedback,
6
+ * dark mode support, and accessibility.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const [value, setValue] = useState('option1');
11
+ * <RadioGroup value={value} onValueChange={setValue}>
12
+ * <RadioGroupItem value="option1" label="Option 1" />
13
+ * <RadioGroupItem value="option2" label="Option 2" />
14
+ * <RadioGroupItem value="option3" label="Option 3" />
15
+ * </RadioGroup>
16
+ * ```
17
+ */
18
+
19
+ import React, { createContext, useContext, useEffect, useCallback } from 'react';
20
+ import {
21
+ Pressable,
22
+ View,
23
+ Text,
24
+ StyleSheet,
25
+ ViewStyle,
26
+ AccessibilityInfo,
27
+ } from 'react-native';
28
+ import Animated, {
29
+ useSharedValue,
30
+ useAnimatedStyle,
31
+ withSpring,
32
+ interpolate,
33
+ interpolateColor,
34
+ Extrapolation,
35
+ } from 'react-native-reanimated';
36
+ import { useTheme, ThemeColors, springs as themeSpringPresets } from '@nativeui/core';
37
+ import { haptic } from '@nativeui/core';
38
+
39
+ // Context for sharing state between RadioGroup and RadioGroupItem
40
+ interface RadioGroupContextValue {
41
+ value: string;
42
+ onValueChange: (value: string) => void;
43
+ disabled: boolean;
44
+ size: 'sm' | 'md' | 'lg';
45
+ colors: ThemeColors;
46
+ springs: typeof themeSpringPresets;
47
+ }
48
+
49
+ const RadioGroupContext = createContext<RadioGroupContextValue | null>(null);
50
+
51
+ function useRadioGroup() {
52
+ const context = useContext(RadioGroupContext);
53
+ if (!context) {
54
+ throw new Error('RadioGroupItem must be used within a RadioGroup');
55
+ }
56
+ return context;
57
+ }
58
+
59
+ export interface RadioGroupProps {
60
+ /** Currently selected value */
61
+ value: string;
62
+ /** Callback when selection changes */
63
+ onValueChange: (value: string) => void;
64
+ /** Disable all items */
65
+ disabled?: boolean;
66
+ /** Size variant for all items */
67
+ size?: 'sm' | 'md' | 'lg';
68
+ /** Radio items */
69
+ children: React.ReactNode;
70
+ /** Additional container styles */
71
+ style?: ViewStyle;
72
+ }
73
+
74
+ export function RadioGroup({
75
+ value,
76
+ onValueChange,
77
+ disabled = false,
78
+ size = 'md',
79
+ children,
80
+ style,
81
+ }: RadioGroupProps) {
82
+ const { colors, springs } = useTheme();
83
+
84
+ return (
85
+ <RadioGroupContext.Provider
86
+ value={{ value, onValueChange, disabled, size, colors, springs }}
87
+ >
88
+ <View
89
+ style={[styles.group, style]}
90
+ accessibilityRole="radiogroup"
91
+ >
92
+ {children}
93
+ </View>
94
+ </RadioGroupContext.Provider>
95
+ );
96
+ }
97
+
98
+ export interface RadioGroupItemProps {
99
+ /** Value for this radio option */
100
+ value: string;
101
+ /** Label text */
102
+ label: string;
103
+ /** Description text (optional) */
104
+ description?: string;
105
+ /** Disable this specific item */
106
+ disabled?: boolean;
107
+ /** Additional accessibility label */
108
+ accessibilityLabel?: string;
109
+ /** Additional container styles */
110
+ style?: ViewStyle;
111
+ }
112
+
113
+ export function RadioGroupItem({
114
+ value,
115
+ label,
116
+ description,
117
+ disabled: itemDisabled = false,
118
+ accessibilityLabel,
119
+ style,
120
+ }: RadioGroupItemProps) {
121
+ const context = useRadioGroup();
122
+ const isSelected = context.value === value;
123
+ const disabled = context.disabled || itemDisabled;
124
+ const { colors, springs } = context;
125
+
126
+ const progress = useSharedValue(isSelected ? 1 : 0);
127
+ const [reduceMotion, setReduceMotion] = React.useState(false);
128
+
129
+ // Check for reduce motion preference
130
+ useEffect(() => {
131
+ AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
132
+ const subscription = AccessibilityInfo.addEventListener(
133
+ 'reduceMotionChanged',
134
+ setReduceMotion
135
+ );
136
+ return () => subscription.remove();
137
+ }, []);
138
+
139
+ // Animate on selection change
140
+ useEffect(() => {
141
+ const target = isSelected ? 1 : 0;
142
+ if (reduceMotion) {
143
+ progress.value = target;
144
+ } else {
145
+ progress.value = withSpring(target, springs.snappy);
146
+ }
147
+ }, [isSelected, reduceMotion, springs.snappy]);
148
+
149
+ const handlePress = useCallback(() => {
150
+ if (disabled) return;
151
+ haptic('selection');
152
+ context.onValueChange(value);
153
+ }, [disabled, context.onValueChange, value]);
154
+
155
+ const sizeStyles = sizes[context.size];
156
+
157
+ const outerAnimatedStyle = useAnimatedStyle(() => ({
158
+ borderColor: interpolateColor(
159
+ progress.value,
160
+ [0, 1],
161
+ [colors.border, colors.primary]
162
+ ),
163
+ }));
164
+
165
+ const innerAnimatedStyle = useAnimatedStyle(() => ({
166
+ opacity: progress.value,
167
+ transform: [
168
+ {
169
+ scale: interpolate(
170
+ progress.value,
171
+ [0, 1],
172
+ [0, 1],
173
+ Extrapolation.CLAMP
174
+ ),
175
+ },
176
+ ],
177
+ }));
178
+
179
+ return (
180
+ <Pressable
181
+ onPress={handlePress}
182
+ disabled={disabled}
183
+ style={({ pressed }) => [
184
+ styles.item,
185
+ pressed && !disabled && styles.pressed,
186
+ style,
187
+ ]}
188
+ accessible
189
+ accessibilityRole="radio"
190
+ accessibilityLabel={accessibilityLabel ?? label}
191
+ accessibilityState={{
192
+ checked: isSelected,
193
+ disabled,
194
+ }}
195
+ >
196
+ <Animated.View
197
+ style={[
198
+ styles.outer,
199
+ sizeStyles.outer,
200
+ outerAnimatedStyle,
201
+ disabled && styles.disabled,
202
+ ]}
203
+ >
204
+ <Animated.View
205
+ style={[
206
+ styles.inner,
207
+ sizeStyles.inner,
208
+ { backgroundColor: colors.primary },
209
+ innerAnimatedStyle,
210
+ ]}
211
+ />
212
+ </Animated.View>
213
+
214
+ <View style={styles.textContainer}>
215
+ <Text
216
+ style={[
217
+ styles.label,
218
+ sizeStyles.label,
219
+ { color: disabled ? colors.foregroundMuted : colors.foreground },
220
+ ]}
221
+ >
222
+ {label}
223
+ </Text>
224
+ {description && (
225
+ <Text
226
+ style={[
227
+ styles.description,
228
+ sizeStyles.description,
229
+ { color: disabled ? colors.foregroundMuted : colors.foregroundMuted },
230
+ ]}
231
+ >
232
+ {description}
233
+ </Text>
234
+ )}
235
+ </View>
236
+ </Pressable>
237
+ );
238
+ }
239
+
240
+ // Size-specific styles using StyleSheet
241
+ const sizes = {
242
+ sm: StyleSheet.create({
243
+ outer: { width: 16, height: 16, borderRadius: 8 },
244
+ inner: { width: 8, height: 8, borderRadius: 4 },
245
+ label: { fontSize: 14 },
246
+ description: { fontSize: 12 },
247
+ }),
248
+ md: StyleSheet.create({
249
+ outer: { width: 20, height: 20, borderRadius: 10 },
250
+ inner: { width: 10, height: 10, borderRadius: 5 },
251
+ label: { fontSize: 16 },
252
+ description: { fontSize: 14 },
253
+ }),
254
+ lg: StyleSheet.create({
255
+ outer: { width: 24, height: 24, borderRadius: 12 },
256
+ inner: { width: 12, height: 12, borderRadius: 6 },
257
+ label: { fontSize: 18 },
258
+ description: { fontSize: 16 },
259
+ }),
260
+ };
261
+
262
+ const styles = StyleSheet.create({
263
+ group: {
264
+ gap: 12,
265
+ },
266
+ item: {
267
+ flexDirection: 'row',
268
+ alignItems: 'flex-start',
269
+ gap: 10,
270
+ },
271
+ pressed: {
272
+ opacity: 0.7,
273
+ },
274
+ outer: {
275
+ borderWidth: 2,
276
+ alignItems: 'center',
277
+ justifyContent: 'center',
278
+ marginTop: 2,
279
+ },
280
+ inner: {},
281
+ disabled: {
282
+ opacity: 0.5,
283
+ },
284
+ textContainer: {
285
+ flex: 1,
286
+ gap: 2,
287
+ },
288
+ label: {
289
+ fontWeight: '500',
290
+ },
291
+ description: {
292
+ fontWeight: '400',
293
+ },
294
+ });