@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,311 @@
1
+ /**
2
+ * Rating
3
+ *
4
+ * Interactive star rating for reviews and feedback.
5
+ * Supports half-star precision and readonly display mode.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Interactive rating
10
+ * const [rating, setRating] = useState(0);
11
+ * <Rating value={rating} onChange={setRating} />
12
+ *
13
+ * // Readonly display
14
+ * <Rating value={4.5} readonly />
15
+ *
16
+ * // With label
17
+ * <Rating value={3} readonly size="sm" />
18
+ * <Text>{rating} out of 5</Text>
19
+ *
20
+ * // Different sizes
21
+ * <Rating value={4} size="sm" readonly />
22
+ * <Rating value={4} size="md" readonly />
23
+ * <Rating value={4} size="lg" readonly />
24
+ * ```
25
+ */
26
+
27
+ import React from 'react';
28
+ import {
29
+ View,
30
+ Pressable,
31
+ StyleSheet,
32
+ ViewStyle,
33
+ } from 'react-native';
34
+ import Animated, {
35
+ useAnimatedStyle,
36
+ useSharedValue,
37
+ withSpring,
38
+ withSequence,
39
+ withTiming,
40
+ } from 'react-native-reanimated';
41
+ import { useTheme, haptic } from '@nativeui/core';
42
+
43
+ const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
44
+
45
+ // ─────────────────────────────────────────────────────────────────────────────
46
+ // Types
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+
49
+ export type RatingSize = 'sm' | 'md' | 'lg';
50
+
51
+ export interface RatingProps {
52
+ /** Current rating value (0-5) */
53
+ value: number;
54
+ /** Maximum rating value */
55
+ max?: number;
56
+ /** Size preset */
57
+ size?: RatingSize;
58
+ /** Whether rating is readonly (display only) */
59
+ readonly?: boolean;
60
+ /** Allow half-star precision */
61
+ precision?: 'full' | 'half';
62
+ /** Change handler */
63
+ onChange?: (value: number) => void;
64
+ /** Container style */
65
+ style?: ViewStyle;
66
+ /** Active star color (defaults to warning yellow) */
67
+ activeColor?: string;
68
+ /** Inactive star color */
69
+ inactiveColor?: string;
70
+ }
71
+
72
+ // ─────────────────────────────────────────────────────────────────────────────
73
+ // Size configs
74
+ // ─────────────────────────────────────────────────────────────────────────────
75
+
76
+ const SIZE_CONFIG = {
77
+ sm: {
78
+ starSize: 18,
79
+ gap: 2,
80
+ },
81
+ md: {
82
+ starSize: 26,
83
+ gap: 4,
84
+ },
85
+ lg: {
86
+ starSize: 34,
87
+ gap: 6,
88
+ },
89
+ };
90
+
91
+ // ─────────────────────────────────────────────────────────────────────────────
92
+ // Star SVG Path (simplified 5-point star)
93
+ // ─────────────────────────────────────────────────────────────────────────────
94
+
95
+ function StarIcon({ size, color }: { size: number; color: string }) {
96
+ // Simple star shape using View components
97
+ return (
98
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
99
+ <View
100
+ style={{
101
+ width: size * 0.9,
102
+ height: size * 0.9,
103
+ backgroundColor: color,
104
+ // Create star using clip path workaround (simplified to rounded square for now)
105
+ borderRadius: size * 0.15,
106
+ transform: [{ rotate: '45deg' }],
107
+ }}
108
+ />
109
+ {/* Star points overlay */}
110
+ <View
111
+ style={{
112
+ position: 'absolute',
113
+ width: size * 0.6,
114
+ height: size * 0.6,
115
+ backgroundColor: color,
116
+ borderRadius: size * 0.1,
117
+ }}
118
+ />
119
+ </View>
120
+ );
121
+ }
122
+
123
+ // Unicode star character approach (more reliable)
124
+ function StarText({ size, color, filled }: { size: number; color: string; filled: boolean }) {
125
+ return (
126
+ <Animated.Text
127
+ style={{
128
+ fontSize: size,
129
+ color,
130
+ lineHeight: size * 1.1,
131
+ textAlign: 'center',
132
+ }}
133
+ >
134
+ {filled ? '★' : '☆'}
135
+ </Animated.Text>
136
+ );
137
+ }
138
+
139
+ // ─────────────────────────────────────────────────────────────────────────────
140
+ // Individual Star Component
141
+ // ─────────────────────────────────────────────────────────────────────────────
142
+
143
+ interface StarProps {
144
+ index: number;
145
+ filled: 'full' | 'half' | 'empty';
146
+ size: number;
147
+ activeColor: string;
148
+ inactiveColor: string;
149
+ readonly: boolean;
150
+ onPress: () => void;
151
+ }
152
+
153
+ function Star({
154
+ index,
155
+ filled,
156
+ size,
157
+ activeColor,
158
+ inactiveColor,
159
+ readonly,
160
+ onPress,
161
+ }: StarProps) {
162
+ const scale = useSharedValue(1);
163
+
164
+ const animatedStyle = useAnimatedStyle(() => ({
165
+ transform: [{ scale: scale.value }],
166
+ }));
167
+
168
+ const handlePressIn = () => {
169
+ if (readonly) return;
170
+ scale.value = withSpring(0.85, { damping: 15, stiffness: 400 });
171
+ };
172
+
173
+ const handlePressOut = () => {
174
+ if (readonly) return;
175
+ scale.value = withSequence(
176
+ withSpring(1.15, { damping: 15, stiffness: 400 }),
177
+ withSpring(1, { damping: 15, stiffness: 400 })
178
+ );
179
+ };
180
+
181
+ const handlePress = () => {
182
+ if (readonly) return;
183
+ haptic('light');
184
+ onPress();
185
+ };
186
+
187
+ const color = filled === 'empty' ? inactiveColor : activeColor;
188
+
189
+ if (readonly) {
190
+ return (
191
+ <View style={[styles.star, { width: size, height: size }]}>
192
+ {filled === 'half' ? (
193
+ <View style={styles.halfStarContainer}>
194
+ <View style={[styles.halfStar, { width: size / 2, overflow: 'hidden' }]}>
195
+ <StarText size={size} color={activeColor} filled />
196
+ </View>
197
+ <View style={[styles.halfStar, { width: size / 2, overflow: 'hidden', marginLeft: -size / 2 }]}>
198
+ <View style={{ marginLeft: -size / 2 }}>
199
+ <StarText size={size} color={inactiveColor} filled={false} />
200
+ </View>
201
+ </View>
202
+ </View>
203
+ ) : (
204
+ <StarText size={size} color={color} filled={filled === 'full'} />
205
+ )}
206
+ </View>
207
+ );
208
+ }
209
+
210
+ return (
211
+ <AnimatedPressable
212
+ onPress={handlePress}
213
+ onPressIn={handlePressIn}
214
+ onPressOut={handlePressOut}
215
+ style={[styles.star, { width: size, height: size }, animatedStyle]}
216
+ accessibilityRole="button"
217
+ accessibilityLabel={`Rate ${index + 1} star${index > 0 ? 's' : ''}`}
218
+ >
219
+ <StarText size={size} color={color} filled={filled === 'full'} />
220
+ </AnimatedPressable>
221
+ );
222
+ }
223
+
224
+ // ─────────────────────────────────────────────────────────────────────────────
225
+ // Rating Component
226
+ // ─────────────────────────────────────────────────────────────────────────────
227
+
228
+ export function Rating({
229
+ value,
230
+ max = 5,
231
+ size = 'md',
232
+ readonly = false,
233
+ precision = 'full',
234
+ onChange,
235
+ style,
236
+ activeColor,
237
+ inactiveColor,
238
+ }: RatingProps) {
239
+ const { colors } = useTheme();
240
+ const config = SIZE_CONFIG[size];
241
+
242
+ const starActiveColor = activeColor || '#F59E0B'; // Amber/warning yellow
243
+ const starInactiveColor = inactiveColor || colors.border;
244
+
245
+ const handleStarPress = (index: number) => {
246
+ if (readonly || !onChange) return;
247
+ const newValue = index + 1;
248
+ onChange(newValue);
249
+ };
250
+
251
+ const getStarFill = (index: number): 'full' | 'half' | 'empty' => {
252
+ if (value >= index + 1) {
253
+ return 'full';
254
+ }
255
+ if (precision === 'half' && value >= index + 0.5) {
256
+ return 'half';
257
+ }
258
+ return 'empty';
259
+ };
260
+
261
+ return (
262
+ <View
263
+ style={[
264
+ styles.container,
265
+ { gap: config.gap },
266
+ style,
267
+ ]}
268
+ accessibilityRole="adjustable"
269
+ accessibilityValue={{
270
+ min: 0,
271
+ max,
272
+ now: value,
273
+ text: `${value} out of ${max} stars`,
274
+ }}
275
+ >
276
+ {Array.from({ length: max }, (_, index) => (
277
+ <Star
278
+ key={index}
279
+ index={index}
280
+ filled={getStarFill(index)}
281
+ size={config.starSize}
282
+ activeColor={starActiveColor}
283
+ inactiveColor={starInactiveColor}
284
+ readonly={readonly}
285
+ onPress={() => handleStarPress(index)}
286
+ />
287
+ ))}
288
+ </View>
289
+ );
290
+ }
291
+
292
+ // ─────────────────────────────────────────────────────────────────────────────
293
+ // Styles
294
+ // ─────────────────────────────────────────────────────────────────────────────
295
+
296
+ const styles = StyleSheet.create({
297
+ container: {
298
+ flexDirection: 'row',
299
+ alignItems: 'center',
300
+ },
301
+ star: {
302
+ alignItems: 'center',
303
+ justifyContent: 'center',
304
+ },
305
+ halfStarContainer: {
306
+ flexDirection: 'row',
307
+ },
308
+ halfStar: {
309
+ overflow: 'hidden',
310
+ },
311
+ });
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Row
3
+ *
4
+ * Horizontal flex container with gap, alignment, and padding props.
5
+ * Uses design tokens for consistent spacing.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <Row gap="md" align="center" justify="between">
10
+ * <Text>Left</Text>
11
+ * <Text>Right</Text>
12
+ * </Row>
13
+ *
14
+ * <Row gap={4} wrap>
15
+ * {items.map(item => <Chip key={item.id}>{item.name}</Chip>)}
16
+ * </Row>
17
+ * ```
18
+ */
19
+
20
+ import React, { forwardRef } from 'react';
21
+ import { View, ViewProps, ViewStyle } from 'react-native';
22
+ import { useTheme } from '@nativeui/core';
23
+
24
+ // Semantic gap values
25
+ type GapSemantic = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
26
+
27
+ // Gap can be either semantic string or numeric spacing key
28
+ export type GapValue = GapSemantic | number;
29
+
30
+ // Semantic → Spacing Key Mapping
31
+ const semanticGapMap: Record<GapSemantic, number> = {
32
+ 'none': 0,
33
+ 'xs': 2,
34
+ 'sm': 3,
35
+ 'md': 4,
36
+ 'lg': 6,
37
+ 'xl': 8,
38
+ '2xl': 12,
39
+ };
40
+
41
+ // Alignment value mappings
42
+ const alignMap = {
43
+ 'start': 'flex-start',
44
+ 'center': 'center',
45
+ 'end': 'flex-end',
46
+ 'stretch': 'stretch',
47
+ 'baseline': 'baseline',
48
+ } as const;
49
+
50
+ const justifyMap = {
51
+ 'start': 'flex-start',
52
+ 'center': 'center',
53
+ 'end': 'flex-end',
54
+ 'between': 'space-between',
55
+ 'around': 'space-around',
56
+ 'evenly': 'space-evenly',
57
+ } as const;
58
+
59
+ export interface RowProps extends ViewProps {
60
+ /** Gap between children (semantic or spacing key) */
61
+ gap?: GapValue;
62
+ /** Vertical alignment of children */
63
+ align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
64
+ /** Horizontal distribution of children */
65
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
66
+ /** Allow children to wrap to next line */
67
+ wrap?: boolean;
68
+ /** Flex value for the container */
69
+ flex?: number;
70
+ /** Padding (all sides) */
71
+ p?: GapValue;
72
+ /** Horizontal padding */
73
+ px?: GapValue;
74
+ /** Vertical padding */
75
+ py?: GapValue;
76
+ /** Additional styles */
77
+ style?: ViewStyle;
78
+ /** Children */
79
+ children?: React.ReactNode;
80
+ }
81
+
82
+ function resolveGap(
83
+ value: GapValue | undefined,
84
+ spacing: ReturnType<typeof useTheme>['spacing']
85
+ ): number | undefined {
86
+ if (value === undefined) return undefined;
87
+
88
+ if (typeof value === 'string') {
89
+ const key = semanticGapMap[value as GapSemantic];
90
+ return (spacing as Record<number, number>)[key];
91
+ }
92
+
93
+ return (spacing as Record<number, number>)[value];
94
+ }
95
+
96
+ export const Row = forwardRef<View, RowProps>(function Row(
97
+ {
98
+ gap,
99
+ align,
100
+ justify,
101
+ wrap = false,
102
+ flex,
103
+ p,
104
+ px,
105
+ py,
106
+ style,
107
+ children,
108
+ ...props
109
+ },
110
+ ref
111
+ ) {
112
+ const { spacing } = useTheme();
113
+
114
+ const resolvedGap = resolveGap(gap, spacing);
115
+ const resolvedP = resolveGap(p, spacing);
116
+ const resolvedPx = resolveGap(px, spacing);
117
+ const resolvedPy = resolveGap(py, spacing);
118
+
119
+ const containerStyle: ViewStyle = {
120
+ flexDirection: 'row',
121
+ ...(resolvedGap !== undefined && { gap: resolvedGap }),
122
+ ...(align && { alignItems: alignMap[align] }),
123
+ ...(justify && { justifyContent: justifyMap[justify] }),
124
+ ...(wrap && { flexWrap: 'wrap' }),
125
+ ...(flex !== undefined && { flex }),
126
+ ...(resolvedP !== undefined && { padding: resolvedP }),
127
+ ...(resolvedPx !== undefined && { paddingHorizontal: resolvedPx }),
128
+ ...(resolvedPy !== undefined && { paddingVertical: resolvedPy }),
129
+ };
130
+
131
+ return (
132
+ <View ref={ref} style={[containerStyle, style]} {...props}>
133
+ {children}
134
+ </View>
135
+ );
136
+ });
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Screen
3
+ *
4
+ * Screen container with safe area, scroll support, and background variants.
5
+ * Uses design tokens for consistent styling.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Basic screen with safe area
10
+ * <Screen>
11
+ * <Text>Content</Text>
12
+ * </Screen>
13
+ *
14
+ * // Scrollable screen with padding
15
+ * <Screen scroll padded>
16
+ * <Text>Scrollable content</Text>
17
+ * </Screen>
18
+ *
19
+ * // Surface variant with custom edges
20
+ * <Screen variant="surface" edges={['top', 'bottom']}>
21
+ * <Text>Surface background</Text>
22
+ * </Screen>
23
+ * ```
24
+ */
25
+
26
+ import React, { forwardRef } from 'react';
27
+ import {
28
+ View,
29
+ ScrollView,
30
+ ScrollViewProps,
31
+ ViewStyle,
32
+ StyleSheet,
33
+ } from 'react-native';
34
+ import { useSafeAreaInsets, Edge } from 'react-native-safe-area-context';
35
+ import { useTheme } from '@nativeui/core';
36
+
37
+ export type ScreenVariant = 'default' | 'surface' | 'muted';
38
+
39
+ export interface ScreenProps {
40
+ /** Enable scrolling */
41
+ scroll?: boolean;
42
+ /** Additional ScrollView props when scroll is enabled */
43
+ scrollProps?: Omit<ScrollViewProps, 'style' | 'contentContainerStyle'>;
44
+ /** Add horizontal padding (spacing[4] = 16px) */
45
+ padded?: boolean;
46
+ /** Background variant */
47
+ variant?: ScreenVariant;
48
+ /** Safe area edges to respect (default: all) */
49
+ edges?: Edge[];
50
+ /** Additional styles for the container */
51
+ style?: ViewStyle;
52
+ /** Additional styles for the content container (only when scroll is true) */
53
+ contentContainerStyle?: ViewStyle;
54
+ /** Children */
55
+ children?: React.ReactNode;
56
+ }
57
+
58
+ export const Screen = forwardRef<View | ScrollView, ScreenProps>(function Screen(
59
+ {
60
+ scroll = false,
61
+ scrollProps,
62
+ padded = false,
63
+ variant = 'default',
64
+ edges = ['top', 'bottom', 'left', 'right'],
65
+ style,
66
+ contentContainerStyle,
67
+ children,
68
+ },
69
+ ref
70
+ ) {
71
+ const { colors, spacing } = useTheme();
72
+ const insets = useSafeAreaInsets();
73
+
74
+ // Get background color based on variant
75
+ const backgroundColor = getBackgroundColor(variant, colors);
76
+
77
+ // Calculate safe area padding based on edges
78
+ const safeAreaStyle: ViewStyle = {
79
+ paddingTop: edges.includes('top') ? insets.top : 0,
80
+ paddingBottom: edges.includes('bottom') ? insets.bottom : 0,
81
+ paddingLeft: edges.includes('left') ? insets.left : 0,
82
+ paddingRight: edges.includes('right') ? insets.right : 0,
83
+ };
84
+
85
+ // Content padding when padded is true
86
+ const paddedStyle: ViewStyle = padded
87
+ ? { paddingHorizontal: spacing[4] }
88
+ : {};
89
+
90
+ if (scroll) {
91
+ return (
92
+ <ScrollView
93
+ ref={ref as React.ForwardedRef<ScrollView>}
94
+ style={[
95
+ styles.container,
96
+ { backgroundColor },
97
+ safeAreaStyle,
98
+ style,
99
+ ]}
100
+ contentContainerStyle={[
101
+ styles.scrollContent,
102
+ paddedStyle,
103
+ contentContainerStyle,
104
+ ]}
105
+ showsVerticalScrollIndicator={false}
106
+ keyboardShouldPersistTaps="handled"
107
+ {...scrollProps}
108
+ >
109
+ {children}
110
+ </ScrollView>
111
+ );
112
+ }
113
+
114
+ return (
115
+ <View
116
+ ref={ref as React.ForwardedRef<View>}
117
+ style={[
118
+ styles.container,
119
+ { backgroundColor },
120
+ safeAreaStyle,
121
+ paddedStyle,
122
+ style,
123
+ ]}
124
+ >
125
+ {children}
126
+ </View>
127
+ );
128
+ });
129
+
130
+ function getBackgroundColor(
131
+ variant: ScreenVariant,
132
+ colors: ReturnType<typeof useTheme>['colors']
133
+ ): string {
134
+ switch (variant) {
135
+ case 'default':
136
+ return colors.background;
137
+ case 'surface':
138
+ return colors.backgroundSubtle;
139
+ case 'muted':
140
+ return colors.backgroundMuted;
141
+ default:
142
+ return colors.background;
143
+ }
144
+ }
145
+
146
+ const styles = StyleSheet.create({
147
+ container: {
148
+ flex: 1,
149
+ },
150
+ scrollContent: {
151
+ flexGrow: 1,
152
+ },
153
+ });