@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,281 @@
1
+ /**
2
+ * SearchInput
3
+ *
4
+ * A search input with icon, clear button, and loading state.
5
+ * Commonly used in list headers and search screens.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Basic usage
10
+ * <SearchInput
11
+ * value={query}
12
+ * onChangeText={setQuery}
13
+ * placeholder="Search..."
14
+ * />
15
+ *
16
+ * // With loading state
17
+ * <SearchInput
18
+ * value={query}
19
+ * onChangeText={setQuery}
20
+ * loading={isSearching}
21
+ * />
22
+ *
23
+ * // Uncontrolled
24
+ * <SearchInput
25
+ * defaultValue=""
26
+ * onChangeText={(text) => debouncedSearch(text)}
27
+ * />
28
+ * ```
29
+ */
30
+
31
+ import React, { useState, useRef, useCallback } from 'react';
32
+ import {
33
+ View,
34
+ TextInput,
35
+ Pressable,
36
+ StyleSheet,
37
+ ViewStyle,
38
+ TextStyle,
39
+ ActivityIndicator,
40
+ TextInputProps,
41
+ } from 'react-native';
42
+ import Animated, {
43
+ useSharedValue,
44
+ useAnimatedStyle,
45
+ withSpring,
46
+ withTiming,
47
+ interpolate,
48
+ } from 'react-native-reanimated';
49
+ import Svg, { Path, Circle } from 'react-native-svg';
50
+ import { useTheme } from '@nativeui/core';
51
+ import { haptic } from '@nativeui/core';
52
+
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ // Types
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+
57
+ export interface SearchInputProps extends Omit<TextInputProps, 'style'> {
58
+ /** Controlled value */
59
+ value?: string;
60
+ /** Default value for uncontrolled mode */
61
+ defaultValue?: string;
62
+ /** Callback when text changes */
63
+ onChangeText?: (text: string) => void;
64
+ /** Placeholder text */
65
+ placeholder?: string;
66
+ /** Show loading spinner */
67
+ loading?: boolean;
68
+ /** Show clear button when there's text */
69
+ showClearButton?: boolean;
70
+ /** Called when clear button is pressed */
71
+ onClear?: () => void;
72
+ /** Called when search is submitted */
73
+ onSubmit?: (text: string) => void;
74
+ /** Container style */
75
+ style?: ViewStyle;
76
+ /** Input style */
77
+ inputStyle?: TextStyle;
78
+ /** Auto focus on mount */
79
+ autoFocus?: boolean;
80
+ }
81
+
82
+ // ─────────────────────────────────────────────────────────────────────────────
83
+ // Icons
84
+ // ─────────────────────────────────────────────────────────────────────────────
85
+
86
+ function SearchIcon({ color, size = 20 }: { color: string; size?: number }) {
87
+ return (
88
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
89
+ <Circle cx="11" cy="11" r="8" stroke={color} strokeWidth="2" />
90
+ <Path
91
+ d="M21 21l-4.35-4.35"
92
+ stroke={color}
93
+ strokeWidth="2"
94
+ strokeLinecap="round"
95
+ />
96
+ </Svg>
97
+ );
98
+ }
99
+
100
+ function ClearIcon({ color, size = 18 }: { color: string; size?: number }) {
101
+ return (
102
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
103
+ <Circle cx="12" cy="12" r="10" fill={color} fillOpacity={0.1} />
104
+ <Path
105
+ d="M15 9l-6 6M9 9l6 6"
106
+ stroke={color}
107
+ strokeWidth="2"
108
+ strokeLinecap="round"
109
+ />
110
+ </Svg>
111
+ );
112
+ }
113
+
114
+ // ─────────────────────────────────────────────────────────────────────────────
115
+ // Component
116
+ // ─────────────────────────────────────────────────────────────────────────────
117
+
118
+ export function SearchInput({
119
+ value: controlledValue,
120
+ defaultValue = '',
121
+ onChangeText,
122
+ placeholder = 'Search...',
123
+ loading = false,
124
+ showClearButton = true,
125
+ onClear,
126
+ onSubmit,
127
+ style,
128
+ inputStyle,
129
+ autoFocus = false,
130
+ ...props
131
+ }: SearchInputProps) {
132
+ const { colors, spacing, radius, springs } = useTheme();
133
+ const inputRef = useRef<TextInput>(null);
134
+
135
+ // Controlled/uncontrolled state
136
+ const [internalValue, setInternalValue] = useState(defaultValue);
137
+ const isControlled = controlledValue !== undefined;
138
+ const value = isControlled ? controlledValue : internalValue;
139
+
140
+ // Focus animation
141
+ const isFocused = useSharedValue(0);
142
+
143
+ const handleChangeText = useCallback(
144
+ (text: string) => {
145
+ if (!isControlled) {
146
+ setInternalValue(text);
147
+ }
148
+ onChangeText?.(text);
149
+ },
150
+ [isControlled, onChangeText]
151
+ );
152
+
153
+ const handleClear = useCallback(() => {
154
+ haptic('light');
155
+ handleChangeText('');
156
+ onClear?.();
157
+ inputRef.current?.focus();
158
+ }, [handleChangeText, onClear]);
159
+
160
+ const handleSubmit = useCallback(() => {
161
+ haptic('light');
162
+ onSubmit?.(value);
163
+ }, [onSubmit, value]);
164
+
165
+ const handleFocus = useCallback(() => {
166
+ isFocused.value = withSpring(1, springs.snappy);
167
+ }, [springs.snappy]);
168
+
169
+ const handleBlur = useCallback(() => {
170
+ isFocused.value = withSpring(0, springs.snappy);
171
+ }, [springs.snappy]);
172
+
173
+ const containerAnimatedStyle = useAnimatedStyle(() => ({
174
+ borderColor: interpolate(
175
+ isFocused.value,
176
+ [0, 1],
177
+ [0, 1]
178
+ ) === 1
179
+ ? colors.primary
180
+ : colors.border,
181
+ transform: [
182
+ {
183
+ scale: interpolate(isFocused.value, [0, 1], [1, 1.01]),
184
+ },
185
+ ],
186
+ }));
187
+
188
+ const showClear = showClearButton && value.length > 0 && !loading;
189
+
190
+ return (
191
+ <Animated.View
192
+ style={[
193
+ styles.container,
194
+ {
195
+ backgroundColor: colors.backgroundMuted,
196
+ borderRadius: radius.lg,
197
+ borderWidth: 1,
198
+ borderColor: colors.border,
199
+ paddingHorizontal: spacing[3],
200
+ height: 44,
201
+ },
202
+ containerAnimatedStyle,
203
+ style,
204
+ ]}
205
+ >
206
+ {/* Search Icon */}
207
+ <View style={[styles.iconContainer, { marginRight: spacing[2] }]}>
208
+ <SearchIcon color={colors.foregroundMuted} />
209
+ </View>
210
+
211
+ {/* Input */}
212
+ <TextInput
213
+ ref={inputRef}
214
+ style={[
215
+ styles.input,
216
+ {
217
+ color: colors.foreground,
218
+ fontSize: 16,
219
+ },
220
+ inputStyle,
221
+ ]}
222
+ value={value}
223
+ onChangeText={handleChangeText}
224
+ placeholder={placeholder}
225
+ placeholderTextColor={colors.foregroundMuted}
226
+ onFocus={handleFocus}
227
+ onBlur={handleBlur}
228
+ onSubmitEditing={handleSubmit}
229
+ returnKeyType="search"
230
+ autoCapitalize="none"
231
+ autoCorrect={false}
232
+ autoFocus={autoFocus}
233
+ accessibilityRole="search"
234
+ accessibilityLabel={placeholder}
235
+ {...props}
236
+ />
237
+
238
+ {/* Loading / Clear Button */}
239
+ <View style={[styles.rightContainer, { marginLeft: spacing[2] }]}>
240
+ {loading && (
241
+ <ActivityIndicator size="small" color={colors.foregroundMuted} />
242
+ )}
243
+ {showClear && (
244
+ <Pressable
245
+ onPress={handleClear}
246
+ hitSlop={8}
247
+ accessibilityRole="button"
248
+ accessibilityLabel="Clear search"
249
+ >
250
+ <ClearIcon color={colors.foregroundMuted} />
251
+ </Pressable>
252
+ )}
253
+ </View>
254
+ </Animated.View>
255
+ );
256
+ }
257
+
258
+ // ─────────────────────────────────────────────────────────────────────────────
259
+ // Styles
260
+ // ─────────────────────────────────────────────────────────────────────────────
261
+
262
+ const styles = StyleSheet.create({
263
+ container: {
264
+ flexDirection: 'row',
265
+ alignItems: 'center',
266
+ },
267
+ iconContainer: {
268
+ flexShrink: 0,
269
+ },
270
+ input: {
271
+ flex: 1,
272
+ padding: 0,
273
+ margin: 0,
274
+ },
275
+ rightContainer: {
276
+ flexShrink: 0,
277
+ width: 24,
278
+ alignItems: 'center',
279
+ justifyContent: 'center',
280
+ },
281
+ });
@@ -0,0 +1,258 @@
1
+ /**
2
+ * SectionHeader
3
+ *
4
+ * A section header with title and optional "See All" action.
5
+ * Commonly used above horizontal lists and content sections.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Basic usage
10
+ * <SectionHeader title="Popular Items" />
11
+ *
12
+ * // With "See All" action
13
+ * <SectionHeader
14
+ * title="Recent Orders"
15
+ * actionText="View All"
16
+ * onAction={() => navigation.navigate('Orders')}
17
+ * />
18
+ *
19
+ * // With subtitle
20
+ * <SectionHeader
21
+ * title="Recommendations"
22
+ * subtitle="Based on your preferences"
23
+ * onAction={handleSeeAll}
24
+ * />
25
+ *
26
+ * // Custom styling
27
+ * <SectionHeader
28
+ * title="Featured"
29
+ * size="lg"
30
+ * style={{ paddingHorizontal: 24 }}
31
+ * />
32
+ * ```
33
+ */
34
+
35
+ import React, { useCallback } from 'react';
36
+ import {
37
+ View,
38
+ Text,
39
+ Pressable,
40
+ StyleSheet,
41
+ ViewStyle,
42
+ TextStyle,
43
+ } from 'react-native';
44
+ import Animated, {
45
+ useSharedValue,
46
+ useAnimatedStyle,
47
+ withSpring,
48
+ } from 'react-native-reanimated';
49
+ import Svg, { Path } from 'react-native-svg';
50
+ import { useTheme } from '@nativeui/core';
51
+ import { haptic } from '@nativeui/core';
52
+
53
+ const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
54
+
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+ // Types
57
+ // ─────────────────────────────────────────────────────────────────────────────
58
+
59
+ export type SectionHeaderSize = 'sm' | 'md' | 'lg';
60
+
61
+ export interface SectionHeaderProps {
62
+ /** Section title */
63
+ title: string;
64
+ /** Optional subtitle below title */
65
+ subtitle?: string;
66
+ /** Text for the action button */
67
+ actionText?: string;
68
+ /** Show arrow icon next to action text */
69
+ showActionArrow?: boolean;
70
+ /** Action button press handler */
71
+ onAction?: () => void;
72
+ /** Size variant */
73
+ size?: SectionHeaderSize;
74
+ /** Container style */
75
+ style?: ViewStyle;
76
+ /** Title text style */
77
+ titleStyle?: TextStyle;
78
+ /** Subtitle text style */
79
+ subtitleStyle?: TextStyle;
80
+ /** Action text style */
81
+ actionStyle?: TextStyle;
82
+ }
83
+
84
+ // ─────────────────────────────────────────────────────────────────────────────
85
+ // Size Configuration
86
+ // ─────────────────────────────────────────────────────────────────────────────
87
+
88
+ const sizeConfig = {
89
+ sm: { titleSize: 'sm' as const, subtitleSize: 'xs' as const, actionSize: 'xs' as const },
90
+ md: { titleSize: 'lg' as const, subtitleSize: 'sm' as const, actionSize: 'sm' as const },
91
+ lg: { titleSize: 'xl' as const, subtitleSize: 'base' as const, actionSize: 'base' as const },
92
+ };
93
+
94
+ // ─────────────────────────────────────────────────────────────────────────────
95
+ // Icons
96
+ // ─────────────────────────────────────────────────────────────────────────────
97
+
98
+ function ChevronRightIcon({ color, size = 16 }: { color: string; size?: number }) {
99
+ return (
100
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
101
+ <Path
102
+ d="M9 18l6-6-6-6"
103
+ stroke={color}
104
+ strokeWidth="2"
105
+ strokeLinecap="round"
106
+ strokeLinejoin="round"
107
+ />
108
+ </Svg>
109
+ );
110
+ }
111
+
112
+ // ─────────────────────────────────────────────────────────────────────────────
113
+ // Component
114
+ // ─────────────────────────────────────────────────────────────────────────────
115
+
116
+ export function SectionHeader({
117
+ title,
118
+ subtitle,
119
+ actionText = 'See All',
120
+ showActionArrow = true,
121
+ onAction,
122
+ size = 'md',
123
+ style,
124
+ titleStyle,
125
+ subtitleStyle,
126
+ actionStyle,
127
+ }: SectionHeaderProps) {
128
+ const { colors, spacing, fontSize, fontWeight, springs } = useTheme();
129
+ const config = sizeConfig[size];
130
+ const actionScale = useSharedValue(1);
131
+
132
+ const handleActionPressIn = useCallback(() => {
133
+ actionScale.value = withSpring(0.95, springs.snappy);
134
+ }, [springs.snappy]);
135
+
136
+ const handleActionPressOut = useCallback(() => {
137
+ actionScale.value = withSpring(1, springs.snappy);
138
+ }, [springs.snappy]);
139
+
140
+ const handleAction = useCallback(() => {
141
+ haptic('light');
142
+ onAction?.();
143
+ }, [onAction]);
144
+
145
+ const actionAnimatedStyle = useAnimatedStyle(() => ({
146
+ transform: [{ scale: actionScale.value }],
147
+ }));
148
+
149
+ return (
150
+ <View
151
+ style={[
152
+ styles.container,
153
+ {
154
+ paddingHorizontal: spacing[4],
155
+ paddingVertical: spacing[2],
156
+ },
157
+ style,
158
+ ]}
159
+ >
160
+ {/* Title & Subtitle */}
161
+ <View style={styles.titleContainer}>
162
+ <Text
163
+ style={[
164
+ styles.title,
165
+ {
166
+ color: colors.foreground,
167
+ fontSize: fontSize[config.titleSize],
168
+ fontWeight: fontWeight.semibold,
169
+ },
170
+ titleStyle,
171
+ ]}
172
+ numberOfLines={1}
173
+ >
174
+ {title}
175
+ </Text>
176
+ {subtitle && (
177
+ <Text
178
+ style={[
179
+ styles.subtitle,
180
+ {
181
+ color: colors.foregroundMuted,
182
+ fontSize: fontSize[config.subtitleSize],
183
+ marginTop: spacing[0.5],
184
+ },
185
+ subtitleStyle,
186
+ ]}
187
+ numberOfLines={1}
188
+ >
189
+ {subtitle}
190
+ </Text>
191
+ )}
192
+ </View>
193
+
194
+ {/* Action Button */}
195
+ {onAction && (
196
+ <AnimatedPressable
197
+ style={[styles.actionButton, actionAnimatedStyle]}
198
+ onPressIn={handleActionPressIn}
199
+ onPressOut={handleActionPressOut}
200
+ onPress={handleAction}
201
+ hitSlop={8}
202
+ accessibilityRole="button"
203
+ accessibilityLabel={actionText}
204
+ >
205
+ <Text
206
+ style={[
207
+ styles.actionText,
208
+ {
209
+ color: colors.primary,
210
+ fontSize: fontSize[config.actionSize],
211
+ fontWeight: fontWeight.medium,
212
+ },
213
+ actionStyle,
214
+ ]}
215
+ >
216
+ {actionText}
217
+ </Text>
218
+ {showActionArrow && (
219
+ <ChevronRightIcon
220
+ color={colors.primary}
221
+ size={fontSize[config.actionSize]}
222
+ />
223
+ )}
224
+ </AnimatedPressable>
225
+ )}
226
+ </View>
227
+ );
228
+ }
229
+
230
+ // ─────────────────────────────────────────────────────────────────────────────
231
+ // Styles
232
+ // ─────────────────────────────────────────────────────────────────────────────
233
+
234
+ const styles = StyleSheet.create({
235
+ container: {
236
+ flexDirection: 'row',
237
+ alignItems: 'center',
238
+ justifyContent: 'space-between',
239
+ },
240
+ titleContainer: {
241
+ flex: 1,
242
+ marginRight: 16,
243
+ },
244
+ title: {
245
+ // Dynamic styles applied inline
246
+ },
247
+ subtitle: {
248
+ // Dynamic styles applied inline
249
+ },
250
+ actionButton: {
251
+ flexDirection: 'row',
252
+ alignItems: 'center',
253
+ gap: 2,
254
+ },
255
+ actionText: {
256
+ // Dynamic styles applied inline
257
+ },
258
+ });