@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,229 @@
1
+ /**
2
+ * Segmented Control
3
+ *
4
+ * A native iOS-style segmented control component.
5
+ * Perfect for switching between views or filtering content.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const [selected, setSelected] = useState('daily');
10
+ *
11
+ * <SegmentedControl
12
+ * value={selected}
13
+ * onValueChange={setSelected}
14
+ * segments={[
15
+ * { label: 'Daily', value: 'daily' },
16
+ * { label: 'Weekly', value: 'weekly' },
17
+ * { label: 'Monthly', value: 'monthly' },
18
+ * ]}
19
+ * />
20
+ * ```
21
+ */
22
+
23
+ import React, { useCallback, useRef, useEffect } from 'react';
24
+ import {
25
+ View,
26
+ Text,
27
+ Pressable,
28
+ StyleSheet,
29
+ ViewStyle,
30
+ TextStyle,
31
+ LayoutChangeEvent,
32
+ LayoutRectangle,
33
+ } from 'react-native';
34
+ import Animated, {
35
+ useSharedValue,
36
+ useAnimatedStyle,
37
+ withSpring,
38
+ } from 'react-native-reanimated';
39
+ import { useTheme } from '@nativeui/core';
40
+ import { haptic } from '@nativeui/core';
41
+
42
+ export type SegmentedControlSize = 'sm' | 'md' | 'lg';
43
+
44
+ export interface Segment {
45
+ label: string;
46
+ value: string;
47
+ disabled?: boolean;
48
+ }
49
+
50
+ export interface SegmentedControlProps {
51
+ /** Array of segment options */
52
+ segments: Segment[];
53
+ /** Current selected value */
54
+ value: string;
55
+ /** Callback when value changes */
56
+ onValueChange: (value: string) => void;
57
+ /** Size preset */
58
+ size?: SegmentedControlSize;
59
+ /** Disabled state */
60
+ disabled?: boolean;
61
+ /** Container style */
62
+ style?: ViewStyle;
63
+ /** Segment text style */
64
+ textStyle?: TextStyle;
65
+ }
66
+
67
+ const heights: Record<SegmentedControlSize, number> = {
68
+ sm: 32,
69
+ md: 40,
70
+ lg: 48,
71
+ };
72
+
73
+ const fontSizes: Record<SegmentedControlSize, number> = {
74
+ sm: 13,
75
+ md: 14,
76
+ lg: 15,
77
+ };
78
+
79
+ const SPRING_CONFIG = { damping: 20, stiffness: 200 };
80
+
81
+ export function SegmentedControl({
82
+ segments,
83
+ value,
84
+ onValueChange,
85
+ size = 'md',
86
+ disabled = false,
87
+ style,
88
+ textStyle,
89
+ }: SegmentedControlProps) {
90
+ const { colors, radius } = useTheme();
91
+ const segmentLayouts = useRef(new Map<string, LayoutRectangle>()).current;
92
+
93
+ const indicatorX = useSharedValue(0);
94
+ const indicatorWidth = useSharedValue(0);
95
+
96
+ const height = heights[size];
97
+ const fontSize = fontSizes[size];
98
+ const padding = 4;
99
+ const segmentHeight = height - padding * 2;
100
+
101
+ const handleLayout = (segmentValue: string, event: LayoutChangeEvent) => {
102
+ const layout = event.nativeEvent.layout;
103
+ segmentLayouts.set(segmentValue, layout);
104
+
105
+ // Update indicator if this is the active segment
106
+ if (segmentValue === value) {
107
+ indicatorX.value = withSpring(layout.x, SPRING_CONFIG);
108
+ indicatorWidth.value = withSpring(layout.width, SPRING_CONFIG);
109
+ }
110
+ };
111
+
112
+ // Update indicator when value changes
113
+ useEffect(() => {
114
+ const layout = segmentLayouts.get(value);
115
+ if (layout) {
116
+ indicatorX.value = withSpring(layout.x, SPRING_CONFIG);
117
+ indicatorWidth.value = withSpring(layout.width, SPRING_CONFIG);
118
+ }
119
+ }, [value, segmentLayouts]);
120
+
121
+ const handlePress = useCallback(
122
+ (segmentValue: string, segmentDisabled?: boolean) => {
123
+ if (disabled || segmentDisabled) return;
124
+ haptic('selection');
125
+ onValueChange(segmentValue);
126
+ },
127
+ [disabled, onValueChange]
128
+ );
129
+
130
+ const indicatorStyle = useAnimatedStyle(() => ({
131
+ transform: [{ translateX: indicatorX.value }],
132
+ width: indicatorWidth.value,
133
+ }));
134
+
135
+ return (
136
+ <View
137
+ style={[
138
+ styles.container,
139
+ {
140
+ height,
141
+ backgroundColor: colors.backgroundMuted,
142
+ borderRadius: radius.lg,
143
+ padding,
144
+ opacity: disabled ? 0.5 : 1,
145
+ },
146
+ style,
147
+ ]}
148
+ >
149
+ {/* Indicator */}
150
+ <Animated.View
151
+ style={[
152
+ styles.indicator,
153
+ {
154
+ height: segmentHeight,
155
+ backgroundColor: colors.background,
156
+ borderRadius: radius.md,
157
+ },
158
+ indicatorStyle,
159
+ ]}
160
+ />
161
+
162
+ {/* Segments */}
163
+ {segments.map((segment) => {
164
+ const isActive = value === segment.value;
165
+ const isSegmentDisabled = disabled || segment.disabled;
166
+
167
+ return (
168
+ <Pressable
169
+ key={segment.value}
170
+ style={[
171
+ styles.segment,
172
+ {
173
+ height: segmentHeight,
174
+ opacity: isSegmentDisabled ? 0.5 : 1,
175
+ },
176
+ ]}
177
+ onLayout={(e) => handleLayout(segment.value, e)}
178
+ onPress={() => handlePress(segment.value, segment.disabled)}
179
+ disabled={isSegmentDisabled}
180
+ accessibilityRole="button"
181
+ accessibilityState={{ selected: isActive, disabled: isSegmentDisabled }}
182
+ >
183
+ <Text
184
+ style={[
185
+ styles.segmentText,
186
+ {
187
+ fontSize,
188
+ color: isActive ? colors.foreground : colors.foregroundMuted,
189
+ fontWeight: isActive ? '600' : '500',
190
+ },
191
+ textStyle,
192
+ ]}
193
+ numberOfLines={1}
194
+ >
195
+ {segment.label}
196
+ </Text>
197
+ </Pressable>
198
+ );
199
+ })}
200
+ </View>
201
+ );
202
+ }
203
+
204
+ const styles = StyleSheet.create({
205
+ container: {
206
+ flexDirection: 'row',
207
+ position: 'relative',
208
+ },
209
+ indicator: {
210
+ position: 'absolute',
211
+ top: 4,
212
+ left: 0,
213
+ shadowColor: '#000',
214
+ shadowOffset: { width: 0, height: 1 },
215
+ shadowOpacity: 0.08,
216
+ shadowRadius: 2,
217
+ elevation: 2,
218
+ },
219
+ segment: {
220
+ flex: 1,
221
+ alignItems: 'center',
222
+ justifyContent: 'center',
223
+ zIndex: 1,
224
+ paddingHorizontal: 8,
225
+ },
226
+ segmentText: {
227
+ textAlign: 'center',
228
+ },
229
+ });
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Select
3
+ *
4
+ * A selection component using a bottom sheet picker.
5
+ * Native-feeling select experience for mobile.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const [value, setValue] = useState('');
10
+ *
11
+ * <Select
12
+ * label="Country"
13
+ * placeholder="Select a country"
14
+ * value={value}
15
+ * onValueChange={setValue}
16
+ * options={[
17
+ * { label: 'United States', value: 'us' },
18
+ * { label: 'Germany', value: 'de' },
19
+ * { label: 'Japan', value: 'jp' },
20
+ * ]}
21
+ * />
22
+ * ```
23
+ */
24
+
25
+ import React, { useState, useCallback } from 'react';
26
+ import {
27
+ View,
28
+ Text,
29
+ Pressable,
30
+ StyleSheet,
31
+ ViewStyle,
32
+ TextStyle,
33
+ ScrollView,
34
+ } from 'react-native';
35
+ import Animated, {
36
+ useSharedValue,
37
+ useAnimatedStyle,
38
+ withTiming,
39
+ interpolateColor,
40
+ Easing,
41
+ } from 'react-native-reanimated';
42
+ import { useTheme } from '@nativeui/core';
43
+ import { haptic } from '@nativeui/core';
44
+
45
+ const TIMING_CONFIG = { duration: 200, easing: Easing.out(Easing.quad) };
46
+ import {
47
+ Sheet,
48
+ SheetContent,
49
+ SheetHeader,
50
+ SheetTitle,
51
+ } from './sheet';
52
+
53
+ export interface SelectOption {
54
+ label: string;
55
+ value: string;
56
+ disabled?: boolean;
57
+ }
58
+
59
+ export interface SelectProps {
60
+ /** Label text above select */
61
+ label?: string;
62
+ /** Placeholder when no value selected */
63
+ placeholder?: string;
64
+ /** Current selected value */
65
+ value?: string;
66
+ /** Callback when value changes */
67
+ onValueChange?: (value: string) => void;
68
+ /** Available options */
69
+ options: SelectOption[];
70
+ /** Error message */
71
+ error?: string;
72
+ /** Helper text */
73
+ helperText?: string;
74
+ /** Disabled state */
75
+ disabled?: boolean;
76
+ /** Sheet title */
77
+ sheetTitle?: string;
78
+ /** Container style */
79
+ containerStyle?: ViewStyle;
80
+ /** Trigger style */
81
+ style?: ViewStyle;
82
+ /** Label style */
83
+ labelStyle?: TextStyle;
84
+ }
85
+
86
+ const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
87
+
88
+ export function Select({
89
+ label,
90
+ placeholder = 'Select an option',
91
+ value,
92
+ onValueChange,
93
+ options,
94
+ error,
95
+ helperText,
96
+ disabled = false,
97
+ sheetTitle,
98
+ containerStyle,
99
+ style,
100
+ labelStyle,
101
+ }: SelectProps) {
102
+ const { colors, radius, spacing } = useTheme();
103
+ const [open, setOpen] = useState(false);
104
+ const focusProgress = useSharedValue(0);
105
+
106
+ const hasError = !!error;
107
+ const selectedOption = options.find((opt) => opt.value === value);
108
+
109
+ const handleOpen = useCallback(() => {
110
+ if (disabled) return;
111
+ haptic('light');
112
+ focusProgress.value = withTiming(1, TIMING_CONFIG);
113
+ setOpen(true);
114
+ }, [disabled]);
115
+
116
+ const handleClose = useCallback(() => {
117
+ focusProgress.value = withTiming(0, TIMING_CONFIG);
118
+ setOpen(false);
119
+ }, []);
120
+
121
+ const handleSelect = useCallback(
122
+ (optionValue: string) => {
123
+ haptic('selection');
124
+ onValueChange?.(optionValue);
125
+ handleClose();
126
+ },
127
+ [onValueChange, handleClose]
128
+ );
129
+
130
+ const animatedBorderStyle = useAnimatedStyle(() => {
131
+ if (hasError) {
132
+ return {
133
+ borderColor: colors.destructive,
134
+ borderWidth: 2,
135
+ };
136
+ }
137
+
138
+ return {
139
+ borderColor: interpolateColor(
140
+ focusProgress.value,
141
+ [0, 1],
142
+ [colors.border, colors.primary]
143
+ ),
144
+ borderWidth: focusProgress.value > 0.5 ? 2 : 1,
145
+ };
146
+ }, [hasError, colors]);
147
+
148
+ return (
149
+ <View style={[styles.container, containerStyle]}>
150
+ {label && (
151
+ <Text
152
+ style={[
153
+ styles.label,
154
+ {
155
+ fontSize: 14,
156
+ marginBottom: spacing[1.5],
157
+ color: hasError ? colors.destructive : colors.foreground,
158
+ },
159
+ labelStyle,
160
+ ]}
161
+ >
162
+ {label}
163
+ </Text>
164
+ )}
165
+
166
+ <AnimatedPressable
167
+ style={[
168
+ styles.trigger,
169
+ {
170
+ minHeight: 48,
171
+ paddingHorizontal: 12,
172
+ borderRadius: radius.md,
173
+ backgroundColor: disabled ? colors.backgroundMuted : colors.background,
174
+ },
175
+ animatedBorderStyle,
176
+ style,
177
+ ]}
178
+ onPress={handleOpen}
179
+ disabled={disabled}
180
+ accessibilityRole="combobox"
181
+ accessibilityState={{ disabled, expanded: open }}
182
+ accessibilityLabel={label}
183
+ >
184
+ <Text
185
+ style={[
186
+ styles.triggerText,
187
+ {
188
+ color: selectedOption
189
+ ? disabled
190
+ ? colors.foregroundMuted
191
+ : colors.foreground
192
+ : colors.foregroundMuted,
193
+ },
194
+ ]}
195
+ numberOfLines={1}
196
+ >
197
+ {selectedOption?.label ?? placeholder}
198
+ </Text>
199
+ <Text style={[styles.chevron, { color: colors.foregroundMuted }]}>
200
+
201
+ </Text>
202
+ </AnimatedPressable>
203
+
204
+ {(error || helperText) && (
205
+ <Text
206
+ style={[
207
+ styles.helperText,
208
+ {
209
+ fontSize: 12,
210
+ marginTop: spacing[1],
211
+ color: hasError ? colors.destructive : colors.foregroundMuted,
212
+ },
213
+ ]}
214
+ >
215
+ {error || helperText}
216
+ </Text>
217
+ )}
218
+
219
+ <Sheet open={open} onOpenChange={setOpen}>
220
+ <SheetContent height="50%">
221
+ <SheetHeader>
222
+ <SheetTitle>{sheetTitle ?? label ?? 'Select'}</SheetTitle>
223
+ </SheetHeader>
224
+ <ScrollView style={styles.optionsList} showsVerticalScrollIndicator={false}>
225
+ {options.map((option) => (
226
+ <Pressable
227
+ key={option.value}
228
+ style={({ pressed }) => [
229
+ styles.option,
230
+ {
231
+ backgroundColor:
232
+ option.value === value
233
+ ? colors.primary + '15'
234
+ : pressed
235
+ ? colors.backgroundMuted
236
+ : 'transparent',
237
+ borderRadius: radius.md,
238
+ paddingVertical: spacing[3],
239
+ paddingHorizontal: spacing[4],
240
+ opacity: option.disabled ? 0.5 : 1,
241
+ },
242
+ ]}
243
+ onPress={() => !option.disabled && handleSelect(option.value)}
244
+ disabled={option.disabled}
245
+ >
246
+ <Text
247
+ style={[
248
+ styles.optionText,
249
+ {
250
+ color:
251
+ option.value === value
252
+ ? colors.primary
253
+ : colors.foreground,
254
+ fontWeight: option.value === value ? '600' : '400',
255
+ },
256
+ ]}
257
+ >
258
+ {option.label}
259
+ </Text>
260
+ {option.value === value && (
261
+ <Text style={[styles.checkmark, { color: colors.primary }]}>
262
+
263
+ </Text>
264
+ )}
265
+ </Pressable>
266
+ ))}
267
+ </ScrollView>
268
+ </SheetContent>
269
+ </Sheet>
270
+ </View>
271
+ );
272
+ }
273
+
274
+ const styles = StyleSheet.create({
275
+ container: {
276
+ width: '100%',
277
+ },
278
+ label: {
279
+ fontWeight: '500',
280
+ },
281
+ trigger: {
282
+ flexDirection: 'row',
283
+ alignItems: 'center',
284
+ justifyContent: 'space-between',
285
+ },
286
+ triggerText: {
287
+ flex: 1,
288
+ fontSize: 14,
289
+ },
290
+ chevron: {
291
+ fontSize: 10,
292
+ marginLeft: 8,
293
+ },
294
+ helperText: {},
295
+ optionsList: {
296
+ flex: 1,
297
+ marginTop: 8,
298
+ },
299
+ option: {
300
+ flexDirection: 'row',
301
+ alignItems: 'center',
302
+ justifyContent: 'space-between',
303
+ },
304
+ optionText: {
305
+ fontSize: 16,
306
+ },
307
+ checkmark: {
308
+ fontSize: 16,
309
+ fontWeight: '600',
310
+ },
311
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Separator
3
+ *
4
+ * A visual divider component for separating content.
5
+ * Supports horizontal and vertical orientations.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <Separator />
10
+ * <Separator orientation="vertical" />
11
+ * <Separator decorative={false} />
12
+ * <Separator style={{ marginVertical: 16 }} />
13
+ * ```
14
+ */
15
+
16
+ import React from 'react';
17
+ import {
18
+ View,
19
+ StyleSheet,
20
+ ViewStyle,
21
+ ViewProps,
22
+ } from 'react-native';
23
+ import { useTheme } from '@nativeui/core';
24
+
25
+ export type SeparatorOrientation = 'horizontal' | 'vertical';
26
+
27
+ export interface SeparatorProps extends Omit<ViewProps, 'style'> {
28
+ /** Direction of the separator */
29
+ orientation?: SeparatorOrientation;
30
+ /** Whether the separator is purely decorative (affects accessibility) */
31
+ decorative?: boolean;
32
+ /** Additional styles */
33
+ style?: ViewStyle;
34
+ }
35
+
36
+ export function Separator({
37
+ orientation = 'horizontal',
38
+ decorative = true,
39
+ style,
40
+ ...props
41
+ }: SeparatorProps) {
42
+ const { colors } = useTheme();
43
+
44
+ const isHorizontal = orientation === 'horizontal';
45
+
46
+ return (
47
+ <View
48
+ style={[
49
+ styles.base,
50
+ isHorizontal ? styles.horizontal : styles.vertical,
51
+ { backgroundColor: colors.border },
52
+ style,
53
+ ]}
54
+ accessibilityRole={decorative ? 'none' : undefined}
55
+ accessible={!decorative}
56
+ accessibilityLabel={decorative ? undefined : 'Separator'}
57
+ {...props}
58
+ />
59
+ );
60
+ }
61
+
62
+ const styles = StyleSheet.create({
63
+ base: {
64
+ flexShrink: 0,
65
+ },
66
+ horizontal: {
67
+ height: StyleSheet.hairlineWidth,
68
+ width: '100%',
69
+ },
70
+ vertical: {
71
+ width: StyleSheet.hairlineWidth,
72
+ alignSelf: 'stretch',
73
+ },
74
+ });