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.
- package/README.md +34 -0
- package/babel.config.js +6 -0
- package/jest.config.js +22 -0
- package/jest.init.js +5 -0
- package/jest.setup.js +173 -0
- package/package.json +87 -0
- package/src/__tests__/a11y/accessibility.test.tsx +33 -0
- package/src/__tests__/components/badge.test.tsx +25 -0
- package/src/__tests__/components/button.test.tsx +53 -0
- package/src/__tests__/components/card.test.tsx +28 -0
- package/src/__tests__/components/input.test.tsx +33 -0
- package/src/__tests__/hooks/use-controllable.test.ts +58 -0
- package/src/__tests__/integration.test.tsx +35 -0
- package/src/__tests__/lib/utils.test.ts +23 -0
- package/src/__tests__/mocks/handlers.ts +19 -0
- package/src/components/accordion/accordion.tsx +143 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.tsx +65 -0
- package/src/components/alert/index.ts +1 -0
- package/src/components/alert-dialog/alert-dialog.tsx +145 -0
- package/src/components/alert-dialog/index.ts +1 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +18 -0
- package/src/components/aspect-ratio/index.ts +1 -0
- package/src/components/avatar/avatar.tsx +93 -0
- package/src/components/avatar/index.ts +1 -0
- package/src/components/badge/badge.tsx +64 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/breadcrumb/breadcrumb.tsx +75 -0
- package/src/components/breadcrumb/index.ts +1 -0
- package/src/components/button/button.tsx +119 -0
- package/src/components/button/index.ts +1 -0
- package/src/components/card/card.tsx +40 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/checkbox/checkbox.tsx +87 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/collapsible/collapsible.tsx +92 -0
- package/src/components/collapsible/index.ts +1 -0
- package/src/components/context-menu/context-menu.tsx +121 -0
- package/src/components/context-menu/index.ts +1 -0
- package/src/components/dialog/dialog.tsx +124 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +145 -0
- package/src/components/dropdown-menu/index.ts +1 -0
- package/src/components/form/form.tsx +84 -0
- package/src/components/form/index.ts +1 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.tsx +115 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/label/label.tsx +13 -0
- package/src/components/navigation-menu/index.ts +1 -0
- package/src/components/navigation-menu/navigation-menu.tsx +68 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.tsx +70 -0
- package/src/components/progress/index.ts +1 -0
- package/src/components/progress/progress.tsx +66 -0
- package/src/components/radio-group/index.ts +1 -0
- package/src/components/radio-group/radio-group.tsx +90 -0
- package/src/components/scroll-area/index.ts +1 -0
- package/src/components/scroll-area/scroll-area.tsx +27 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.tsx +154 -0
- package/src/components/separator/index.ts +1 -0
- package/src/components/separator/separator.tsx +37 -0
- package/src/components/sheet/index.ts +1 -0
- package/src/components/sheet/sheet.tsx +128 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.tsx +84 -0
- package/src/components/slider/index.ts +1 -0
- package/src/components/slider/slider.tsx +145 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.tsx +78 -0
- package/src/components/table/index.ts +1 -0
- package/src/components/table/table.tsx +71 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.tsx +124 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.tsx +83 -0
- package/src/components/toast/index.ts +1 -0
- package/src/components/toast/toast.tsx +124 -0
- package/src/components/toggle/index.ts +1 -0
- package/src/components/toggle/toggle.tsx +87 -0
- package/src/components/toggle-group/index.ts +1 -0
- package/src/components/toggle-group/toggle-group.tsx +87 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.tsx +103 -0
- package/src/components/typography/index.ts +1 -0
- package/src/components/typography/typography.tsx +57 -0
- package/src/context/index.ts +3 -0
- package/src/context/provider.tsx +35 -0
- package/src/context/theme-context.tsx +81 -0
- package/src/context/toast-context.tsx +63 -0
- package/src/env.d.ts +2 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/use-biometric.ts +27 -0
- package/src/hooks/use-color-scheme.ts +10 -0
- package/src/hooks/use-controllable.ts +40 -0
- package/src/hooks/use-countdown.ts +33 -0
- package/src/hooks/use-debounce.ts +18 -0
- package/src/hooks/use-disclosure.ts +14 -0
- package/src/hooks/use-haptics.ts +47 -0
- package/src/hooks/use-keyboard.ts +35 -0
- package/src/hooks/use-media-query.ts +27 -0
- package/src/hooks/use-press-animation.ts +45 -0
- package/src/hooks/use-previous.ts +14 -0
- package/src/hooks/use-scroll-header.ts +42 -0
- package/src/hooks/use-spring.ts +18 -0
- package/src/hooks/use-theme.ts +6 -0
- package/src/hooks/use-toast.ts +6 -0
- package/src/index.ts +53 -0
- package/src/lib/create-animated.tsx +25 -0
- package/src/lib/create-component.tsx +56 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/platform.ts +25 -0
- package/src/lib/types.ts +28 -0
- package/src/lib/utils.ts +35 -0
- package/src/lib/variants.ts +7 -0
- package/src/premium/ai/chat-bubble.tsx +58 -0
- package/src/premium/ai/typing-indicator.tsx +59 -0
- package/src/premium/charts/bar-chart.tsx +66 -0
- package/src/premium/charts/progress-ring.tsx +63 -0
- package/src/premium/glass/glass-bottom-sheet.tsx +50 -0
- package/src/premium/glass/glass-card.tsx +51 -0
- package/src/premium/glass/glass-header.tsx +61 -0
- package/src/premium/glass/glass-panel.tsx +32 -0
- package/src/premium/glass/glass-sidebar.tsx +56 -0
- package/src/premium/index.ts +44 -0
- package/src/premium/index2.ts +13 -0
- package/src/premium/index3.ts +1 -0
- package/src/premium/inputs/color-picker.tsx +92 -0
- package/src/premium/inputs/currency-input.tsx +50 -0
- package/src/premium/inputs/otp-input.tsx +92 -0
- package/src/premium/inputs/phone-input.tsx +58 -0
- package/src/premium/inputs/rating.tsx +51 -0
- package/src/premium/layout/carousel.tsx +57 -0
- package/src/premium/layout/floating-dock.tsx +63 -0
- package/src/premium/layout/masonry-grid.tsx +41 -0
- package/src/premium/layout/parallax-scroll.tsx +81 -0
- package/src/premium/magic/animated-number.tsx +104 -0
- package/src/premium/magic/bento-grid.tsx +55 -0
- package/src/premium/magic/border-beam.tsx +68 -0
- package/src/premium/magic/confetti.tsx +88 -0
- package/src/premium/magic/magic-card.tsx +65 -0
- package/src/premium/magic/meteors.tsx +95 -0
- package/src/premium/magic/ripple.tsx +70 -0
- package/src/premium/magic/shimmer.tsx +58 -0
- package/src/premium/magic/shiny-button.tsx +70 -0
- package/src/premium/mobile/biometric-button.tsx +82 -0
- package/src/premium/mobile/bottom-tab-bar.tsx +81 -0
- package/src/premium/mobile/fab.tsx +74 -0
- package/src/premium/mobile/haptic-pressable.tsx +53 -0
- package/src/premium/mobile/notification-badge.tsx +61 -0
- package/src/premium/mobile/pull-to-refresh.tsx +84 -0
- package/src/premium/mobile/scroll-header.tsx +57 -0
- package/src/premium/mobile/swipe-row.tsx +128 -0
- package/src/premium/mobile/swipeable-card-stack.tsx +121 -0
- package/src/premium/motion/blur-fade.tsx +51 -0
- package/src/premium/motion/fade-up.tsx +34 -0
- package/src/premium/motion/marquee.tsx +67 -0
- package/src/premium/motion/pulsating-button.tsx +95 -0
- package/src/premium/motion/slide-in.tsx +38 -0
- package/src/premium/motion/stagger-children.tsx +28 -0
- package/src/premium/motion/typing-text.tsx +55 -0
- package/src/premium/motion/word-pull-up.tsx +34 -0
- package/src/premium/onboarding/step-indicator.tsx +65 -0
- package/src/tokens/colors.ts +83 -0
- package/src/tokens/global.css +83 -0
- package/src/tokens/index.ts +10 -0
- package/src/tokens/layout.ts +121 -0
- package/src/tokens/motion.ts +94 -0
- package/src/tokens/themes/dark.ts +7 -0
- package/src/tokens/themes/default.ts +8 -0
- package/src/tokens/themes/ocean.ts +28 -0
- package/src/tokens/themes/rose.ts +29 -0
- package/src/tokens/typography.ts +127 -0
- 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';
|