@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,578 @@
1
+ /**
2
+ * DateTimePicker
3
+ *
4
+ * Date and time selection via bottom sheet with native pickers.
5
+ * Supports date, time, and datetime modes.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Date picker
10
+ * const [date, setDate] = useState(new Date());
11
+ * <DateTimePicker
12
+ * mode="date"
13
+ * value={date}
14
+ * onChange={setDate}
15
+ * label="Birth Date"
16
+ * />
17
+ *
18
+ * // Time picker
19
+ * <DateTimePicker
20
+ * mode="time"
21
+ * value={time}
22
+ * onChange={setTime}
23
+ * label="Reminder Time"
24
+ * />
25
+ *
26
+ * // DateTime picker
27
+ * <DateTimePicker
28
+ * mode="datetime"
29
+ * value={datetime}
30
+ * onChange={setDatetime}
31
+ * label="Event Date & Time"
32
+ * />
33
+ *
34
+ * // With min/max dates
35
+ * <DateTimePicker
36
+ * mode="date"
37
+ * value={date}
38
+ * onChange={setDate}
39
+ * minimumDate={new Date()}
40
+ * maximumDate={new Date(2030, 11, 31)}
41
+ * />
42
+ * ```
43
+ */
44
+
45
+ import React, { useState, useCallback, useEffect } from 'react';
46
+ import {
47
+ View,
48
+ Text,
49
+ Pressable,
50
+ StyleSheet,
51
+ Modal,
52
+ Platform,
53
+ } from 'react-native';
54
+ import Animated, {
55
+ useAnimatedStyle,
56
+ useSharedValue,
57
+ withTiming,
58
+ Easing,
59
+ runOnJS,
60
+ } from 'react-native-reanimated';
61
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
62
+ import { useTheme, haptic } from '@nativeui/core';
63
+
64
+ // Try to import native picker - may fail in Expo Go
65
+ let DateTimePickerNative: typeof import('@react-native-community/datetimepicker').default | null = null;
66
+ let isNativePickerAvailable = false;
67
+
68
+ try {
69
+ const picker = require('@react-native-community/datetimepicker');
70
+ DateTimePickerNative = picker.default;
71
+ isNativePickerAvailable = true;
72
+ } catch {
73
+ // Native picker not available (Expo Go)
74
+ isNativePickerAvailable = false;
75
+ }
76
+
77
+ type DateTimePickerEvent = {
78
+ type: string;
79
+ nativeEvent: { timestamp: number };
80
+ };
81
+
82
+ // ─────────────────────────────────────────────────────────────────────────────
83
+ // Types
84
+ // ─────────────────────────────────────────────────────────────────────────────
85
+
86
+ export type DateTimePickerMode = 'date' | 'time' | 'datetime';
87
+
88
+ export interface DateTimePickerProps {
89
+ /** Current value */
90
+ value: Date;
91
+ /** Change handler */
92
+ onChange: (date: Date) => void;
93
+ /** Picker mode */
94
+ mode?: DateTimePickerMode;
95
+ /** Label shown above the input */
96
+ label?: string;
97
+ /** Placeholder text */
98
+ placeholder?: string;
99
+ /** Minimum selectable date */
100
+ minimumDate?: Date;
101
+ /** Maximum selectable date */
102
+ maximumDate?: Date;
103
+ /** Whether the picker is disabled */
104
+ disabled?: boolean;
105
+ /** Error message */
106
+ error?: string;
107
+ /** 24-hour time format (for time mode) */
108
+ is24Hour?: boolean;
109
+ /** Custom format function */
110
+ formatValue?: (date: Date, mode: DateTimePickerMode) => string;
111
+ }
112
+
113
+ // ─────────────────────────────────────────────────────────────────────────────
114
+ // Default formatters
115
+ // ─────────────────────────────────────────────────────────────────────────────
116
+
117
+ function defaultFormatDate(date: Date): string {
118
+ return date.toLocaleDateString(undefined, {
119
+ year: 'numeric',
120
+ month: 'long',
121
+ day: 'numeric',
122
+ });
123
+ }
124
+
125
+ function defaultFormatTime(date: Date, is24Hour: boolean): string {
126
+ return date.toLocaleTimeString(undefined, {
127
+ hour: '2-digit',
128
+ minute: '2-digit',
129
+ hour12: !is24Hour,
130
+ });
131
+ }
132
+
133
+ function defaultFormatDateTime(date: Date, is24Hour: boolean): string {
134
+ return `${defaultFormatDate(date)}, ${defaultFormatTime(date, is24Hour)}`;
135
+ }
136
+
137
+ // ─────────────────────────────────────────────────────────────────────────────
138
+ // DateTimePicker Component
139
+ // ─────────────────────────────────────────────────────────────────────────────
140
+
141
+ export function DateTimePicker({
142
+ value,
143
+ onChange,
144
+ mode = 'date',
145
+ label,
146
+ placeholder,
147
+ minimumDate,
148
+ maximumDate,
149
+ disabled = false,
150
+ error,
151
+ is24Hour = false,
152
+ formatValue,
153
+ }: DateTimePickerProps) {
154
+ const { colors, spacing, radius, fontSize, fontWeight } = useTheme();
155
+
156
+ const [isOpen, setIsOpen] = useState(false);
157
+ const [tempValue, setTempValue] = useState(value);
158
+ const [currentPickerMode, setCurrentPickerMode] = useState<'date' | 'time'>(
159
+ mode === 'time' ? 'time' : 'date'
160
+ );
161
+
162
+ const translateY = useSharedValue(300);
163
+ const backdropOpacity = useSharedValue(0);
164
+
165
+ const open = useCallback(() => {
166
+ setTempValue(value);
167
+ setCurrentPickerMode(mode === 'time' ? 'time' : 'date');
168
+ setIsOpen(true);
169
+ haptic('light');
170
+ backdropOpacity.value = withTiming(1, { duration: 200 });
171
+ translateY.value = withTiming(0, { duration: 250, easing: Easing.out(Easing.ease) });
172
+ }, [value, mode, backdropOpacity, translateY]);
173
+
174
+ const close = useCallback(() => {
175
+ backdropOpacity.value = withTiming(0, { duration: 150 });
176
+ translateY.value = withTiming(300, {
177
+ duration: 200,
178
+ easing: Easing.in(Easing.ease),
179
+ });
180
+ setTimeout(() => setIsOpen(false), 200);
181
+ }, [backdropOpacity, translateY]);
182
+
183
+ const handleConfirm = useCallback(() => {
184
+ if (mode === 'datetime' && currentPickerMode === 'date') {
185
+ // Move to time picker
186
+ setCurrentPickerMode('time');
187
+ haptic('light');
188
+ return;
189
+ }
190
+ onChange(tempValue);
191
+ haptic('success');
192
+ close();
193
+ }, [mode, currentPickerMode, tempValue, onChange, close]);
194
+
195
+ const handleCancel = useCallback(() => {
196
+ haptic('light');
197
+ close();
198
+ }, [close]);
199
+
200
+ const handleDateChange = useCallback(
201
+ (_event: DateTimePickerEvent, selectedDate?: Date) => {
202
+ if (selectedDate) {
203
+ setTempValue(selectedDate);
204
+ }
205
+ },
206
+ []
207
+ );
208
+
209
+ // Swipe to dismiss gesture
210
+ const panGesture = Gesture.Pan()
211
+ .onUpdate((event) => {
212
+ if (event.translationY > 0) {
213
+ translateY.value = event.translationY;
214
+ }
215
+ })
216
+ .onEnd((event) => {
217
+ if (event.translationY > 80 || event.velocityY > 500) {
218
+ translateY.value = withTiming(300, { duration: 200 });
219
+ backdropOpacity.value = withTiming(0, { duration: 150 });
220
+ runOnJS(close)();
221
+ } else {
222
+ translateY.value = withTiming(0, { duration: 150 });
223
+ }
224
+ });
225
+
226
+ const backdropStyle = useAnimatedStyle(() => ({
227
+ opacity: backdropOpacity.value,
228
+ }));
229
+
230
+ const sheetStyle = useAnimatedStyle(() => ({
231
+ transform: [{ translateY: translateY.value }],
232
+ }));
233
+
234
+ // Format displayed value
235
+ const displayValue = formatValue
236
+ ? formatValue(value, mode)
237
+ : mode === 'date'
238
+ ? defaultFormatDate(value)
239
+ : mode === 'time'
240
+ ? defaultFormatTime(value, is24Hour)
241
+ : defaultFormatDateTime(value, is24Hour);
242
+
243
+ const defaultPlaceholder =
244
+ mode === 'date'
245
+ ? 'Select date'
246
+ : mode === 'time'
247
+ ? 'Select time'
248
+ : 'Select date & time';
249
+
250
+ return (
251
+ <View style={styles.container}>
252
+ {label && (
253
+ <Text
254
+ style={[
255
+ styles.label,
256
+ {
257
+ color: colors.foreground,
258
+ fontSize: fontSize.sm,
259
+ fontWeight: fontWeight.medium,
260
+ marginBottom: spacing[2],
261
+ },
262
+ ]}
263
+ >
264
+ {label}
265
+ </Text>
266
+ )}
267
+
268
+ <Pressable
269
+ onPress={disabled ? undefined : open}
270
+ style={({ pressed }) => [
271
+ styles.input,
272
+ {
273
+ backgroundColor: colors.background,
274
+ borderColor: error ? colors.destructive : colors.border,
275
+ borderRadius: radius.lg,
276
+ paddingHorizontal: spacing[4],
277
+ paddingVertical: spacing[3],
278
+ opacity: disabled ? 0.5 : pressed ? 0.8 : 1,
279
+ },
280
+ ]}
281
+ accessibilityRole="button"
282
+ accessibilityLabel={label || defaultPlaceholder}
283
+ accessibilityState={{ disabled }}
284
+ >
285
+ <Text
286
+ style={[
287
+ styles.inputText,
288
+ {
289
+ color: value ? colors.foreground : colors.foregroundMuted,
290
+ fontSize: fontSize.base,
291
+ },
292
+ ]}
293
+ >
294
+ {displayValue || placeholder || defaultPlaceholder}
295
+ </Text>
296
+ <View style={styles.iconContainer}>
297
+ <CalendarIcon color={colors.foregroundMuted} />
298
+ </View>
299
+ </Pressable>
300
+
301
+ {error && (
302
+ <Text
303
+ style={[
304
+ styles.error,
305
+ {
306
+ color: colors.destructive,
307
+ fontSize: fontSize.sm,
308
+ marginTop: spacing[1],
309
+ },
310
+ ]}
311
+ >
312
+ {error}
313
+ </Text>
314
+ )}
315
+
316
+ {/* Bottom Sheet Modal */}
317
+ <Modal visible={isOpen} transparent animationType="none" statusBarTranslucent>
318
+ <View style={styles.modalContainer}>
319
+ <Animated.View style={[styles.backdrop, backdropStyle]}>
320
+ <Pressable style={StyleSheet.absoluteFill} onPress={handleCancel} />
321
+ </Animated.View>
322
+
323
+ <GestureDetector gesture={panGesture}>
324
+ <Animated.View
325
+ style={[
326
+ styles.sheet,
327
+ {
328
+ backgroundColor: colors.card,
329
+ paddingBottom: spacing[8],
330
+ },
331
+ sheetStyle,
332
+ ]}
333
+ >
334
+ {/* Handle */}
335
+ <View style={styles.handleContainer}>
336
+ <View style={[styles.handle, { backgroundColor: colors.border }]} />
337
+ </View>
338
+
339
+ {/* Header */}
340
+ <View
341
+ style={[
342
+ styles.header,
343
+ {
344
+ paddingHorizontal: spacing[4],
345
+ paddingBottom: spacing[3],
346
+ borderBottomWidth: 1,
347
+ borderBottomColor: colors.border,
348
+ },
349
+ ]}
350
+ >
351
+ <Pressable onPress={handleCancel}>
352
+ <Text
353
+ style={[
354
+ styles.headerButton,
355
+ { color: colors.foregroundMuted, fontSize: fontSize.base },
356
+ ]}
357
+ >
358
+ Cancel
359
+ </Text>
360
+ </Pressable>
361
+ <Text
362
+ style={[
363
+ styles.headerTitle,
364
+ {
365
+ color: colors.foreground,
366
+ fontSize: fontSize.base,
367
+ fontWeight: fontWeight.semibold,
368
+ },
369
+ ]}
370
+ >
371
+ {mode === 'datetime'
372
+ ? currentPickerMode === 'date'
373
+ ? 'Select Date'
374
+ : 'Select Time'
375
+ : mode === 'date'
376
+ ? 'Select Date'
377
+ : 'Select Time'}
378
+ </Text>
379
+ <Pressable onPress={handleConfirm}>
380
+ <Text
381
+ style={[
382
+ styles.headerButton,
383
+ {
384
+ color: colors.primary,
385
+ fontSize: fontSize.base,
386
+ fontWeight: fontWeight.semibold,
387
+ },
388
+ ]}
389
+ >
390
+ {mode === 'datetime' && currentPickerMode === 'date' ? 'Next' : 'Done'}
391
+ </Text>
392
+ </Pressable>
393
+ </View>
394
+
395
+ {/* Picker */}
396
+ <View style={styles.pickerContainer}>
397
+ {isNativePickerAvailable && DateTimePickerNative ? (
398
+ <DateTimePickerNative
399
+ value={tempValue}
400
+ mode={currentPickerMode}
401
+ display={Platform.OS === 'ios' ? 'spinner' : 'default'}
402
+ onChange={handleDateChange}
403
+ minimumDate={minimumDate}
404
+ maximumDate={maximumDate}
405
+ is24Hour={is24Hour}
406
+ style={styles.picker}
407
+ textColor={colors.foreground}
408
+ />
409
+ ) : (
410
+ <View style={styles.expoGoFallback}>
411
+ <Text
412
+ style={[
413
+ styles.expoGoTitle,
414
+ {
415
+ color: colors.foreground,
416
+ fontSize: fontSize.lg,
417
+ fontWeight: fontWeight.semibold,
418
+ },
419
+ ]}
420
+ >
421
+ Development Build Required
422
+ </Text>
423
+ <Text
424
+ style={[
425
+ styles.expoGoText,
426
+ {
427
+ color: colors.foregroundMuted,
428
+ fontSize: fontSize.sm,
429
+ },
430
+ ]}
431
+ >
432
+ The native date picker is not available in Expo Go.{'\n'}
433
+ Create a development build to use this component.
434
+ </Text>
435
+ <Text
436
+ style={[
437
+ styles.expoGoCode,
438
+ {
439
+ color: colors.primary,
440
+ fontSize: fontSize.xs,
441
+ backgroundColor: colors.backgroundSubtle,
442
+ padding: spacing[2],
443
+ borderRadius: radius.sm,
444
+ },
445
+ ]}
446
+ >
447
+ npx expo run:ios{'\n'}npx expo run:android
448
+ </Text>
449
+ </View>
450
+ )}
451
+ </View>
452
+ </Animated.View>
453
+ </GestureDetector>
454
+ </View>
455
+ </Modal>
456
+ </View>
457
+ );
458
+ }
459
+
460
+ // ─────────────────────────────────────────────────────────────────────────────
461
+ // Calendar Icon
462
+ // ─────────────────────────────────────────────────────────────────────────────
463
+
464
+ function CalendarIcon({ color }: { color: string }) {
465
+ return (
466
+ <View style={styles.calendarIcon}>
467
+ <View style={[styles.calendarTop, { backgroundColor: color }]} />
468
+ <View style={[styles.calendarBody, { borderColor: color }]}>
469
+ <View style={styles.calendarDots}>
470
+ <View style={[styles.calendarDot, { backgroundColor: color }]} />
471
+ <View style={[styles.calendarDot, { backgroundColor: color }]} />
472
+ </View>
473
+ </View>
474
+ </View>
475
+ );
476
+ }
477
+
478
+ // ─────────────────────────────────────────────────────────────────────────────
479
+ // Styles
480
+ // ─────────────────────────────────────────────────────────────────────────────
481
+
482
+ const styles = StyleSheet.create({
483
+ container: {},
484
+ label: {},
485
+ input: {
486
+ flexDirection: 'row',
487
+ alignItems: 'center',
488
+ borderWidth: 1,
489
+ },
490
+ inputText: {
491
+ flex: 1,
492
+ },
493
+ iconContainer: {
494
+ marginLeft: 8,
495
+ },
496
+ error: {},
497
+ modalContainer: {
498
+ flex: 1,
499
+ justifyContent: 'flex-end',
500
+ },
501
+ backdrop: {
502
+ ...StyleSheet.absoluteFillObject,
503
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
504
+ },
505
+ sheet: {
506
+ borderTopLeftRadius: 20,
507
+ borderTopRightRadius: 20,
508
+ },
509
+ handleContainer: {
510
+ alignItems: 'center',
511
+ paddingVertical: 12,
512
+ },
513
+ handle: {
514
+ width: 36,
515
+ height: 4,
516
+ borderRadius: 2,
517
+ },
518
+ header: {
519
+ flexDirection: 'row',
520
+ alignItems: 'center',
521
+ justifyContent: 'space-between',
522
+ },
523
+ headerButton: {},
524
+ headerTitle: {},
525
+ pickerContainer: {
526
+ alignItems: 'center',
527
+ paddingVertical: 16,
528
+ },
529
+ picker: {
530
+ width: '100%',
531
+ height: 200,
532
+ },
533
+ expoGoFallback: {
534
+ alignItems: 'center',
535
+ padding: 24,
536
+ gap: 12,
537
+ },
538
+ expoGoTitle: {
539
+ textAlign: 'center',
540
+ },
541
+ expoGoText: {
542
+ textAlign: 'center',
543
+ lineHeight: 20,
544
+ },
545
+ expoGoCode: {
546
+ fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
547
+ textAlign: 'center',
548
+ overflow: 'hidden',
549
+ },
550
+ calendarIcon: {
551
+ width: 20,
552
+ height: 20,
553
+ alignItems: 'center',
554
+ },
555
+ calendarTop: {
556
+ width: 14,
557
+ height: 3,
558
+ borderRadius: 1,
559
+ marginBottom: 1,
560
+ },
561
+ calendarBody: {
562
+ width: 16,
563
+ height: 14,
564
+ borderWidth: 1.5,
565
+ borderRadius: 2,
566
+ justifyContent: 'center',
567
+ alignItems: 'center',
568
+ },
569
+ calendarDots: {
570
+ flexDirection: 'row',
571
+ gap: 3,
572
+ },
573
+ calendarDot: {
574
+ width: 3,
575
+ height: 3,
576
+ borderRadius: 1.5,
577
+ },
578
+ });