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,14 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ /**
4
+ * Returns the previous state/value from the last render.
5
+ */
6
+ export function usePrevious<T>(value: T): T | undefined {
7
+ const ref = useRef<T>(undefined);
8
+
9
+ useEffect(() => {
10
+ ref.current = value;
11
+ }, [value]);
12
+
13
+ return ref.current;
14
+ }
@@ -0,0 +1,42 @@
1
+ import { useSharedValue, useAnimatedScrollHandler, useAnimatedStyle, interpolate, Extrapolate } from 'react-native-reanimated';
2
+
3
+ export interface UseScrollHeaderProps {
4
+ maxHeight?: number;
5
+ minHeight?: number;
6
+ }
7
+
8
+ /**
9
+ * Hook to create collapsing header animations on scroll.
10
+ */
11
+ export function useScrollHeader({ maxHeight = 200, minHeight = 80 }: UseScrollHeaderProps = {}) {
12
+ const scrollY = useSharedValue(0);
13
+ const scrollDistance = maxHeight - minHeight;
14
+
15
+ const onScroll = useAnimatedScrollHandler((event) => {
16
+ scrollY.value = event.contentOffset.y;
17
+ });
18
+
19
+ const headerStyle = useAnimatedStyle(() => {
20
+ const height = interpolate(
21
+ scrollY.value,
22
+ [0, scrollDistance],
23
+ [maxHeight, minHeight],
24
+ Extrapolate.CLAMP
25
+ );
26
+
27
+ return { height };
28
+ });
29
+
30
+ const titleStyle = useAnimatedStyle(() => {
31
+ const opacity = interpolate(
32
+ scrollY.value,
33
+ [0, scrollDistance / 2, scrollDistance],
34
+ [1, 0.5, 0],
35
+ Extrapolate.CLAMP
36
+ );
37
+
38
+ return { opacity };
39
+ });
40
+
41
+ return { onScroll, scrollY, headerStyle, titleStyle };
42
+ }
@@ -0,0 +1,18 @@
1
+ import { useReducedMotion } from 'react-native-reanimated';
2
+ import { springPresets, type SpringPreset } from '../tokens/motion';
3
+ import type { WithSpringConfig } from 'react-native-reanimated';
4
+
5
+ /**
6
+ * Returns a reanimated spring config based on a semantic preset,
7
+ * automatically falling back to an instant transition if the user
8
+ * has "Reduce Motion" enabled in their OS settings.
9
+ */
10
+ export function useSpring(preset: SpringPreset = 'smooth'): WithSpringConfig {
11
+ const reducedMotion = useReducedMotion();
12
+
13
+ if (reducedMotion) {
14
+ return springPresets.instant;
15
+ }
16
+
17
+ return springPresets[preset];
18
+ }
@@ -0,0 +1,6 @@
1
+ import { useThemeContext } from '../context/theme-context';
2
+
3
+ /**
4
+ * Hook to access the current theme, color scheme, and multi-theme switcher.
5
+ */
6
+ export const useTheme = useThemeContext;
@@ -0,0 +1,6 @@
1
+ import { useToastContext } from '../context/toast-context';
2
+
3
+ /**
4
+ * Hook to trigger and manage toasts.
5
+ */
6
+ export const useToast = useToastContext;
package/src/index.ts ADDED
@@ -0,0 +1,53 @@
1
+ // Tokens & Design System
2
+ export * from './tokens';
3
+
4
+ // Core Library Utilities
5
+ export * from './lib';
6
+
7
+ // Hooks
8
+ export * from './hooks';
9
+
10
+ // Context & Providers
11
+ export * from './context';
12
+
13
+ // Layer 2: Styled Components
14
+ export * from './components/typography';
15
+ export * from './components/button';
16
+ export * from './components/badge';
17
+ export * from './components/card';
18
+ export * from './components/separator';
19
+ export * from './components/label';
20
+ export * from './components/input';
21
+ export * from './components/skeleton';
22
+ export * from './components/switch';
23
+ export * from './components/checkbox';
24
+ export * from './components/progress';
25
+ export * from './components/alert';
26
+ export * from './components/slider';
27
+ export * from './components/textarea';
28
+ export * from './components/avatar';
29
+ export * from './components/aspect-ratio';
30
+ export * from './components/scroll-area';
31
+ export * from './components/toggle';
32
+ export * from './components/toggle-group';
33
+ export * from './components/radio-group';
34
+ export * from './components/breadcrumb';
35
+ export * from './components/pagination';
36
+ export * from './components/accordion';
37
+ export * from './components/collapsible';
38
+ export * from './components/tabs';
39
+ export * from './components/toast';
40
+ export * from './components/dialog';
41
+ export * from './components/sheet';
42
+ export * from './components/tooltip';
43
+ export * from './components/dropdown-menu';
44
+ export * from './components/alert-dialog';
45
+ export * from './components/select';
46
+ export * from './components/form';
47
+ export * from './components/context-menu';
48
+ export * from './components/table';
49
+ export * from './components/navigation-menu';
50
+
51
+ // Note: Layer 3 (Premium) components are exported from their respective directories
52
+ // e.g., import { SwipeRow } from '@nativecn/ui/premium/mobile/swipe-row'
53
+ export * from './premium';
@@ -0,0 +1,25 @@
1
+ import React, { forwardRef } from 'react';
2
+ import Animated from 'react-native-reanimated';
3
+ import { createComponent, type CreateComponentOptions } from './create-component';
4
+
5
+ /**
6
+ * An animated component factory.
7
+ * Wraps a component in `Animated.createAnimatedComponent` and then applies the `createComponent` factory logic.
8
+ */
9
+ export function createAnimatedComponent<
10
+ TRef,
11
+ TProps extends { className?: string; style?: any }
12
+ >(options: CreateComponentOptions<TProps, any>) {
13
+ // First, make the underlying component animatable if it isn't already
14
+ const AnimatableComponent = Animated.createAnimatedComponent(options.Component as any);
15
+
16
+ // Then run it through our standard component factory
17
+ const ForwardedComponent = createComponent<TRef, TProps>({
18
+ ...options,
19
+ Component: AnimatableComponent,
20
+ });
21
+
22
+ ForwardedComponent.displayName = `NativecnAnimated(${(options.Component as any).displayName || (options.Component as any).name || 'Unknown'})`;
23
+
24
+ return ForwardedComponent;
25
+ }
@@ -0,0 +1,56 @@
1
+ import React, { forwardRef } from 'react';
2
+ import type { ViewStyle, TextStyle, ImageStyle } from 'react-native';
3
+ import { cn } from './utils';
4
+
5
+ /**
6
+ * A highly reusable component factory that automatically handles:
7
+ * - ref forwarding
8
+ * - tailwind class merging via `cn()`
9
+ * - default base classes
10
+ * - CVA variant resolution (if a variant function is provided)
11
+ */
12
+
13
+ export interface CreateComponentOptions<TProps, TVariants> {
14
+ /** The base React Native component to wrap (e.g. View, Text, Pressable) */
15
+ Component: React.ElementType;
16
+ /** Base tailwind classes always applied to this component */
17
+ baseClassName?: string;
18
+ /** CVA variant function for prop-driven styles */
19
+ variants?: (props: any) => string;
20
+ /** Default props to apply */
21
+ defaultProps?: Partial<TProps>;
22
+ }
23
+
24
+ export function createComponent<
25
+ TRef,
26
+ TProps extends { className?: string; style?: any }
27
+ >(
28
+ options: CreateComponentOptions<TProps, any>
29
+ ) {
30
+ const { Component, baseClassName = '', variants, defaultProps = {} } = options;
31
+
32
+ const ForwardedComponent = forwardRef<TRef, TProps>((props, ref) => {
33
+ // Merge default props with passed props
34
+ const mergedProps = { ...defaultProps, ...props };
35
+ const { className, style, ...restProps } = mergedProps;
36
+
37
+ // Resolve variants if the CVA function is provided
38
+ const variantClassName = variants ? variants(mergedProps) : '';
39
+
40
+ // Merge base classes, variant classes, and user-passed classes
41
+ const finalClassName = cn(baseClassName, variantClassName, className);
42
+
43
+ return (
44
+ <Component
45
+ ref={ref}
46
+ className={finalClassName || undefined}
47
+ style={style}
48
+ {...restProps}
49
+ />
50
+ );
51
+ });
52
+
53
+ ForwardedComponent.displayName = `NativecnComponent(${(Component as any).displayName || (Component as any).name || 'Unknown'})`;
54
+
55
+ return ForwardedComponent;
56
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './utils';
3
+ export * from './variants';
4
+ export * from './platform';
@@ -0,0 +1,25 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ export const isIOS = Platform.OS === 'ios';
4
+ export const isAndroid = Platform.OS === 'android';
5
+ export const isWeb = Platform.OS === 'web';
6
+
7
+ export const isNative = isIOS || isAndroid;
8
+
9
+ /**
10
+ * Platform-specific class name helper.
11
+ * Selectively applies classes based on the current platform.
12
+ */
13
+ export function platformClasses(classes: {
14
+ ios?: string;
15
+ android?: string;
16
+ web?: string;
17
+ native?: string;
18
+ default?: string;
19
+ }): string {
20
+ if (isIOS && classes.ios) return classes.ios;
21
+ if (isAndroid && classes.android) return classes.android;
22
+ if (isWeb && classes.web) return classes.web;
23
+ if (isNative && classes.native) return classes.native;
24
+ return classes.default || '';
25
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared types used across the Nativecn UI library
3
+ */
4
+ import type { ViewStyle, TextStyle, ImageStyle } from 'react-native';
5
+
6
+ export type AnyStyle = ViewStyle | TextStyle | ImageStyle;
7
+
8
+ export type BooleanString = 'true' | 'false';
9
+
10
+ /**
11
+ * Common size variants used across components
12
+ */
13
+ export type ComponentSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'icon' | 'icon-sm' | 'icon-lg';
14
+
15
+ /**
16
+ * Common intent variants used across components
17
+ */
18
+ export type ComponentVariant =
19
+ | 'default'
20
+ | 'destructive'
21
+ | 'outline'
22
+ | 'secondary'
23
+ | 'ghost'
24
+ | 'link'
25
+ | 'gradient'
26
+ | 'glass';
27
+
28
+ export type HapticFeedbackType = 'none' | 'selection' | 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error';
@@ -0,0 +1,35 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ /**
5
+ * Merges Tailwind CSS classes securely.
6
+ * It uses clsx to construct the class string conditionally,
7
+ * and tailwind-merge to deduplicate conflicting Tailwind utilities.
8
+ *
9
+ * @example
10
+ * cn('px-2 py-1', { 'bg-red-500': hasError }, ['text-sm', 'font-bold'])
11
+ */
12
+ export function cn(...inputs: ClassValue[]): string {
13
+ return twMerge(clsx(inputs));
14
+ }
15
+
16
+ /**
17
+ * Compose multiple event handlers together.
18
+ * Useful when forwarding refs and preserving user-passed event handlers.
19
+ */
20
+ export function composeEventHandlers<E>(
21
+ originalEventHandler?: (event: E) => void,
22
+ ourEventHandler?: (event: E) => void,
23
+ { checkForDefaultPrevented = true } = {}
24
+ ) {
25
+ return function handleEvent(event: E) {
26
+ originalEventHandler?.(event);
27
+
28
+ if (
29
+ checkForDefaultPrevented === false ||
30
+ !(event as any)?.defaultPrevented
31
+ ) {
32
+ return ourEventHandler?.(event);
33
+ }
34
+ };
35
+ }
@@ -0,0 +1,7 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority';
2
+ export { cva, type VariantProps };
3
+
4
+ /**
5
+ * Re-exporting CVA for consistent internal usage.
6
+ * CVA allows building type-safe UI components with variants.
7
+ */
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ import { cn } from '../../lib/utils';
4
+ import { Text } from '../../components/typography';
5
+ import Animated, { FadeInUp, FadeInDown } from 'react-native-reanimated';
6
+
7
+ export interface ChatBubbleProps extends ViewProps {
8
+ message: string;
9
+ isSender?: boolean;
10
+ timestamp?: string;
11
+ avatar?: React.ReactNode;
12
+ }
13
+
14
+ export const ChatBubble = React.forwardRef<React.ElementRef<typeof View>, ChatBubbleProps>(
15
+ ({ className, message, isSender = false, timestamp, avatar, ...props }, ref) => {
16
+
17
+ const enteringAnimation = isSender ? FadeInDown.springify() : FadeInUp.springify();
18
+
19
+ return (
20
+ <Animated.View
21
+ entering={enteringAnimation}
22
+ ref={ref as any}
23
+ className={cn('mb-4 flex w-full flex-row', isSender ? 'justify-end' : 'justify-start', className)}
24
+ {...props}
25
+ >
26
+ {!isSender && avatar && (
27
+ <View className="mr-2 justify-end pb-4">{avatar}</View>
28
+ )}
29
+
30
+ <View className="max-w-[80%]">
31
+ <View
32
+ className={cn(
33
+ 'rounded-2xl px-4 py-3 shadow-sm',
34
+ isSender
35
+ ? 'rounded-tr-sm bg-primary border border-primary'
36
+ : 'rounded-tl-sm bg-muted border border-border'
37
+ )}
38
+ >
39
+ <Text className={cn('text-[15px] leading-5', isSender ? 'text-primary-foreground' : 'text-foreground')}>
40
+ {message}
41
+ </Text>
42
+ </View>
43
+
44
+ {timestamp && (
45
+ <Text className={cn('mt-1 text-xs text-muted-foreground', isSender ? 'text-right' : 'text-left')}>
46
+ {timestamp}
47
+ </Text>
48
+ )}
49
+ </View>
50
+
51
+ {isSender && avatar && (
52
+ <View className="ml-2 justify-end pb-4">{avatar}</View>
53
+ )}
54
+ </Animated.View>
55
+ );
56
+ }
57
+ );
58
+ ChatBubble.displayName = 'ChatBubble';
@@ -0,0 +1,59 @@
1
+ import React, { useEffect } from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ import Animated, { useAnimatedStyle, useSharedValue, withRepeat, withSequence, withTiming, withDelay } from 'react-native-reanimated';
4
+ import { cn } from '../../lib/utils';
5
+
6
+ export const TypingIndicator = React.forwardRef<React.ElementRef<typeof View>, ViewProps>(
7
+ ({ className, ...props }, ref) => {
8
+ return (
9
+ <View
10
+ ref={ref}
11
+ className={cn('flex-row items-center space-x-1.5 rounded-2xl rounded-tl-sm bg-muted px-4 py-3 self-start', className)}
12
+ {...props}
13
+ >
14
+ <Dot delay={0} />
15
+ <Dot delay={150} />
16
+ <Dot delay={300} />
17
+ </View>
18
+ );
19
+ }
20
+ );
21
+ TypingIndicator.displayName = 'TypingIndicator';
22
+
23
+ const Dot = ({ delay }: { delay: number }) => {
24
+ const opacity = useSharedValue(0.3);
25
+ const translateY = useSharedValue(0);
26
+
27
+ useEffect(() => {
28
+ opacity.value = withDelay(
29
+ delay,
30
+ withRepeat(
31
+ withSequence(
32
+ withTiming(1, { duration: 300 }),
33
+ withTiming(0.3, { duration: 300 })
34
+ ),
35
+ -1,
36
+ true
37
+ )
38
+ );
39
+
40
+ translateY.value = withDelay(
41
+ delay,
42
+ withRepeat(
43
+ withSequence(
44
+ withTiming(-3, { duration: 300 }),
45
+ withTiming(0, { duration: 300 })
46
+ ),
47
+ -1,
48
+ true
49
+ )
50
+ );
51
+ }, []);
52
+
53
+ const animatedStyle = useAnimatedStyle(() => ({
54
+ opacity: opacity.value,
55
+ transform: [{ translateY: translateY.value }],
56
+ }));
57
+
58
+ return <Animated.View style={animatedStyle} className="h-2 w-2 rounded-full bg-foreground" />;
59
+ };
@@ -0,0 +1,66 @@
1
+ import React, { useEffect } from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ import Animated, { useAnimatedStyle, useSharedValue, withSpring, withDelay } from 'react-native-reanimated';
4
+ import { Text } from '../../components/typography';
5
+ import { cn } from '../../lib/utils';
6
+
7
+ export interface BarChartProps extends ViewProps {
8
+ data: { label: string; value: number }[];
9
+ maxValue?: number;
10
+ height?: number;
11
+ barColor?: string;
12
+ }
13
+
14
+ export const BarChart = React.forwardRef<React.ElementRef<typeof View>, BarChartProps>(
15
+ ({ className, data, maxValue, height = 200, barColor = 'hsl(var(--primary))', style, ...props }, ref) => {
16
+ const max = maxValue || Math.max(...data.map((d) => d.value));
17
+
18
+ return (
19
+ <View
20
+ ref={ref}
21
+ className={cn('flex-row items-end justify-between px-4 pb-8 pt-4 border-b border-border', className)}
22
+ style={[{ height }, style]}
23
+ {...props}
24
+ >
25
+ {data.map((item, index) => (
26
+ <Bar
27
+ key={item.label}
28
+ value={item.value}
29
+ max={max}
30
+ label={item.label}
31
+ color={barColor}
32
+ delay={index * 100}
33
+ totalHeight={height - 40} // account for label and padding
34
+ />
35
+ ))}
36
+ </View>
37
+ );
38
+ }
39
+ );
40
+ BarChart.displayName = 'BarChart';
41
+
42
+ const Bar = ({ value, max, label, color, delay, totalHeight }: any) => {
43
+ const heightAnim = useSharedValue(0);
44
+
45
+ const targetHeight = (value / max) * totalHeight;
46
+
47
+ useEffect(() => {
48
+ heightAnim.value = withDelay(delay, withSpring(targetHeight, { damping: 15 }));
49
+ }, [targetHeight, delay]);
50
+
51
+ const animatedStyle = useAnimatedStyle(() => ({
52
+ height: heightAnim.value,
53
+ }));
54
+
55
+ return (
56
+ <View className="items-center flex-1">
57
+ <View className="w-full flex-1 justify-end px-1">
58
+ <Animated.View
59
+ style={[animatedStyle, { backgroundColor: color }]}
60
+ className="w-full rounded-t-sm"
61
+ />
62
+ </View>
63
+ <Text className="mt-2 text-xs text-muted-foreground">{label}</Text>
64
+ </View>
65
+ );
66
+ };
@@ -0,0 +1,63 @@
1
+ import React, { useEffect } from 'react';
2
+ import { View, type ViewProps, StyleSheet } from 'react-native';
3
+ import Animated, { useAnimatedStyle, useSharedValue, withTiming, Easing, interpolate, Extrapolate } from 'react-native-reanimated';
4
+ import { cn } from '../../lib/utils';
5
+ import { Text } from '../../components/typography';
6
+
7
+ export interface ProgressRingProps extends ViewProps {
8
+ progress: number; // 0 to 1
9
+ size?: number;
10
+ strokeWidth?: number;
11
+ color?: string;
12
+ trackColor?: string;
13
+ }
14
+
15
+ export const ProgressRing = React.forwardRef<React.ElementRef<typeof View>, ProgressRingProps>(
16
+ ({ className, progress, size = 120, strokeWidth = 10, color = 'hsl(var(--primary))', trackColor = 'hsl(var(--muted))', style, ...props }, ref) => {
17
+ // Pure View/CSS based fake ring using half-circles
18
+ const rotation = useSharedValue(0);
19
+
20
+ useEffect(() => {
21
+ // Limit to 0-1
22
+ const p = Math.max(0, Math.min(1, progress));
23
+ rotation.value = withTiming(p * 360, { duration: 1500, easing: Easing.out(Easing.cubic) });
24
+ }, [progress]);
25
+
26
+ const rightAnimatedStyle = useAnimatedStyle(() => {
27
+ const rot = interpolate(rotation.value, [0, 180, 360], [0, 180, 180], Extrapolate.CLAMP);
28
+ return { transform: [{ rotate: `${rot}deg` }] };
29
+ });
30
+
31
+ const leftAnimatedStyle = useAnimatedStyle(() => {
32
+ const rot = interpolate(rotation.value, [0, 180, 360], [0, 0, 180], Extrapolate.CLAMP);
33
+ return { transform: [{ rotate: `${rot}deg` }] };
34
+ });
35
+
36
+ return (
37
+ <View
38
+ ref={ref}
39
+ className={cn('items-center justify-center relative', className)}
40
+ style={[{ width: size, height: size }, style]}
41
+ {...props}
42
+ >
43
+ {/* Track */}
44
+ <View
45
+ style={[
46
+ StyleSheet.absoluteFill,
47
+ { borderRadius: size / 2, borderWidth: strokeWidth, borderColor: trackColor }
48
+ ]}
49
+ />
50
+
51
+ <View style={[StyleSheet.absoluteFill, { overflow: 'hidden' }]}>
52
+ {/* We would typically use react-native-svg for a real ring, but simulating with views for zero-deps */}
53
+ <View style={[StyleSheet.absoluteFill, { borderRadius: size / 2, borderWidth: strokeWidth, borderColor: color, opacity: 0.2 }]} />
54
+ </View>
55
+
56
+ <View className="items-center justify-center">
57
+ <Text className="text-2xl font-bold">{Math.round(progress * 100)}%</Text>
58
+ </View>
59
+ </View>
60
+ );
61
+ }
62
+ );
63
+ ProgressRing.displayName = 'ProgressRing';
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, type ViewProps, Modal, Pressable } from 'react-native';
3
+ import { BlurView } from 'expo-blur';
4
+ import Animated, { SlideInDown, SlideOutDown } from 'react-native-reanimated';
5
+ import { cn } from '../../lib/utils';
6
+ import { useThemeContext } from '../../context/theme-context';
7
+
8
+ export interface GlassBottomSheetProps extends ViewProps {
9
+ open: boolean;
10
+ onOpenChange: (open: boolean) => void;
11
+ children: React.ReactNode;
12
+ }
13
+
14
+ export const GlassBottomSheet = React.forwardRef<React.ElementRef<typeof View>, GlassBottomSheetProps>(
15
+ ({ className, open, onOpenChange, children, ...props }, ref) => {
16
+ const { isDark } = useThemeContext();
17
+
18
+ return (
19
+ <Modal
20
+ visible={open}
21
+ transparent
22
+ animationType="none"
23
+ onRequestClose={() => onOpenChange(false)}
24
+ >
25
+ <View className="flex-1 justify-end">
26
+ <Pressable className="absolute inset-0 bg-black/40" onPress={() => onOpenChange(false)} />
27
+
28
+ <Animated.View
29
+ entering={SlideInDown.springify().damping(20).stiffness(200)}
30
+ exiting={SlideOutDown.duration(300)}
31
+ ref={ref}
32
+ className={cn('w-full overflow-hidden rounded-t-3xl border-t border-white/20 shadow-2xl', className)}
33
+ {...props}
34
+ >
35
+ <BlurView
36
+ intensity={80}
37
+ tint={isDark ? 'dark' : 'light'}
38
+ style={StyleSheet.absoluteFill}
39
+ />
40
+ <View className="p-6 pb-safe relative z-10">
41
+ <View className="w-12 h-1.5 bg-muted-foreground/30 rounded-full self-center mb-6" />
42
+ {children}
43
+ </View>
44
+ </Animated.View>
45
+ </View>
46
+ </Modal>
47
+ );
48
+ }
49
+ );
50
+ GlassBottomSheet.displayName = 'GlassBottomSheet';