@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,416 @@
1
+ /**
2
+ * Accordion
3
+ *
4
+ * An expandable/collapsible content panel component.
5
+ * Supports single or multiple expanded items with smooth height animations.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <Accordion type="single" defaultValue="item-1">
10
+ * <AccordionItem value="item-1">
11
+ * <AccordionTrigger>Section 1</AccordionTrigger>
12
+ * <AccordionContent>
13
+ * <Text>Content for section 1</Text>
14
+ * </AccordionContent>
15
+ * </AccordionItem>
16
+ * <AccordionItem value="item-2">
17
+ * <AccordionTrigger>Section 2</AccordionTrigger>
18
+ * <AccordionContent>
19
+ * <Text>Content for section 2</Text>
20
+ * </AccordionContent>
21
+ * </AccordionItem>
22
+ * </Accordion>
23
+ * ```
24
+ */
25
+
26
+ import React, { createContext, useContext, useState, useCallback } from 'react';
27
+ import {
28
+ View,
29
+ Text,
30
+ Pressable,
31
+ StyleSheet,
32
+ ViewStyle,
33
+ TextStyle,
34
+ LayoutChangeEvent,
35
+ } from 'react-native';
36
+ import Animated, {
37
+ useSharedValue,
38
+ useAnimatedStyle,
39
+ withSpring,
40
+ interpolate,
41
+ Extrapolation,
42
+ } from 'react-native-reanimated';
43
+ import Svg, { Path } from 'react-native-svg';
44
+ import { useTheme } from '@nativeui/core';
45
+ import { haptic } from '@nativeui/core';
46
+
47
+ // Smooth spring config for natural feel
48
+ const SPRING_CONFIG = {
49
+ damping: 20,
50
+ stiffness: 200,
51
+ mass: 0.5,
52
+ };
53
+
54
+ // Accordion Context
55
+ interface AccordionContextValue {
56
+ type: 'single' | 'multiple';
57
+ value: string[];
58
+ onValueChange: (itemValue: string) => void;
59
+ }
60
+
61
+ const AccordionContext = createContext<AccordionContextValue | null>(null);
62
+
63
+ function useAccordionContext() {
64
+ const context = useContext(AccordionContext);
65
+ if (!context) {
66
+ throw new Error('Accordion components must be used within an Accordion provider');
67
+ }
68
+ return context;
69
+ }
70
+
71
+ // Item Context
72
+ interface AccordionItemContextValue {
73
+ value: string;
74
+ isOpen: boolean;
75
+ isLast: boolean;
76
+ }
77
+
78
+ const AccordionItemContext = createContext<AccordionItemContextValue | null>(null);
79
+
80
+ function useAccordionItemContext() {
81
+ const context = useContext(AccordionItemContext);
82
+ if (!context) {
83
+ throw new Error('AccordionItem components must be used within an AccordionItem');
84
+ }
85
+ return context;
86
+ }
87
+
88
+ // Animated SVG Chevron
89
+ const AnimatedSvg = Animated.createAnimatedComponent(Svg);
90
+
91
+ function ChevronIcon({ color, style }: { color: string; style?: any }) {
92
+ return (
93
+ <AnimatedSvg width={16} height={16} viewBox="0 0 24 24" fill="none" style={style}>
94
+ <Path
95
+ d="M6 9l6 6 6-6"
96
+ stroke={color}
97
+ strokeWidth={2}
98
+ strokeLinecap="round"
99
+ strokeLinejoin="round"
100
+ />
101
+ </AnimatedSvg>
102
+ );
103
+ }
104
+
105
+ // Accordion Root
106
+ export interface AccordionProps {
107
+ /** Selection mode */
108
+ type?: 'single' | 'multiple';
109
+ /** Controlled value */
110
+ value?: string | string[];
111
+ /** Default value */
112
+ defaultValue?: string | string[];
113
+ /** Callback when value changes */
114
+ onValueChange?: (value: string | string[]) => void;
115
+ /** Whether to allow collapsing all items (single mode only) */
116
+ collapsible?: boolean;
117
+ children: React.ReactNode;
118
+ style?: ViewStyle;
119
+ }
120
+
121
+ export function Accordion({
122
+ type = 'single',
123
+ value: controlledValue,
124
+ defaultValue,
125
+ onValueChange,
126
+ collapsible = true,
127
+ children,
128
+ style,
129
+ }: AccordionProps) {
130
+ const { colors, radius } = useTheme();
131
+
132
+ // Normalize values to array
133
+ const normalizeValue = (val: string | string[] | undefined): string[] => {
134
+ if (val === undefined) return [];
135
+ return Array.isArray(val) ? val : [val];
136
+ };
137
+
138
+ const [internalValue, setInternalValue] = useState(() =>
139
+ normalizeValue(defaultValue)
140
+ );
141
+ const isControlled = controlledValue !== undefined;
142
+ const value = isControlled ? normalizeValue(controlledValue) : internalValue;
143
+
144
+ const handleValueChange = useCallback(
145
+ (itemValue: string) => {
146
+ let newValue: string[];
147
+
148
+ if (type === 'single') {
149
+ if (value.includes(itemValue)) {
150
+ newValue = collapsible ? [] : [itemValue];
151
+ } else {
152
+ newValue = [itemValue];
153
+ }
154
+ } else {
155
+ if (value.includes(itemValue)) {
156
+ newValue = value.filter((v) => v !== itemValue);
157
+ } else {
158
+ newValue = [...value, itemValue];
159
+ }
160
+ }
161
+
162
+ if (!isControlled) {
163
+ setInternalValue(newValue);
164
+ }
165
+
166
+ const returnValue = type === 'single' ? newValue[0] ?? '' : newValue;
167
+ onValueChange?.(returnValue);
168
+ },
169
+ [type, value, collapsible, isControlled, onValueChange]
170
+ );
171
+
172
+ // Count children to determine last item
173
+ const childArray = React.Children.toArray(children);
174
+ const childCount = childArray.length;
175
+
176
+ return (
177
+ <AccordionContext.Provider value={{ type, value, onValueChange: handleValueChange }}>
178
+ <View
179
+ style={[
180
+ styles.container,
181
+ {
182
+ borderRadius: radius.lg,
183
+ borderWidth: 1,
184
+ borderColor: colors.border,
185
+ overflow: 'hidden',
186
+ },
187
+ style,
188
+ ]}
189
+ >
190
+ {React.Children.map(children, (child, index) => {
191
+ if (React.isValidElement(child)) {
192
+ return React.cloneElement(child as React.ReactElement<any>, {
193
+ __isLast: index === childCount - 1,
194
+ });
195
+ }
196
+ return child;
197
+ })}
198
+ </View>
199
+ </AccordionContext.Provider>
200
+ );
201
+ }
202
+
203
+ // AccordionItem
204
+ export interface AccordionItemProps {
205
+ value: string;
206
+ children: React.ReactNode;
207
+ disabled?: boolean;
208
+ style?: ViewStyle;
209
+ /** @internal */
210
+ __isLast?: boolean;
211
+ }
212
+
213
+ export function AccordionItem({
214
+ value,
215
+ children,
216
+ disabled = false,
217
+ style,
218
+ __isLast = false,
219
+ }: AccordionItemProps) {
220
+ const { colors } = useTheme();
221
+ const { value: selectedValues } = useAccordionContext();
222
+ const isOpen = selectedValues.includes(value);
223
+
224
+ return (
225
+ <AccordionItemContext.Provider value={{ value, isOpen, isLast: __isLast }}>
226
+ <View
227
+ style={[
228
+ styles.item,
229
+ {
230
+ borderBottomWidth: __isLast ? 0 : 1,
231
+ borderBottomColor: colors.border,
232
+ opacity: disabled ? 0.5 : 1,
233
+ },
234
+ style,
235
+ ]}
236
+ >
237
+ {children}
238
+ </View>
239
+ </AccordionItemContext.Provider>
240
+ );
241
+ }
242
+
243
+ // AccordionTrigger
244
+ export interface AccordionTriggerProps {
245
+ children: React.ReactNode;
246
+ disabled?: boolean;
247
+ style?: ViewStyle;
248
+ textStyle?: TextStyle;
249
+ }
250
+
251
+ export function AccordionTrigger({
252
+ children,
253
+ disabled = false,
254
+ style,
255
+ textStyle,
256
+ }: AccordionTriggerProps) {
257
+ const { colors, spacing } = useTheme();
258
+ const { onValueChange } = useAccordionContext();
259
+ const { value, isOpen } = useAccordionItemContext();
260
+
261
+ const progress = useSharedValue(isOpen ? 1 : 0);
262
+
263
+ React.useEffect(() => {
264
+ progress.value = withSpring(isOpen ? 1 : 0, SPRING_CONFIG);
265
+ }, [isOpen]);
266
+
267
+ const handlePress = () => {
268
+ if (disabled) return;
269
+ haptic('selection');
270
+ onValueChange(value);
271
+ };
272
+
273
+ const chevronStyle = useAnimatedStyle(() => ({
274
+ transform: [
275
+ { rotate: `${interpolate(progress.value, [0, 1], [0, 180])}deg` },
276
+ ],
277
+ }));
278
+
279
+ return (
280
+ <Pressable
281
+ style={({ pressed }) => [
282
+ styles.trigger,
283
+ {
284
+ paddingVertical: spacing[4],
285
+ paddingHorizontal: spacing[4],
286
+ backgroundColor: pressed ? colors.secondary : colors.background,
287
+ },
288
+ style,
289
+ ]}
290
+ onPress={handlePress}
291
+ disabled={disabled}
292
+ accessibilityRole="button"
293
+ accessibilityState={{ expanded: isOpen, disabled }}
294
+ >
295
+ <Text
296
+ style={[
297
+ styles.triggerText,
298
+ { color: colors.foreground },
299
+ textStyle,
300
+ ]}
301
+ >
302
+ {children}
303
+ </Text>
304
+ <Animated.View style={chevronStyle}>
305
+ <ChevronIcon color={colors.foregroundMuted} />
306
+ </Animated.View>
307
+ </Pressable>
308
+ );
309
+ }
310
+
311
+ // AccordionContent
312
+ export interface AccordionContentProps {
313
+ children: React.ReactNode;
314
+ style?: ViewStyle;
315
+ }
316
+
317
+ export function AccordionContent({ children, style }: AccordionContentProps) {
318
+ const { colors, spacing } = useTheme();
319
+ const { isOpen } = useAccordionItemContext();
320
+
321
+ // Track if we've ever been opened (for initial render optimization)
322
+ const hasBeenOpened = React.useRef(isOpen);
323
+ const [measuredHeight, setMeasuredHeight] = useState(0);
324
+ const heightValue = useSharedValue(0);
325
+ const progress = useSharedValue(isOpen ? 1 : 0);
326
+
327
+ // Update ref synchronously before render logic
328
+ if (isOpen && !hasBeenOpened.current) {
329
+ hasBeenOpened.current = true;
330
+ }
331
+
332
+ // Keep a ref to isOpen for use in callbacks
333
+ const isOpenRef = React.useRef(isOpen);
334
+ isOpenRef.current = isOpen;
335
+
336
+ // Sync measured height to shared value
337
+ React.useEffect(() => {
338
+ if (measuredHeight > 0) {
339
+ heightValue.value = measuredHeight;
340
+ }
341
+ }, [measuredHeight]);
342
+
343
+ // Animate open/close
344
+ React.useEffect(() => {
345
+ progress.value = withSpring(isOpen ? 1 : 0, SPRING_CONFIG);
346
+ }, [isOpen]);
347
+
348
+ const onLayout = (event: LayoutChangeEvent) => {
349
+ const h = event.nativeEvent.layout.height;
350
+ // Only accept heights LARGER than current measurement
351
+ // This prevents accepting small heights from lingering collapse animation
352
+ if (isOpenRef.current && h > 0 && h > measuredHeight) {
353
+ setMeasuredHeight(h);
354
+ }
355
+ };
356
+
357
+ const animatedStyle = useAnimatedStyle(() => {
358
+ if (heightValue.value === 0) {
359
+ // Not measured yet - show full height so onLayout can measure
360
+ return { overflow: 'hidden' as const };
361
+ }
362
+
363
+ return {
364
+ height: interpolate(
365
+ progress.value,
366
+ [0, 1],
367
+ [0, heightValue.value],
368
+ Extrapolation.CLAMP
369
+ ),
370
+ overflow: 'hidden' as const,
371
+ };
372
+ });
373
+
374
+ // Don't render until first opened
375
+ if (!hasBeenOpened.current) {
376
+ return null;
377
+ }
378
+
379
+ return (
380
+ <Animated.View style={animatedStyle}>
381
+ <View
382
+ onLayout={onLayout}
383
+ style={[
384
+ styles.content,
385
+ {
386
+ // Fix height after measurement to prevent text reflow during animation
387
+ height: measuredHeight > 0 ? measuredHeight : undefined,
388
+ paddingHorizontal: spacing[4],
389
+ paddingBottom: spacing[4],
390
+ backgroundColor: colors.background,
391
+ },
392
+ style,
393
+ ]}
394
+ >
395
+ {children}
396
+ </View>
397
+ </Animated.View>
398
+ );
399
+ }
400
+
401
+ const styles = StyleSheet.create({
402
+ container: {},
403
+ item: {},
404
+ trigger: {
405
+ flexDirection: 'row',
406
+ alignItems: 'center',
407
+ justifyContent: 'space-between',
408
+ },
409
+ triggerText: {
410
+ fontSize: 15,
411
+ fontWeight: '500',
412
+ flex: 1,
413
+ marginRight: 8,
414
+ },
415
+ content: {},
416
+ });