@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,431 @@
1
+ /**
2
+ * Carousel
3
+ *
4
+ * Auto-playing image/content slideshow with animated pagination indicators.
5
+ * Supports swipe gestures, autoplay, parallax effects, and custom indicators.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Basic carousel with children
10
+ * <Carousel>
11
+ * <CarouselItem>
12
+ * <Image source={{ uri: 'https://...' }} style={{ width: '100%', height: 200 }} />
13
+ * </CarouselItem>
14
+ * <CarouselItem>
15
+ * <View style={{ backgroundColor: 'blue', height: 200 }} />
16
+ * </CarouselItem>
17
+ * </Carousel>
18
+ *
19
+ * // With data + renderItem (for parallax support)
20
+ * <Carousel
21
+ * data={slides}
22
+ * renderItem={({ item, index, scrollX, width }) => (
23
+ * <OnboardingSlide item={item} index={index} scrollX={scrollX} width={width} />
24
+ * )}
25
+ * />
26
+ *
27
+ * // With autoplay
28
+ * <Carousel autoplay autoplayInterval={5000}>
29
+ * {items}
30
+ * </Carousel>
31
+ * ```
32
+ */
33
+
34
+ import React, { useRef, useState, useEffect, useCallback, forwardRef, useImperativeHandle } from 'react';
35
+ import {
36
+ View,
37
+ StyleSheet,
38
+ ViewStyle,
39
+ Dimensions,
40
+ NativeSyntheticEvent,
41
+ NativeScrollEvent,
42
+ LayoutChangeEvent,
43
+ Pressable,
44
+ FlatList,
45
+ } from 'react-native';
46
+ import Animated, {
47
+ useAnimatedStyle,
48
+ useSharedValue,
49
+ interpolate,
50
+ Extrapolation,
51
+ SharedValue,
52
+ } from 'react-native-reanimated';
53
+ import { useTheme } from '@nativeui/core';
54
+
55
+ const { width: SCREEN_WIDTH } = Dimensions.get('window');
56
+
57
+ // ─────────────────────────────────────────────────────────────────────────────
58
+ // Types
59
+ // ─────────────────────────────────────────────────────────────────────────────
60
+
61
+ export interface CarouselRenderItemInfo<T> {
62
+ item: T;
63
+ index: number;
64
+ scrollX: SharedValue<number>;
65
+ width: number;
66
+ }
67
+
68
+ export interface CarouselProps<T = any> {
69
+ /** Carousel slides (alternative to data + renderItem) */
70
+ children?: React.ReactNode;
71
+ /** Data array for renderItem pattern */
72
+ data?: T[];
73
+ /** Render function for data items - receives scrollX for parallax effects */
74
+ renderItem?: (info: CarouselRenderItemInfo<T>) => React.ReactElement;
75
+ /** Enable autoplay */
76
+ autoplay?: boolean;
77
+ /** Autoplay interval in ms */
78
+ autoplayInterval?: number;
79
+ /** Show pagination indicators */
80
+ showIndicators?: boolean;
81
+ /** Indicator position */
82
+ indicatorPosition?: 'bottom' | 'top';
83
+ /** Called when active slide changes */
84
+ onSlideChange?: (index: number) => void;
85
+ /** Initial slide index */
86
+ initialIndex?: number;
87
+ /** Container style */
88
+ style?: ViewStyle;
89
+ /** Content height (defaults to auto) */
90
+ height?: number;
91
+ /** Indicator style variant */
92
+ indicatorStyle?: 'dot' | 'line';
93
+ }
94
+
95
+ export interface CarouselItemProps {
96
+ /** Slide content */
97
+ children: React.ReactNode;
98
+ /** Item style */
99
+ style?: ViewStyle;
100
+ }
101
+
102
+ export interface CarouselRef {
103
+ /** Scroll to a specific slide index */
104
+ scrollToIndex: (index: number, animated?: boolean) => void;
105
+ /** Get the current active index */
106
+ getActiveIndex: () => number;
107
+ }
108
+
109
+ // ─────────────────────────────────────────────────────────────────────────────
110
+ // CarouselItem Component
111
+ // ─────────────────────────────────────────────────────────────────────────────
112
+
113
+ export function CarouselItem({ children, style }: CarouselItemProps) {
114
+ return (
115
+ <View style={[styles.item, style]}>
116
+ {children}
117
+ </View>
118
+ );
119
+ }
120
+
121
+ // ─────────────────────────────────────────────────────────────────────────────
122
+ // Animated Pagination Dot Component
123
+ // ─────────────────────────────────────────────────────────────────────────────
124
+
125
+ interface AnimatedDotProps {
126
+ index: number;
127
+ scrollX: SharedValue<number>;
128
+ width: number;
129
+ activeColor: string;
130
+ inactiveColor: string;
131
+ onPress: () => void;
132
+ variant: 'dot' | 'line';
133
+ }
134
+
135
+ function AnimatedDot({
136
+ index,
137
+ scrollX,
138
+ width,
139
+ activeColor,
140
+ inactiveColor,
141
+ onPress,
142
+ variant,
143
+ }: AnimatedDotProps) {
144
+ const dotStyle = useAnimatedStyle(() => {
145
+ const inputRange = [
146
+ (index - 1) * width,
147
+ index * width,
148
+ (index + 1) * width,
149
+ ];
150
+
151
+ const dotWidth = interpolate(
152
+ scrollX.value,
153
+ inputRange,
154
+ variant === 'line' ? [8, 24, 8] : [8, 12, 8],
155
+ Extrapolation.CLAMP
156
+ );
157
+
158
+ const opacity = interpolate(
159
+ scrollX.value,
160
+ inputRange,
161
+ [0.4, 1, 0.4],
162
+ Extrapolation.CLAMP
163
+ );
164
+
165
+ const scale = interpolate(
166
+ scrollX.value,
167
+ inputRange,
168
+ [0.8, 1, 0.8],
169
+ Extrapolation.CLAMP
170
+ );
171
+
172
+ return {
173
+ width: dotWidth,
174
+ opacity,
175
+ transform: [{ scale }],
176
+ backgroundColor: activeColor,
177
+ };
178
+ });
179
+
180
+ return (
181
+ <Pressable onPress={onPress}>
182
+ <Animated.View style={[styles.dot, dotStyle]} />
183
+ </Pressable>
184
+ );
185
+ }
186
+
187
+ // ─────────────────────────────────────────────────────────────────────────────
188
+ // Carousel Component
189
+ // ─────────────────────────────────────────────────────────────────────────────
190
+
191
+ function CarouselInner<T = any>(
192
+ {
193
+ children,
194
+ data,
195
+ renderItem,
196
+ autoplay = false,
197
+ autoplayInterval = 4000,
198
+ showIndicators = true,
199
+ indicatorPosition = 'bottom',
200
+ onSlideChange,
201
+ initialIndex = 0,
202
+ style,
203
+ height,
204
+ indicatorStyle = 'line',
205
+ }: CarouselProps<T>,
206
+ ref: React.ForwardedRef<CarouselRef>
207
+ ) {
208
+ const { colors, spacing } = useTheme();
209
+ const flatListRef = useRef<FlatList>(null);
210
+ const [activeIndex, setActiveIndex] = useState(initialIndex);
211
+ const [containerWidth, setContainerWidth] = useState(SCREEN_WIDTH);
212
+ const autoplayRef = useRef<ReturnType<typeof setInterval> | null>(null);
213
+ const scrollX = useSharedValue(0);
214
+
215
+ // Determine slide count from children or data
216
+ const childArray = children ? React.Children.toArray(children) : [];
217
+ const slideCount = data ? data.length : childArray.length;
218
+
219
+ // Handle container layout to get accurate width
220
+ const handleLayout = useCallback((event: LayoutChangeEvent) => {
221
+ const { width } = event.nativeEvent.layout;
222
+ setContainerWidth(width);
223
+ }, []);
224
+
225
+ // Scroll to specific slide
226
+ const scrollToSlide = useCallback((index: number, animated = true) => {
227
+ const clampedIndex = Math.max(0, Math.min(index, slideCount - 1));
228
+ flatListRef.current?.scrollToIndex({ index: clampedIndex, animated });
229
+ }, [slideCount]);
230
+
231
+ // Expose imperative methods via ref
232
+ useImperativeHandle(ref, () => ({
233
+ scrollToIndex: (index: number, animated = true) => {
234
+ scrollToSlide(index, animated);
235
+ setActiveIndex(index);
236
+ onSlideChange?.(index);
237
+ },
238
+ getActiveIndex: () => activeIndex,
239
+ }), [scrollToSlide, activeIndex, onSlideChange]);
240
+
241
+ // Handle scroll to update scrollX shared value
242
+ const handleScroll = useCallback(
243
+ (event: NativeSyntheticEvent<NativeScrollEvent>) => {
244
+ scrollX.value = event.nativeEvent.contentOffset.x;
245
+ },
246
+ []
247
+ );
248
+
249
+ // Handle scroll end to determine active slide
250
+ const handleMomentumScrollEnd = useCallback(
251
+ (event: NativeSyntheticEvent<NativeScrollEvent>) => {
252
+ const offsetX = event.nativeEvent.contentOffset.x;
253
+ const newIndex = Math.round(offsetX / containerWidth);
254
+ const clampedIndex = Math.max(0, Math.min(newIndex, slideCount - 1));
255
+
256
+ if (clampedIndex !== activeIndex) {
257
+ setActiveIndex(clampedIndex);
258
+ onSlideChange?.(clampedIndex);
259
+ }
260
+ },
261
+ [containerWidth, slideCount, activeIndex, onSlideChange]
262
+ );
263
+
264
+ // Autoplay effect
265
+ useEffect(() => {
266
+ if (!autoplay || slideCount <= 1) return;
267
+
268
+ const startAutoplay = () => {
269
+ autoplayRef.current = setInterval(() => {
270
+ setActiveIndex((current) => {
271
+ const nextIndex = (current + 1) % slideCount;
272
+ scrollToSlide(nextIndex);
273
+ return nextIndex;
274
+ });
275
+ }, autoplayInterval);
276
+ };
277
+
278
+ startAutoplay();
279
+
280
+ return () => {
281
+ if (autoplayRef.current) {
282
+ clearInterval(autoplayRef.current);
283
+ }
284
+ };
285
+ }, [autoplay, autoplayInterval, slideCount, scrollToSlide]);
286
+
287
+ // Stop autoplay on user interaction
288
+ const handleScrollBeginDrag = useCallback(() => {
289
+ if (autoplayRef.current) {
290
+ clearInterval(autoplayRef.current);
291
+ autoplayRef.current = null;
292
+ }
293
+ }, []);
294
+
295
+ // Restart autoplay after user interaction
296
+ const handleScrollEndDrag = useCallback(() => {
297
+ if (autoplay && slideCount > 1 && !autoplayRef.current) {
298
+ autoplayRef.current = setInterval(() => {
299
+ setActiveIndex((current) => {
300
+ const nextIndex = (current + 1) % slideCount;
301
+ scrollToSlide(nextIndex);
302
+ return nextIndex;
303
+ });
304
+ }, autoplayInterval);
305
+ }
306
+ }, [autoplay, autoplayInterval, slideCount, scrollToSlide]);
307
+
308
+ const handleDotPress = (index: number) => {
309
+ scrollToSlide(index);
310
+ setActiveIndex(index);
311
+ onSlideChange?.(index);
312
+ };
313
+
314
+ // Render indicators
315
+ const indicators = showIndicators && slideCount > 1 && (
316
+ <View
317
+ style={[
318
+ styles.indicators,
319
+ indicatorPosition === 'top' ? styles.indicatorsTop : styles.indicatorsBottom,
320
+ ]}
321
+ >
322
+ {Array.from({ length: slideCount }).map((_, index) => (
323
+ <AnimatedDot
324
+ key={index}
325
+ index={index}
326
+ scrollX={scrollX}
327
+ width={containerWidth}
328
+ activeColor={colors.primary}
329
+ inactiveColor={colors.border}
330
+ onPress={() => handleDotPress(index)}
331
+ variant={indicatorStyle}
332
+ />
333
+ ))}
334
+ </View>
335
+ );
336
+
337
+ // Render slide content
338
+ const renderSlide = useCallback(
339
+ ({ item, index }: { item: T | React.ReactNode; index: number }) => {
340
+ if (data && renderItem) {
341
+ return (
342
+ <View style={{ width: containerWidth, flex: 1 }}>
343
+ {renderItem({ item: item as T, index, scrollX, width: containerWidth })}
344
+ </View>
345
+ );
346
+ }
347
+ return (
348
+ <View style={{ width: containerWidth, flex: 1 }}>
349
+ {item as React.ReactNode}
350
+ </View>
351
+ );
352
+ },
353
+ [containerWidth, data, renderItem, scrollX]
354
+ );
355
+
356
+ const listData = data || childArray;
357
+
358
+ return (
359
+ <View
360
+ style={[styles.container, { height }, style]}
361
+ onLayout={handleLayout}
362
+ >
363
+ {indicatorPosition === 'top' && indicators}
364
+
365
+ <FlatList
366
+ ref={flatListRef}
367
+ data={listData as any[]}
368
+ renderItem={renderSlide}
369
+ keyExtractor={(_, index) => `carousel-${index}`}
370
+ horizontal
371
+ pagingEnabled
372
+ showsHorizontalScrollIndicator={false}
373
+ onScroll={handleScroll}
374
+ scrollEventThrottle={16}
375
+ onMomentumScrollEnd={handleMomentumScrollEnd}
376
+ onScrollBeginDrag={handleScrollBeginDrag}
377
+ onScrollEndDrag={handleScrollEndDrag}
378
+ bounces={false}
379
+ decelerationRate="fast"
380
+ initialScrollIndex={initialIndex}
381
+ getItemLayout={(_, index) => ({
382
+ length: containerWidth,
383
+ offset: containerWidth * index,
384
+ index,
385
+ })}
386
+ style={{ flex: 1 }}
387
+ contentContainerStyle={{ flexGrow: 1 }}
388
+ accessibilityRole="adjustable"
389
+ accessibilityValue={{
390
+ min: 0,
391
+ max: slideCount - 1,
392
+ now: activeIndex,
393
+ text: `Slide ${activeIndex + 1} of ${slideCount}`,
394
+ }}
395
+ />
396
+
397
+ {indicatorPosition === 'bottom' && indicators}
398
+ </View>
399
+ );
400
+ }
401
+
402
+ // Export with forwardRef while preserving generic type
403
+ export const Carousel = forwardRef(CarouselInner) as <T = any>(
404
+ props: CarouselProps<T> & { ref?: React.ForwardedRef<CarouselRef> }
405
+ ) => React.ReactElement;
406
+
407
+ // ─────────────────────────────────────────────────────────────────────────────
408
+ // Styles
409
+ // ─────────────────────────────────────────────────────────────────────────────
410
+
411
+ const styles = StyleSheet.create({
412
+ container: {
413
+ overflow: 'hidden',
414
+ },
415
+ item: {
416
+ flex: 1,
417
+ },
418
+ indicators: {
419
+ flexDirection: 'row',
420
+ justifyContent: 'center',
421
+ alignItems: 'center',
422
+ gap: 6,
423
+ paddingVertical: 8,
424
+ },
425
+ indicatorsTop: {},
426
+ indicatorsBottom: {},
427
+ dot: {
428
+ height: 8,
429
+ borderRadius: 4,
430
+ },
431
+ });
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Checkbox
3
+ *
4
+ * An accessible checkbox component with animated checkmark.
5
+ * Uses design tokens for consistent styling.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const [checked, setChecked] = useState(false);
10
+ * <Checkbox checked={checked} onCheckedChange={setChecked} />
11
+ * <Checkbox checked={checked} onCheckedChange={setChecked} label="Accept terms" />
12
+ * <Checkbox indeterminate />
13
+ * ```
14
+ */
15
+
16
+ import React, { useEffect, useCallback } from 'react';
17
+ import {
18
+ Pressable,
19
+ View,
20
+ Text,
21
+ StyleSheet,
22
+ ViewStyle,
23
+ AccessibilityInfo,
24
+ } from 'react-native';
25
+ import Animated, {
26
+ useSharedValue,
27
+ useAnimatedStyle,
28
+ withSpring,
29
+ interpolate,
30
+ interpolateColor,
31
+ Extrapolation,
32
+ } from 'react-native-reanimated';
33
+ import Svg, { Path } from 'react-native-svg';
34
+ import { useTheme } from '@nativeui/core';
35
+ import { haptic } from '@nativeui/core';
36
+
37
+ const AnimatedSvg = Animated.createAnimatedComponent(Svg);
38
+
39
+ export type CheckboxSize = 'sm' | 'md' | 'lg';
40
+
41
+ export interface CheckboxProps {
42
+ /** Whether the checkbox is checked */
43
+ checked?: boolean;
44
+ /** Callback when checked state changes */
45
+ onCheckedChange?: (checked: boolean) => void;
46
+ /** Show indeterminate state (overrides checked) */
47
+ indeterminate?: boolean;
48
+ /** Disable the checkbox */
49
+ disabled?: boolean;
50
+ /** Label text shown next to checkbox */
51
+ label?: string;
52
+ /** Description text below label */
53
+ description?: string;
54
+ /** Additional accessibility label */
55
+ accessibilityLabel?: string;
56
+ /** Size variant */
57
+ size?: CheckboxSize;
58
+ /** Additional container styles */
59
+ style?: ViewStyle;
60
+ }
61
+
62
+ export function Checkbox({
63
+ checked = false,
64
+ onCheckedChange,
65
+ indeterminate = false,
66
+ disabled = false,
67
+ label,
68
+ description,
69
+ accessibilityLabel,
70
+ size = 'md',
71
+ style,
72
+ }: CheckboxProps) {
73
+ const { colors, components, platformShadow, springs } = useTheme();
74
+ const tokens = components.checkbox[size];
75
+ const progress = useSharedValue(checked || indeterminate ? 1 : 0);
76
+ const scale = useSharedValue(1);
77
+ const [reduceMotion, setReduceMotion] = React.useState(false);
78
+
79
+ // Check for reduce motion preference
80
+ useEffect(() => {
81
+ AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
82
+ const subscription = AccessibilityInfo.addEventListener(
83
+ 'reduceMotionChanged',
84
+ setReduceMotion
85
+ );
86
+ return () => subscription.remove();
87
+ }, []);
88
+
89
+ // Animate on state change
90
+ useEffect(() => {
91
+ const target = checked || indeterminate ? 1 : 0;
92
+ if (reduceMotion) {
93
+ progress.value = target;
94
+ } else {
95
+ progress.value = withSpring(target, springs.snappy);
96
+ }
97
+ }, [checked, indeterminate, reduceMotion, springs.snappy]);
98
+
99
+ const handlePressIn = useCallback(() => {
100
+ if (!disabled) {
101
+ scale.value = withSpring(0.92, springs.snappy);
102
+ }
103
+ }, [disabled, springs.snappy]);
104
+
105
+ const handlePressOut = useCallback(() => {
106
+ scale.value = withSpring(1, springs.snappy);
107
+ }, [springs.snappy]);
108
+
109
+ const handlePress = useCallback(() => {
110
+ if (disabled || !onCheckedChange) return;
111
+ haptic('light');
112
+ onCheckedChange(!checked);
113
+ }, [disabled, onCheckedChange, checked]);
114
+
115
+ const boxAnimatedStyle = useAnimatedStyle(() => ({
116
+ backgroundColor: interpolateColor(
117
+ progress.value,
118
+ [0, 1],
119
+ ['transparent', colors.primary]
120
+ ),
121
+ borderColor: interpolateColor(
122
+ progress.value,
123
+ [0, 1],
124
+ [colors.border, colors.primary]
125
+ ),
126
+ transform: [{ scale: scale.value }],
127
+ }));
128
+
129
+ const iconAnimatedStyle = useAnimatedStyle(() => ({
130
+ opacity: progress.value,
131
+ transform: [
132
+ {
133
+ scale: interpolate(
134
+ progress.value,
135
+ [0, 1],
136
+ [0.5, 1],
137
+ Extrapolation.CLAMP
138
+ ),
139
+ },
140
+ ],
141
+ }));
142
+
143
+ // Checkmark SVG path
144
+ const checkPath = "M4 12l5 5L20 6";
145
+ // Indeterminate line path
146
+ const indeterminatePath = "M6 12h12";
147
+
148
+ return (
149
+ <Pressable
150
+ onPressIn={handlePressIn}
151
+ onPressOut={handlePressOut}
152
+ onPress={handlePress}
153
+ disabled={disabled}
154
+ style={[
155
+ styles.container,
156
+ { gap: tokens.gap },
157
+ style,
158
+ ]}
159
+ accessible
160
+ accessibilityRole="checkbox"
161
+ accessibilityLabel={accessibilityLabel ?? label}
162
+ accessibilityState={{
163
+ checked: indeterminate ? 'mixed' : checked,
164
+ disabled,
165
+ }}
166
+ >
167
+ <Animated.View
168
+ style={[
169
+ styles.box,
170
+ {
171
+ width: tokens.size,
172
+ height: tokens.size,
173
+ borderRadius: tokens.borderRadius,
174
+ borderWidth: tokens.borderWidth,
175
+ },
176
+ boxAnimatedStyle,
177
+ (checked || indeterminate) && platformShadow('sm'),
178
+ disabled && styles.disabled,
179
+ ]}
180
+ >
181
+ <AnimatedSvg
182
+ width={tokens.iconSize}
183
+ height={tokens.iconSize}
184
+ viewBox="0 0 24 24"
185
+ fill="none"
186
+ style={iconAnimatedStyle}
187
+ >
188
+ <Path
189
+ d={indeterminate ? indeterminatePath : checkPath}
190
+ stroke={colors.primaryForeground}
191
+ strokeWidth={3}
192
+ strokeLinecap="round"
193
+ strokeLinejoin="round"
194
+ />
195
+ </AnimatedSvg>
196
+ </Animated.View>
197
+
198
+ {(label || description) && (
199
+ <View style={styles.labelContainer}>
200
+ {label && (
201
+ <Text
202
+ style={[
203
+ styles.label,
204
+ {
205
+ fontSize: tokens.labelFontSize,
206
+ color: disabled ? colors.foregroundMuted : colors.foreground,
207
+ },
208
+ ]}
209
+ >
210
+ {label}
211
+ </Text>
212
+ )}
213
+ {description && (
214
+ <Text
215
+ style={[
216
+ styles.description,
217
+ { color: colors.foregroundMuted },
218
+ ]}
219
+ >
220
+ {description}
221
+ </Text>
222
+ )}
223
+ </View>
224
+ )}
225
+ </Pressable>
226
+ );
227
+ }
228
+
229
+ const styles = StyleSheet.create({
230
+ container: {
231
+ flexDirection: 'row',
232
+ alignItems: 'flex-start',
233
+ },
234
+ box: {
235
+ alignItems: 'center',
236
+ justifyContent: 'center',
237
+ overflow: 'hidden',
238
+ },
239
+ disabled: {
240
+ opacity: 0.5,
241
+ },
242
+ labelContainer: {
243
+ flex: 1,
244
+ gap: 2,
245
+ },
246
+ label: {
247
+ fontWeight: '500',
248
+ },
249
+ description: {
250
+ fontSize: 13,
251
+ },
252
+ });