cnnative-ui 1.0.0

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 (175) hide show
  1. package/README.md +34 -0
  2. package/babel.config.js +6 -0
  3. package/jest.config.js +22 -0
  4. package/jest.init.js +5 -0
  5. package/jest.setup.js +173 -0
  6. package/package.json +87 -0
  7. package/src/__tests__/a11y/accessibility.test.tsx +33 -0
  8. package/src/__tests__/components/badge.test.tsx +25 -0
  9. package/src/__tests__/components/button.test.tsx +53 -0
  10. package/src/__tests__/components/card.test.tsx +28 -0
  11. package/src/__tests__/components/input.test.tsx +33 -0
  12. package/src/__tests__/hooks/use-controllable.test.ts +58 -0
  13. package/src/__tests__/integration.test.tsx +35 -0
  14. package/src/__tests__/lib/utils.test.ts +23 -0
  15. package/src/__tests__/mocks/handlers.ts +19 -0
  16. package/src/components/accordion/accordion.tsx +143 -0
  17. package/src/components/accordion/index.ts +1 -0
  18. package/src/components/alert/alert.tsx +65 -0
  19. package/src/components/alert/index.ts +1 -0
  20. package/src/components/alert-dialog/alert-dialog.tsx +145 -0
  21. package/src/components/alert-dialog/index.ts +1 -0
  22. package/src/components/aspect-ratio/aspect-ratio.tsx +18 -0
  23. package/src/components/aspect-ratio/index.ts +1 -0
  24. package/src/components/avatar/avatar.tsx +93 -0
  25. package/src/components/avatar/index.ts +1 -0
  26. package/src/components/badge/badge.tsx +64 -0
  27. package/src/components/badge/index.ts +1 -0
  28. package/src/components/breadcrumb/breadcrumb.tsx +75 -0
  29. package/src/components/breadcrumb/index.ts +1 -0
  30. package/src/components/button/button.tsx +119 -0
  31. package/src/components/button/index.ts +1 -0
  32. package/src/components/card/card.tsx +40 -0
  33. package/src/components/card/index.ts +1 -0
  34. package/src/components/checkbox/checkbox.tsx +87 -0
  35. package/src/components/checkbox/index.ts +1 -0
  36. package/src/components/collapsible/collapsible.tsx +92 -0
  37. package/src/components/collapsible/index.ts +1 -0
  38. package/src/components/context-menu/context-menu.tsx +121 -0
  39. package/src/components/context-menu/index.ts +1 -0
  40. package/src/components/dialog/dialog.tsx +124 -0
  41. package/src/components/dialog/index.ts +1 -0
  42. package/src/components/dropdown-menu/dropdown-menu.tsx +145 -0
  43. package/src/components/dropdown-menu/index.ts +1 -0
  44. package/src/components/form/form.tsx +84 -0
  45. package/src/components/form/index.ts +1 -0
  46. package/src/components/input/index.ts +1 -0
  47. package/src/components/input/input.tsx +115 -0
  48. package/src/components/label/index.ts +1 -0
  49. package/src/components/label/label.tsx +13 -0
  50. package/src/components/navigation-menu/index.ts +1 -0
  51. package/src/components/navigation-menu/navigation-menu.tsx +68 -0
  52. package/src/components/pagination/index.ts +1 -0
  53. package/src/components/pagination/pagination.tsx +70 -0
  54. package/src/components/progress/index.ts +1 -0
  55. package/src/components/progress/progress.tsx +66 -0
  56. package/src/components/radio-group/index.ts +1 -0
  57. package/src/components/radio-group/radio-group.tsx +90 -0
  58. package/src/components/scroll-area/index.ts +1 -0
  59. package/src/components/scroll-area/scroll-area.tsx +27 -0
  60. package/src/components/select/index.ts +1 -0
  61. package/src/components/select/select.tsx +154 -0
  62. package/src/components/separator/index.ts +1 -0
  63. package/src/components/separator/separator.tsx +37 -0
  64. package/src/components/sheet/index.ts +1 -0
  65. package/src/components/sheet/sheet.tsx +128 -0
  66. package/src/components/skeleton/index.ts +1 -0
  67. package/src/components/skeleton/skeleton.tsx +84 -0
  68. package/src/components/slider/index.ts +1 -0
  69. package/src/components/slider/slider.tsx +145 -0
  70. package/src/components/switch/index.ts +1 -0
  71. package/src/components/switch/switch.tsx +78 -0
  72. package/src/components/table/index.ts +1 -0
  73. package/src/components/table/table.tsx +71 -0
  74. package/src/components/tabs/index.ts +1 -0
  75. package/src/components/tabs/tabs.tsx +124 -0
  76. package/src/components/textarea/index.ts +1 -0
  77. package/src/components/textarea/textarea.tsx +83 -0
  78. package/src/components/toast/index.ts +1 -0
  79. package/src/components/toast/toast.tsx +124 -0
  80. package/src/components/toggle/index.ts +1 -0
  81. package/src/components/toggle/toggle.tsx +87 -0
  82. package/src/components/toggle-group/index.ts +1 -0
  83. package/src/components/toggle-group/toggle-group.tsx +87 -0
  84. package/src/components/tooltip/index.ts +1 -0
  85. package/src/components/tooltip/tooltip.tsx +103 -0
  86. package/src/components/typography/index.ts +1 -0
  87. package/src/components/typography/typography.tsx +57 -0
  88. package/src/context/index.ts +3 -0
  89. package/src/context/provider.tsx +35 -0
  90. package/src/context/theme-context.tsx +81 -0
  91. package/src/context/toast-context.tsx +63 -0
  92. package/src/env.d.ts +2 -0
  93. package/src/hooks/index.ts +15 -0
  94. package/src/hooks/use-biometric.ts +27 -0
  95. package/src/hooks/use-color-scheme.ts +10 -0
  96. package/src/hooks/use-controllable.ts +40 -0
  97. package/src/hooks/use-countdown.ts +33 -0
  98. package/src/hooks/use-debounce.ts +18 -0
  99. package/src/hooks/use-disclosure.ts +14 -0
  100. package/src/hooks/use-haptics.ts +47 -0
  101. package/src/hooks/use-keyboard.ts +35 -0
  102. package/src/hooks/use-media-query.ts +27 -0
  103. package/src/hooks/use-press-animation.ts +45 -0
  104. package/src/hooks/use-previous.ts +14 -0
  105. package/src/hooks/use-scroll-header.ts +42 -0
  106. package/src/hooks/use-spring.ts +18 -0
  107. package/src/hooks/use-theme.ts +6 -0
  108. package/src/hooks/use-toast.ts +6 -0
  109. package/src/index.ts +53 -0
  110. package/src/lib/create-animated.tsx +25 -0
  111. package/src/lib/create-component.tsx +56 -0
  112. package/src/lib/index.ts +4 -0
  113. package/src/lib/platform.ts +25 -0
  114. package/src/lib/types.ts +28 -0
  115. package/src/lib/utils.ts +35 -0
  116. package/src/lib/variants.ts +7 -0
  117. package/src/premium/ai/chat-bubble.tsx +58 -0
  118. package/src/premium/ai/typing-indicator.tsx +59 -0
  119. package/src/premium/charts/bar-chart.tsx +66 -0
  120. package/src/premium/charts/progress-ring.tsx +63 -0
  121. package/src/premium/glass/glass-bottom-sheet.tsx +50 -0
  122. package/src/premium/glass/glass-card.tsx +51 -0
  123. package/src/premium/glass/glass-header.tsx +61 -0
  124. package/src/premium/glass/glass-panel.tsx +32 -0
  125. package/src/premium/glass/glass-sidebar.tsx +56 -0
  126. package/src/premium/index.ts +44 -0
  127. package/src/premium/index2.ts +13 -0
  128. package/src/premium/index3.ts +1 -0
  129. package/src/premium/inputs/color-picker.tsx +92 -0
  130. package/src/premium/inputs/currency-input.tsx +50 -0
  131. package/src/premium/inputs/otp-input.tsx +92 -0
  132. package/src/premium/inputs/phone-input.tsx +58 -0
  133. package/src/premium/inputs/rating.tsx +51 -0
  134. package/src/premium/layout/carousel.tsx +57 -0
  135. package/src/premium/layout/floating-dock.tsx +63 -0
  136. package/src/premium/layout/masonry-grid.tsx +41 -0
  137. package/src/premium/layout/parallax-scroll.tsx +81 -0
  138. package/src/premium/magic/animated-number.tsx +104 -0
  139. package/src/premium/magic/bento-grid.tsx +55 -0
  140. package/src/premium/magic/border-beam.tsx +68 -0
  141. package/src/premium/magic/confetti.tsx +88 -0
  142. package/src/premium/magic/magic-card.tsx +65 -0
  143. package/src/premium/magic/meteors.tsx +95 -0
  144. package/src/premium/magic/ripple.tsx +70 -0
  145. package/src/premium/magic/shimmer.tsx +58 -0
  146. package/src/premium/magic/shiny-button.tsx +70 -0
  147. package/src/premium/mobile/biometric-button.tsx +82 -0
  148. package/src/premium/mobile/bottom-tab-bar.tsx +81 -0
  149. package/src/premium/mobile/fab.tsx +74 -0
  150. package/src/premium/mobile/haptic-pressable.tsx +53 -0
  151. package/src/premium/mobile/notification-badge.tsx +61 -0
  152. package/src/premium/mobile/pull-to-refresh.tsx +84 -0
  153. package/src/premium/mobile/scroll-header.tsx +57 -0
  154. package/src/premium/mobile/swipe-row.tsx +128 -0
  155. package/src/premium/mobile/swipeable-card-stack.tsx +121 -0
  156. package/src/premium/motion/blur-fade.tsx +51 -0
  157. package/src/premium/motion/fade-up.tsx +34 -0
  158. package/src/premium/motion/marquee.tsx +67 -0
  159. package/src/premium/motion/pulsating-button.tsx +95 -0
  160. package/src/premium/motion/slide-in.tsx +38 -0
  161. package/src/premium/motion/stagger-children.tsx +28 -0
  162. package/src/premium/motion/typing-text.tsx +55 -0
  163. package/src/premium/motion/word-pull-up.tsx +34 -0
  164. package/src/premium/onboarding/step-indicator.tsx +65 -0
  165. package/src/tokens/colors.ts +83 -0
  166. package/src/tokens/global.css +83 -0
  167. package/src/tokens/index.ts +10 -0
  168. package/src/tokens/layout.ts +121 -0
  169. package/src/tokens/motion.ts +94 -0
  170. package/src/tokens/themes/dark.ts +7 -0
  171. package/src/tokens/themes/default.ts +8 -0
  172. package/src/tokens/themes/ocean.ts +28 -0
  173. package/src/tokens/themes/rose.ts +29 -0
  174. package/src/tokens/typography.ts +127 -0
  175. package/tsconfig.json +15 -0
@@ -0,0 +1,143 @@
1
+ import React, { forwardRef, useState } from 'react';
2
+ import { View, Pressable, type ViewProps, type PressableProps } from 'react-native';
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withSpring,
7
+ interpolate,
8
+ measure,
9
+ runOnUI,
10
+ useAnimatedRef,
11
+ } from 'react-native-reanimated';
12
+ import { ChevronDown } from 'lucide-react-native';
13
+ import { useSpring } from '../../hooks/use-spring';
14
+ import { useHaptics } from '../../hooks/use-haptics';
15
+ import { cn } from '../../lib/utils';
16
+ import { Text } from '../typography';
17
+ import { useControllableState } from '../../hooks/use-controllable';
18
+
19
+ export const Accordion = forwardRef<React.ElementRef<typeof View>, ViewProps>(
20
+ ({ className, ...props }, ref) => (
21
+ <View ref={ref} className={cn('w-full', className)} {...props} />
22
+ )
23
+ );
24
+ Accordion.displayName = 'Accordion';
25
+
26
+ const AccordionItemContext = React.createContext<{
27
+ isExpanded: boolean;
28
+ toggle: () => void;
29
+ } | null>(null);
30
+
31
+ export interface AccordionItemProps extends ViewProps {
32
+ value: string;
33
+ isExpanded?: boolean;
34
+ onExpandedChange?: (expanded: boolean) => void;
35
+ }
36
+
37
+ export const AccordionItem = forwardRef<React.ElementRef<typeof View>, AccordionItemProps>(
38
+ ({ className, value, isExpanded: isExpandedProp, onExpandedChange, children, ...props }, ref) => {
39
+ const [isExpanded, setIsExpanded] = useControllableState({
40
+ prop: isExpandedProp,
41
+ defaultProp: false,
42
+ onChange: onExpandedChange,
43
+ });
44
+ const triggerHaptic = useHaptics();
45
+
46
+ const toggle = () => {
47
+ triggerHaptic('light');
48
+ setIsExpanded(!isExpanded);
49
+ };
50
+
51
+ return (
52
+ <AccordionItemContext.Provider value={{ isExpanded, toggle }}>
53
+ <View ref={ref} className={cn('border-b border-border', className)} {...props}>
54
+ {children}
55
+ </View>
56
+ </AccordionItemContext.Provider>
57
+ );
58
+ }
59
+ );
60
+ AccordionItem.displayName = 'AccordionItem';
61
+
62
+ export interface AccordionTriggerProps extends Omit<PressableProps, 'children'> {
63
+ children?: React.ReactNode;
64
+ }
65
+
66
+ export const AccordionTrigger = forwardRef<React.ElementRef<typeof Pressable>, AccordionTriggerProps>(
67
+ ({ className, children, ...props }, ref) => {
68
+ const context = React.useContext(AccordionItemContext);
69
+ if (!context) throw new Error('AccordionTrigger must be used within AccordionItem');
70
+ const springConfig = useSpring('snappy');
71
+ const rotation = useSharedValue(context.isExpanded ? 180 : 0);
72
+
73
+ React.useEffect(() => {
74
+ rotation.value = withSpring(context.isExpanded ? 180 : 0, springConfig);
75
+ }, [context.isExpanded, springConfig]);
76
+
77
+ const iconStyle = useAnimatedStyle(() => ({
78
+ transform: [{ rotateZ: `${rotation.value}deg` }],
79
+ }));
80
+
81
+ return (
82
+ <Pressable
83
+ ref={ref}
84
+ onPress={context.toggle}
85
+ className={cn(
86
+ 'flex flex-row items-center justify-between py-4 font-medium transition-all hover:underline',
87
+ className
88
+ )}
89
+ {...props}
90
+ >
91
+ <Text className="font-medium text-foreground">{children}</Text>
92
+ <Animated.View style={iconStyle}>
93
+ <ChevronDown size={16} className="text-muted-foreground shrink-0 transition-transform duration-200" />
94
+ </Animated.View>
95
+ </Pressable>
96
+ );
97
+ }
98
+ );
99
+ AccordionTrigger.displayName = 'AccordionTrigger';
100
+
101
+ export const AccordionContent = forwardRef<React.ElementRef<typeof View>, ViewProps>(
102
+ ({ className, children, ...props }, ref) => {
103
+ const context = React.useContext(AccordionItemContext);
104
+ if (!context) throw new Error('AccordionContent must be used within AccordionItem');
105
+
106
+ const animatedRef = useAnimatedRef<View>();
107
+ const height = useSharedValue(context.isExpanded ? 100 : 0); // Initially rough guess, we need to measure
108
+ const opacity = useSharedValue(context.isExpanded ? 1 : 0);
109
+ const springConfig = useSpring('snappy');
110
+
111
+ const [contentHeight, setContentHeight] = useState(0);
112
+
113
+ React.useEffect(() => {
114
+ height.value = withSpring(context.isExpanded ? contentHeight : 0, springConfig);
115
+ opacity.value = withSpring(context.isExpanded ? 1 : 0, springConfig);
116
+ }, [context.isExpanded, contentHeight, springConfig]);
117
+
118
+ const animatedStyle = useAnimatedStyle(() => ({
119
+ height: height.value,
120
+ opacity: opacity.value,
121
+ }));
122
+
123
+ return (
124
+ <Animated.View style={[animatedStyle, { overflow: 'hidden' }]}>
125
+ <View
126
+ ref={animatedRef as any}
127
+ onLayout={(e) => {
128
+ const h = e.nativeEvent.layout.height;
129
+ if (h > 0 && contentHeight !== h) {
130
+ setContentHeight(h);
131
+ if (context.isExpanded) height.value = h;
132
+ }
133
+ }}
134
+ className={cn('pb-4 pt-0', className)}
135
+ {...props}
136
+ >
137
+ {children}
138
+ </View>
139
+ </Animated.View>
140
+ );
141
+ }
142
+ );
143
+ AccordionContent.displayName = 'AccordionContent';
@@ -0,0 +1 @@
1
+ export * from './accordion';
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ import { cva, type VariantProps } from '../../lib/variants';
4
+ import { createComponent } from '../../lib/create-component';
5
+ import { Text } from '../typography';
6
+
7
+ const alertVariants = cva(
8
+ 'relative w-full rounded-lg border px-4 py-3 text-sm flex flex-col',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-background text-foreground border-border',
13
+ destructive: 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
14
+ success: 'border-success/50 text-success [&>svg]:text-success bg-success/5',
15
+ warning: 'border-warning/50 text-warning [&>svg]:text-warning bg-warning/5',
16
+ info: 'border-info/50 text-info [&>svg]:text-info bg-info/5',
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: 'default',
21
+ },
22
+ }
23
+ );
24
+
25
+ export interface AlertProps extends ViewProps, VariantProps<typeof alertVariants> {
26
+ icon?: React.ReactNode;
27
+ }
28
+
29
+ export const Alert = React.forwardRef<React.ElementRef<typeof View>, AlertProps>(
30
+ ({ className, variant, icon, children, ...props }, ref) => {
31
+ return (
32
+ <View
33
+ ref={ref}
34
+ role="alert"
35
+ className={alertVariants({ variant, className })}
36
+ {...props}
37
+ >
38
+ {icon && (
39
+ <View className="absolute left-4 top-4">
40
+ {icon}
41
+ </View>
42
+ )}
43
+ <View className={cn('flex flex-col space-y-1', icon && 'pl-8')}>
44
+ {children}
45
+ </View>
46
+ </View>
47
+ );
48
+ }
49
+ );
50
+ Alert.displayName = 'Alert';
51
+
52
+ export const AlertTitle = createComponent<React.ElementRef<typeof Text>, React.ComponentPropsWithoutRef<typeof Text>>({
53
+ Component: Text,
54
+ baseClassName: 'mb-1 font-medium leading-none tracking-tight text-foreground',
55
+ });
56
+ AlertTitle.displayName = 'AlertTitle';
57
+
58
+ export const AlertDescription = createComponent<React.ElementRef<typeof Text>, React.ComponentPropsWithoutRef<typeof Text>>({
59
+ Component: Text,
60
+ baseClassName: 'text-sm text-foreground opacity-90',
61
+ });
62
+ AlertDescription.displayName = 'AlertDescription';
63
+
64
+ // Need to import cn locally since it's used inside the render
65
+ import { cn } from '../../lib/utils';
@@ -0,0 +1 @@
1
+ export * from './alert';
@@ -0,0 +1,145 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { View, Modal, Pressable, type ViewProps } from 'react-native';
3
+ import Animated, { FadeIn, FadeOut, ZoomIn, ZoomOut } from 'react-native-reanimated';
4
+ import { useControllableState } from '../../hooks/use-controllable';
5
+ import { cn } from '../../lib/utils';
6
+ import { Text } from '../typography';
7
+
8
+ const AlertDialogContext = React.createContext<{
9
+ open: boolean;
10
+ onOpenChange: (open: boolean) => void;
11
+ } | null>(null);
12
+
13
+ export interface AlertDialogProps {
14
+ open?: boolean;
15
+ defaultOpen?: boolean;
16
+ onOpenChange?: (open: boolean) => void;
17
+ children: React.ReactNode;
18
+ }
19
+
20
+ export const AlertDialog = ({ open: openProp, defaultOpen, onOpenChange, children }: AlertDialogProps) => {
21
+ const [open, setOpen] = useControllableState<boolean>({
22
+ ...(openProp !== undefined ? { prop: openProp } : {}),
23
+ defaultProp: defaultOpen || false,
24
+ ...(onOpenChange !== undefined ? { onChange: onOpenChange } : {}),
25
+ });
26
+
27
+ return (
28
+ <AlertDialogContext.Provider value={{ open, onOpenChange: setOpen }}>
29
+ {children}
30
+ </AlertDialogContext.Provider>
31
+ );
32
+ };
33
+
34
+ export const AlertDialogTrigger = forwardRef<React.ElementRef<typeof Pressable>, React.ComponentPropsWithoutRef<typeof Pressable>>(
35
+ ({ children, onPress, ...props }, ref) => {
36
+ const context = React.useContext(AlertDialogContext);
37
+ if (!context) throw new Error('AlertDialogTrigger must be used within AlertDialog');
38
+
39
+ return (
40
+ <Pressable ref={ref} onPress={(e) => { context.onOpenChange(true); onPress?.(e); }} {...props}>
41
+ {children}
42
+ </Pressable>
43
+ );
44
+ }
45
+ );
46
+ AlertDialogTrigger.displayName = 'AlertDialogTrigger';
47
+
48
+ export interface AlertDialogContentProps extends ViewProps {
49
+ overlayClassName?: string;
50
+ }
51
+
52
+ export const AlertDialogContent = forwardRef<React.ElementRef<typeof View>, AlertDialogContentProps>(
53
+ ({ className, overlayClassName, children, ...props }, ref) => {
54
+ const context = React.useContext(AlertDialogContext);
55
+ if (!context) throw new Error('AlertDialogContent must be used within AlertDialog');
56
+
57
+ return (
58
+ <Modal
59
+ visible={context.open}
60
+ transparent
61
+ animationType="none"
62
+ onRequestClose={() => context.onOpenChange(false)}
63
+ >
64
+ <View className={cn('flex-1 items-center justify-center', overlayClassName)}>
65
+ <Animated.View
66
+ entering={FadeIn.duration(200)}
67
+ exiting={FadeOut.duration(200)}
68
+ className="absolute inset-0 bg-black/80"
69
+ >
70
+ {/* Note: Unlike Dialog, clicking the backdrop does NOT close an AlertDialog by default */}
71
+ <View className="flex-1" />
72
+ </Animated.View>
73
+
74
+ <Animated.View
75
+ entering={ZoomIn.duration(200).springify().damping(20).stiffness(200)}
76
+ exiting={ZoomOut.duration(200)}
77
+ ref={ref}
78
+ className={cn(
79
+ 'z-50 grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg sm:rounded-lg md:w-full sm:w-[90%] w-[90%] rounded-xl mx-4',
80
+ className
81
+ )}
82
+ {...props}
83
+ >
84
+ {children}
85
+ </Animated.View>
86
+ </View>
87
+ </Modal>
88
+ );
89
+ }
90
+ );
91
+ AlertDialogContent.displayName = 'AlertDialogContent';
92
+
93
+ export const AlertDialogHeader = ({ className, ...props }: ViewProps) => (
94
+ <View className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
95
+ );
96
+ AlertDialogHeader.displayName = 'AlertDialogHeader';
97
+
98
+ export const AlertDialogFooter = ({ className, ...props }: ViewProps) => (
99
+ <View className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 gap-2 mt-6', className)} {...props} />
100
+ );
101
+ AlertDialogFooter.displayName = 'AlertDialogFooter';
102
+
103
+ export const AlertDialogTitle = forwardRef<React.ElementRef<typeof Text>, React.ComponentPropsWithoutRef<typeof Text>>(
104
+ ({ className, ...props }, ref) => (
105
+ <Text ref={ref} className={cn('text-lg font-semibold', className)} {...props} />
106
+ )
107
+ );
108
+ AlertDialogTitle.displayName = 'AlertDialogTitle';
109
+
110
+ export const AlertDialogDescription = forwardRef<React.ElementRef<typeof Text>, React.ComponentPropsWithoutRef<typeof Text>>(
111
+ ({ className, ...props }, ref) => (
112
+ <Text ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
113
+ )
114
+ );
115
+ AlertDialogDescription.displayName = 'AlertDialogDescription';
116
+
117
+ export const AlertDialogAction = forwardRef<React.ElementRef<typeof Pressable>, React.ComponentPropsWithoutRef<typeof Pressable>>(
118
+ ({ className, onPress, ...props }, ref) => {
119
+ const context = React.useContext(AlertDialogContext);
120
+ return (
121
+ <Pressable
122
+ ref={ref}
123
+ onPress={(e) => { context?.onOpenChange(false); onPress?.(e); }}
124
+ className={cn('inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', className)}
125
+ {...props}
126
+ />
127
+ );
128
+ }
129
+ );
130
+ AlertDialogAction.displayName = 'AlertDialogAction';
131
+
132
+ export const AlertDialogCancel = forwardRef<React.ElementRef<typeof Pressable>, React.ComponentPropsWithoutRef<typeof Pressable>>(
133
+ ({ className, onPress, ...props }, ref) => {
134
+ const context = React.useContext(AlertDialogContext);
135
+ return (
136
+ <Pressable
137
+ ref={ref}
138
+ onPress={(e) => { context?.onOpenChange(false); onPress?.(e); }}
139
+ className={cn('inline-flex h-10 items-center justify-center rounded-md border border-input bg-transparent px-4 py-2 text-sm font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', className)}
140
+ {...props}
141
+ />
142
+ );
143
+ }
144
+ );
145
+ AlertDialogCancel.displayName = 'AlertDialogCancel';
@@ -0,0 +1 @@
1
+ export * from './alert-dialog';
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ export interface AspectRatioProps extends ViewProps {
6
+ ratio?: number;
7
+ }
8
+
9
+ export const AspectRatio = React.forwardRef<React.ElementRef<typeof View>, AspectRatioProps>(
10
+ ({ className, ratio = 1, style, children, ...props }, ref) => {
11
+ return (
12
+ <View ref={ref} className={cn('w-full', className)} style={[style, { aspectRatio: ratio }]} {...props}>
13
+ {children}
14
+ </View>
15
+ );
16
+ }
17
+ );
18
+ AspectRatio.displayName = 'AspectRatio';
@@ -0,0 +1 @@
1
+ export * from './aspect-ratio';
@@ -0,0 +1,93 @@
1
+ import React, { useState } from 'react';
2
+ import { View, Image, type ViewProps, type ImageProps } from 'react-native';
3
+ import { cva, type VariantProps } from '../../lib/variants';
4
+ import { Text } from '../typography';
5
+ import { cn } from '../../lib/utils';
6
+
7
+ const avatarVariants = cva(
8
+ 'relative flex shrink-0 overflow-hidden rounded-full bg-muted justify-center items-center',
9
+ {
10
+ variants: {
11
+ size: {
12
+ sm: 'h-8 w-8',
13
+ default: 'h-10 w-10',
14
+ lg: 'h-14 w-14',
15
+ xl: 'h-20 w-20',
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ size: 'default',
20
+ },
21
+ }
22
+ );
23
+
24
+ export interface AvatarProps extends ViewProps, VariantProps<typeof avatarVariants> {}
25
+
26
+ export const Avatar = React.forwardRef<React.ElementRef<typeof View>, AvatarProps>(
27
+ ({ className, size, ...props }, ref) => {
28
+ return (
29
+ <View
30
+ ref={ref}
31
+ className={avatarVariants({ size, className })}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+ );
37
+ Avatar.displayName = 'Avatar';
38
+
39
+ export interface AvatarImageProps extends Omit<ImageProps, 'source'> {
40
+ src?: string;
41
+ onLoadingStatusChange?: (status: 'loading' | 'loaded' | 'error') => void;
42
+ }
43
+
44
+ export const AvatarImage = React.forwardRef<React.ElementRef<typeof Image>, AvatarImageProps>(
45
+ ({ className, src, onLoadingStatusChange, ...props }, ref) => {
46
+ const [hasError, setHasError] = useState(false);
47
+
48
+ if (hasError || !src) {
49
+ return null;
50
+ }
51
+
52
+ return (
53
+ <Image
54
+ ref={ref}
55
+ source={{ uri: src }}
56
+ className={cn('aspect-square h-full w-full', className)}
57
+ onError={() => {
58
+ setHasError(true);
59
+ onLoadingStatusChange?.('error');
60
+ }}
61
+ onLoad={() => {
62
+ setHasError(false);
63
+ onLoadingStatusChange?.('loaded');
64
+ }}
65
+ {...props}
66
+ />
67
+ );
68
+ }
69
+ );
70
+ AvatarImage.displayName = 'AvatarImage';
71
+
72
+ export interface AvatarFallbackProps extends ViewProps {
73
+ children?: React.ReactNode;
74
+ }
75
+
76
+ export const AvatarFallback = React.forwardRef<React.ElementRef<typeof View>, AvatarFallbackProps>(
77
+ ({ className, children, ...props }, ref) => {
78
+ return (
79
+ <View
80
+ ref={ref}
81
+ className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}
82
+ {...props}
83
+ >
84
+ {typeof children === 'string' ? (
85
+ <Text className="font-medium text-muted-foreground">{children}</Text>
86
+ ) : (
87
+ children
88
+ )}
89
+ </View>
90
+ );
91
+ }
92
+ );
93
+ AvatarFallback.displayName = 'AvatarFallback';
@@ -0,0 +1 @@
1
+ export * from './avatar';
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ import { cva, type VariantProps } from '../../lib/variants';
4
+ import { createComponent } from '../../lib/create-component';
5
+ import { Text } from '../typography';
6
+
7
+ const badgeVariants = cva(
8
+ 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'border-transparent bg-primary text-primary-foreground',
13
+ secondary: 'border-transparent bg-secondary text-secondary-foreground',
14
+ destructive: 'border-transparent bg-destructive text-destructive-foreground',
15
+ outline: 'text-foreground',
16
+ // Nativecn extensions
17
+ success: 'border-transparent bg-success text-success-foreground',
18
+ warning: 'border-transparent bg-warning text-warning-foreground',
19
+ info: 'border-transparent bg-info text-info-foreground',
20
+ glass: 'border-glass-border bg-glass text-foreground',
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: 'default',
25
+ },
26
+ }
27
+ );
28
+
29
+ const badgeTextVariants = cva('text-xs font-semibold', {
30
+ variants: {
31
+ variant: {
32
+ default: 'text-primary-foreground',
33
+ secondary: 'text-secondary-foreground',
34
+ destructive: 'text-destructive-foreground',
35
+ outline: 'text-foreground',
36
+ success: 'text-primary-foreground', // Assuming success fg is light
37
+ warning: 'text-foreground',
38
+ info: 'text-primary-foreground',
39
+ glass: 'text-foreground',
40
+ },
41
+ },
42
+ defaultVariants: {
43
+ variant: 'default',
44
+ },
45
+ });
46
+
47
+ export interface BadgeProps extends ViewProps, VariantProps<typeof badgeVariants> {
48
+ children?: React.ReactNode;
49
+ }
50
+
51
+ export const Badge = React.forwardRef<React.ElementRef<typeof View>, BadgeProps>(
52
+ ({ className, variant, children, ...props }, ref) => {
53
+ return (
54
+ <View ref={ref} className={badgeVariants({ variant, className })} {...props}>
55
+ {typeof children === 'string' || typeof children === 'number' ? (
56
+ <Text className={badgeTextVariants({ variant })}>{children}</Text>
57
+ ) : (
58
+ children
59
+ )}
60
+ </View>
61
+ );
62
+ }
63
+ );
64
+ Badge.displayName = 'Badge';
@@ -0,0 +1 @@
1
+ export * from './badge';
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import { View, Pressable, type ViewProps } from 'react-native';
3
+ import { ChevronRight, MoreHorizontal } from 'lucide-react-native';
4
+ import { cn } from '../../lib/utils';
5
+ import { Text } from '../typography';
6
+
7
+ export const Breadcrumb = React.forwardRef<React.ElementRef<typeof View>, ViewProps>(
8
+ ({ ...props }, ref) => <View ref={ref} {...props} />
9
+ );
10
+ Breadcrumb.displayName = 'Breadcrumb';
11
+
12
+ export const BreadcrumbList = React.forwardRef<React.ElementRef<typeof View>, ViewProps>(
13
+ ({ className, ...props }, ref) => (
14
+ <View
15
+ ref={ref}
16
+ className={cn(
17
+ 'flex flex-row flex-wrap items-center break-words text-sm text-muted-foreground sm:gap-2.5',
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ )
23
+ );
24
+ BreadcrumbList.displayName = 'BreadcrumbList';
25
+
26
+ export const BreadcrumbItem = React.forwardRef<React.ElementRef<typeof View>, ViewProps>(
27
+ ({ className, ...props }, ref) => (
28
+ <View ref={ref} className={cn('inline-flex items-center gap-1.5', className)} {...props} />
29
+ )
30
+ );
31
+ BreadcrumbItem.displayName = 'BreadcrumbItem';
32
+
33
+ export const BreadcrumbLink = React.forwardRef<React.ElementRef<typeof Pressable>, React.ComponentPropsWithoutRef<typeof Pressable>>(
34
+ ({ className, children, ...props }, ref) => {
35
+ return (
36
+ <Pressable ref={ref} className={cn('transition-colors hover:text-foreground', className)} {...props}>
37
+ {typeof children === 'string' ? <Text className="text-sm text-muted-foreground hover:text-foreground transition-colors">{children}</Text> : children}
38
+ </Pressable>
39
+ );
40
+ }
41
+ );
42
+ BreadcrumbLink.displayName = 'BreadcrumbLink';
43
+
44
+ export const BreadcrumbPage = React.forwardRef<React.ElementRef<typeof Text>, React.ComponentPropsWithoutRef<typeof Text>>(
45
+ ({ className, ...props }, ref) => (
46
+ <Text
47
+ ref={ref}
48
+ role="link"
49
+ aria-disabled={true}
50
+ aria-current="page"
51
+ className={cn('text-sm font-normal text-foreground', className)}
52
+ {...props}
53
+ />
54
+ )
55
+ );
56
+ BreadcrumbPage.displayName = 'BreadcrumbPage';
57
+
58
+ export const BreadcrumbSeparator = ({ children, className, ...props }: ViewProps) => (
59
+ <View role="presentation" aria-hidden={true} className={cn('px-1', className)} {...props}>
60
+ {children ?? <ChevronRight size={14} className="text-muted-foreground" />}
61
+ </View>
62
+ );
63
+ BreadcrumbSeparator.displayName = 'BreadcrumbSeparator';
64
+
65
+ export const BreadcrumbEllipsis = ({ className, ...props }: ViewProps) => (
66
+ <View
67
+ role="presentation"
68
+ aria-hidden={true}
69
+ className={cn('flex h-9 w-9 items-center justify-center', className)}
70
+ {...props}
71
+ >
72
+ <MoreHorizontal size={16} className="text-muted-foreground" />
73
+ </View>
74
+ );
75
+ BreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis';
@@ -0,0 +1 @@
1
+ export * from './breadcrumb';