@gv-tech/ui-native 2.22.0 → 2.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gv-tech/ui-native",
3
- "version": "2.22.0",
3
+ "version": "2.22.2",
4
4
  "description": "React Native implementations of the GV Tech design system components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,8 +36,7 @@
36
36
  "dependencies": {
37
37
  "@gv-tech/design-tokens": "^2.12.0",
38
38
  "@gv-tech/ui-core": "^2.12.0",
39
- "react-native-reanimated": "4.3.0",
40
- "react-native-worklets": "0.8.1",
39
+ "react-hook-form": "^7.71.1",
41
40
  "@rn-primitives/accordion": "^1.2.0",
42
41
  "@rn-primitives/alert-dialog": "^1.2.0",
43
42
  "@rn-primitives/aspect-ratio": "^1.2.0",
@@ -66,22 +65,28 @@
66
65
  "@rn-primitives/toggle-group": "^1.2.0",
67
66
  "@rn-primitives/tooltip": "^1.2.0",
68
67
  "clsx": "^2.1.1",
69
- "lucide-react-native": "^1.8.0",
70
68
  "nativewind": "^4.2.1",
71
- "react-native-svg": "^15.15.3",
72
69
  "tailwind-merge": "^3.4.1",
73
70
  "tailwindcss": "^4.1.18"
74
71
  },
75
72
  "peerDependencies": {
73
+ "lucide-react-native": "^1.8.0",
76
74
  "react": ">=18",
77
75
  "react-native": ">=0.72",
78
76
  "react-native-reanimated": "^4.2.3",
77
+ "react-native-svg": "^15.15.3",
79
78
  "react-native-worklets": "^0.7.2"
80
79
  },
81
80
  "peerDependenciesMeta": {
81
+ "lucide-react-native": {
82
+ "optional": false
83
+ },
82
84
  "react-native-reanimated": {
83
85
  "optional": false
84
86
  },
87
+ "react-native-svg": {
88
+ "optional": false
89
+ },
85
90
  "react-native-worklets": {
86
91
  "optional": false
87
92
  }
package/src/carousel.tsx CHANGED
@@ -5,25 +5,202 @@ import type {
5
5
  CarouselNextBaseProps,
6
6
  CarouselPreviousBaseProps,
7
7
  } from '@gv-tech/ui-core';
8
+ import { ArrowLeft, ArrowRight } from 'lucide-react-native';
8
9
  import * as React from 'react';
9
- import { View } from 'react-native';
10
+ import {
11
+ Dimensions,
12
+ ScrollView,
13
+ View,
14
+ type LayoutChangeEvent,
15
+ type NativeScrollEvent,
16
+ type NativeSyntheticEvent,
17
+ } from 'react-native';
18
+ import { Button } from './button';
19
+ import { cn } from './lib/utils';
20
+ type CarouselApi = unknown;
10
21
 
11
- export const Carousel: React.FC<CarouselBaseProps> = ({ children, className }) => {
12
- return <View className={className}>{children}</View>;
22
+ type CarouselContextType = {
23
+ orientation: 'horizontal' | 'vertical';
24
+ scrollRef: React.RefObject<ScrollView>;
25
+ scrollNext: () => void;
26
+ scrollPrev: () => void;
27
+ canScrollNext: boolean;
28
+ canScrollPrev: boolean;
29
+ itemWidth: number;
30
+ setItemWidth: (width: number) => void;
13
31
  };
14
32
 
15
- export const CarouselContent: React.FC<CarouselContentBaseProps> = ({ children, className }) => {
16
- return <View className={className}>{children}</View>;
17
- };
33
+ const CarouselContext = React.createContext<CarouselContextType | null>(null);
18
34
 
19
- export const CarouselItem: React.FC<CarouselItemBaseProps> = ({ children, className }) => {
20
- return <View className={className}>{children}</View>;
21
- };
35
+ function useCarousel() {
36
+ const context = React.useContext(CarouselContext);
37
+ if (!context) {
38
+ throw new Error('useCarousel must be used within a <Carousel />');
39
+ }
40
+ return context;
41
+ }
22
42
 
23
- export const CarouselPrevious: React.FC<CarouselPreviousBaseProps> = ({ className }) => {
24
- return <View className={className} />;
43
+ export type CarouselProps = CarouselBaseProps & {
44
+ opts?: unknown;
45
+ plugins?: unknown;
46
+ setApi?: (api: CarouselApi) => void;
25
47
  };
26
48
 
27
- export const CarouselNext: React.FC<CarouselNextBaseProps> = ({ className }) => {
28
- return <View className={className} />;
29
- };
49
+ export const Carousel = React.forwardRef<View, CarouselProps>(
50
+ ({ children, className, opts, orientation = 'horizontal', setApi, plugins, ...props }, ref) => {
51
+ const scrollRef = React.useRef<ScrollView>(null) as React.RefObject<ScrollView>;
52
+ const [canScrollNext, setCanScrollNext] = React.useState(true);
53
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
54
+ const [itemWidth, setItemWidth] = React.useState(Dimensions.get('window').width);
55
+ const [currentIndex, setCurrentIndex] = React.useState(0);
56
+
57
+ const scrollNext = React.useCallback(() => {
58
+ scrollRef.current?.scrollTo({ x: (currentIndex + 1) * itemWidth, animated: true });
59
+ }, [currentIndex, itemWidth]);
60
+
61
+ const scrollPrev = React.useCallback(() => {
62
+ scrollRef.current?.scrollTo({ x: Math.max(0, currentIndex - 1) * itemWidth, animated: true });
63
+ }, [currentIndex, itemWidth]);
64
+
65
+ // Very basic API shim
66
+ React.useEffect(() => {
67
+ if (setApi) {
68
+ setApi({
69
+ scrollNext,
70
+ scrollPrev,
71
+ canScrollNext: () => canScrollNext,
72
+ canScrollPrev: () => canScrollPrev,
73
+ on: () => {},
74
+ off: () => {},
75
+ } as unknown as CarouselApi);
76
+ }
77
+ }, [setApi, scrollNext, scrollPrev, canScrollNext, canScrollPrev]);
78
+
79
+ const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
80
+ const offsetX = event.nativeEvent.contentOffset.x;
81
+ const contentWidth = event.nativeEvent.contentSize.width;
82
+ const layoutWidth = event.nativeEvent.layoutMeasurement.width;
83
+
84
+ const newIndex = Math.round(offsetX / itemWidth);
85
+ setCurrentIndex(newIndex);
86
+ setCanScrollPrev(offsetX > 0);
87
+ setCanScrollNext(offsetX + layoutWidth < contentWidth);
88
+ };
89
+
90
+ return (
91
+ <CarouselContext.Provider
92
+ value={{
93
+ orientation,
94
+ scrollRef,
95
+ scrollNext,
96
+ scrollPrev,
97
+ canScrollNext,
98
+ canScrollPrev,
99
+ itemWidth,
100
+ setItemWidth,
101
+ }}
102
+ >
103
+ <View ref={ref} className={cn('relative', className)} {...props}>
104
+ {children}
105
+ </View>
106
+ </CarouselContext.Provider>
107
+ );
108
+ },
109
+ );
110
+ Carousel.displayName = 'Carousel';
111
+
112
+ export const CarouselContent = React.forwardRef<ScrollView, CarouselContentBaseProps>(
113
+ ({ children, className, ...props }, ref) => {
114
+ const { scrollRef, orientation } = useCarousel();
115
+
116
+ return (
117
+ <View className="overflow-hidden">
118
+ <ScrollView
119
+ ref={scrollRef}
120
+ horizontal={orientation === 'horizontal'}
121
+ showsHorizontalScrollIndicator={false}
122
+ showsVerticalScrollIndicator={false}
123
+ pagingEnabled
124
+ snapToInterval={orientation === 'horizontal' ? Dimensions.get('window').width : undefined}
125
+ decelerationRate="fast"
126
+ className={cn('flex', orientation === 'horizontal' ? 'flex-row' : 'flex-col', className)}
127
+ {...props}
128
+ >
129
+ {children}
130
+ </ScrollView>
131
+ </View>
132
+ );
133
+ },
134
+ );
135
+ CarouselContent.displayName = 'CarouselContent';
136
+
137
+ export const CarouselItem = React.forwardRef<View, CarouselItemBaseProps>(({ children, className, ...props }, ref) => {
138
+ const { orientation, setItemWidth } = useCarousel();
139
+
140
+ const handleLayout = (e: LayoutChangeEvent) => {
141
+ if (orientation === 'horizontal') {
142
+ setItemWidth(e.nativeEvent.layout.width);
143
+ }
144
+ };
145
+
146
+ return (
147
+ <View ref={ref} onLayout={handleLayout} className={cn('min-w-0 shrink-0 grow-0 basis-full', className)} {...props}>
148
+ {children}
149
+ </View>
150
+ );
151
+ });
152
+ CarouselItem.displayName = 'CarouselItem';
153
+
154
+ export const CarouselPrevious = React.forwardRef<React.ElementRef<typeof Button>, CarouselPreviousBaseProps>(
155
+ ({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
156
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
157
+
158
+ return (
159
+ <Button
160
+ ref={ref}
161
+ variant={variant as React.ComponentProps<typeof Button>['variant']}
162
+ size={size as React.ComponentProps<typeof Button>['size']}
163
+ className={cn(
164
+ 'absolute h-8 w-8 rounded-full',
165
+ orientation === 'horizontal'
166
+ ? 'top-1/2 -left-12 -translate-y-1/2'
167
+ : '-top-12 left-1/2 -translate-x-1/2 rotate-90',
168
+ className,
169
+ )}
170
+ disabled={!canScrollPrev}
171
+ onPress={scrollPrev}
172
+ {...props}
173
+ >
174
+ <ArrowLeft className="text-foreground h-4 w-4" size={16} />
175
+ </Button>
176
+ );
177
+ },
178
+ );
179
+ CarouselPrevious.displayName = 'CarouselPrevious';
180
+
181
+ export const CarouselNext = React.forwardRef<React.ElementRef<typeof Button>, CarouselNextBaseProps>(
182
+ ({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
183
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
184
+
185
+ return (
186
+ <Button
187
+ ref={ref}
188
+ variant={variant as React.ComponentProps<typeof Button>['variant']}
189
+ size={size as React.ComponentProps<typeof Button>['size']}
190
+ className={cn(
191
+ 'absolute h-8 w-8 rounded-full',
192
+ orientation === 'horizontal'
193
+ ? 'top-1/2 -right-12 -translate-y-1/2'
194
+ : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
195
+ className,
196
+ )}
197
+ disabled={!canScrollNext}
198
+ onPress={scrollNext}
199
+ {...props}
200
+ >
201
+ <ArrowRight className="text-foreground h-4 w-4" size={16} />
202
+ </Button>
203
+ );
204
+ },
205
+ );
206
+ CarouselNext.displayName = 'CarouselNext';
@@ -77,10 +77,11 @@ DropdownMenuContent.displayName = 'DropdownMenuContent';
77
77
  export const DropdownMenuItem = React.forwardRef<
78
78
  React.ElementRef<typeof DropdownMenuPrimitive.Item>,
79
79
  DropdownMenuItemBaseProps
80
- >(({ className, children, inset, ...props }, ref) => {
80
+ >(({ className, children, inset, onSelect, ...props }, ref) => {
81
81
  return (
82
82
  <DropdownMenuPrimitive.Item
83
83
  ref={ref}
84
+ onPress={() => onSelect?.(new Event('select'))}
84
85
  className={cn(
85
86
  'focus:bg-accent focus:text-accent-foreground active:bg-accent active:text-accent-foreground relative flex flex-row items-center rounded-sm px-2 py-1.5 text-sm outline-none',
86
87
  inset && 'pl-8',
package/src/form.tsx CHANGED
@@ -1,9 +1,171 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Controller,
4
+ FormProvider,
5
+ useFormContext,
6
+ type ControllerProps,
7
+ type FieldPath,
8
+ type FieldValues,
9
+ } from 'react-hook-form';
1
10
  import { Text, View } from 'react-native';
2
11
 
3
- export const Form = () => {
12
+ import {
13
+ FormControlBaseProps,
14
+ FormDescriptionBaseProps,
15
+ FormItemBaseProps,
16
+ FormLabelBaseProps,
17
+ FormMessageBaseProps,
18
+ } from '@gv-tech/ui-core';
19
+ import { Label } from './label';
20
+ import { cn } from './lib/utils';
21
+ // Assuming we have Slot from @rn-primitives/slot
22
+ import * as Slot from '@rn-primitives/slot';
23
+
24
+ const Form = FormProvider;
25
+
26
+ type FormFieldContextValue<
27
+ TFieldValues extends FieldValues = FieldValues,
28
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
29
+ > = {
30
+ name: TName;
31
+ };
32
+
33
+ const FormFieldContext = React.createContext<FormFieldContextValue | null>(null);
34
+
35
+ const FormField = <
36
+ TFieldValues extends FieldValues = FieldValues,
37
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
38
+ >({
39
+ ...props
40
+ }: ControllerProps<TFieldValues, TName>) => {
41
+ return (
42
+ <FormFieldContext.Provider value={{ name: props.name }}>
43
+ <Controller {...props} />
44
+ </FormFieldContext.Provider>
45
+ );
46
+ };
47
+
48
+ const useFormField = () => {
49
+ const fieldContext = React.useContext(FormFieldContext);
50
+ const itemContext = React.useContext(FormItemContext);
51
+ const { getFieldState, formState } = useFormContext();
52
+
53
+ if (!fieldContext) {
54
+ throw new Error('useFormField should be used within <FormField>');
55
+ }
56
+
57
+ if (!itemContext) {
58
+ throw new Error('useFormField should be used within <FormItem>');
59
+ }
60
+
61
+ const fieldState = getFieldState(fieldContext.name, formState);
62
+
63
+ const { id } = itemContext;
64
+
65
+ return {
66
+ id,
67
+ name: fieldContext.name,
68
+ formItemId: `${id}-form-item`,
69
+ formDescriptionId: `${id}-form-item-description`,
70
+ formMessageId: `${id}-form-item-message`,
71
+ ...fieldState,
72
+ };
73
+ };
74
+
75
+ type FormItemContextValue = {
76
+ id: string;
77
+ };
78
+
79
+ const FormItemContext = React.createContext<FormItemContextValue | null>(null);
80
+
81
+ const FormItem = React.forwardRef<
82
+ React.ElementRef<typeof View>,
83
+ React.ComponentPropsWithoutRef<typeof View> & FormItemBaseProps
84
+ >(({ className, ...props }, ref) => {
85
+ const id = React.useId();
86
+
87
+ return (
88
+ <FormItemContext.Provider value={{ id }}>
89
+ <View ref={ref} className={cn('space-y-2', className)} {...props} />
90
+ </FormItemContext.Provider>
91
+ );
92
+ });
93
+ FormItem.displayName = 'FormItem';
94
+
95
+ const FormLabel = React.forwardRef<
96
+ React.ElementRef<typeof Label>,
97
+ React.ComponentPropsWithoutRef<typeof Label> & FormLabelBaseProps
98
+ >(({ className, ...props }, ref) => {
99
+ const { error, formItemId } = useFormField();
100
+
101
+ return <Label ref={ref} className={cn(error && 'text-destructive', className)} nativeID={formItemId} {...props} />;
102
+ });
103
+ FormLabel.displayName = 'FormLabel';
104
+
105
+ const FormControl = React.forwardRef<
106
+ React.ElementRef<typeof Slot.Slot>,
107
+ React.ComponentPropsWithoutRef<typeof Slot.Slot>
108
+ >(({ ...props }, ref) => {
109
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
110
+
111
+ return (
112
+ <Slot.Slot
113
+ ref={ref}
114
+ nativeID={formItemId}
115
+ aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
116
+ aria-invalid={!!error}
117
+ {...props}
118
+ />
119
+ );
120
+ });
121
+ FormControl.displayName = 'FormControl';
122
+
123
+ const FormDescription = React.forwardRef<
124
+ React.ElementRef<typeof Text>,
125
+ React.ComponentPropsWithoutRef<typeof Text> & FormDescriptionBaseProps
126
+ >(({ className, ...props }, ref) => {
127
+ const { formDescriptionId } = useFormField();
128
+
4
129
  return (
5
- <View>
6
- <Text>form is not yet implemented for React Native</Text>
7
- </View>
130
+ <Text
131
+ ref={ref}
132
+ nativeID={formDescriptionId}
133
+ className={cn('text-muted-foreground text-[13px]', className)}
134
+ {...props}
135
+ />
8
136
  );
137
+ });
138
+ FormDescription.displayName = 'FormDescription';
139
+
140
+ const FormMessage = React.forwardRef<
141
+ React.ElementRef<typeof Text>,
142
+ React.ComponentPropsWithoutRef<typeof Text> & FormMessageBaseProps
143
+ >(({ className, children, ...props }, ref) => {
144
+ const { error, formMessageId } = useFormField();
145
+ const body = error ? String(error?.message ?? '') : children;
146
+
147
+ if (!body) {
148
+ return null;
149
+ }
150
+
151
+ return (
152
+ <Text
153
+ ref={ref}
154
+ nativeID={formMessageId}
155
+ className={cn('text-destructive text-[13px] font-medium', className)}
156
+ {...props}
157
+ >
158
+ {body}
159
+ </Text>
160
+ );
161
+ });
162
+ FormMessage.displayName = 'FormMessage';
163
+
164
+ export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
165
+ export type {
166
+ FormControlBaseProps as FormControlProps,
167
+ FormDescriptionBaseProps as FormDescriptionProps,
168
+ FormItemBaseProps as FormItemProps,
169
+ FormLabelBaseProps as FormLabelProps,
170
+ FormMessageBaseProps as FormMessageProps,
9
171
  };
@@ -0,0 +1,18 @@
1
+ import { theme as designTokens } from '@gv-tech/design-tokens';
2
+ import { useColorScheme } from 'nativewind';
3
+
4
+ export function useTheme() {
5
+ const { colorScheme, setColorScheme } = useColorScheme();
6
+
7
+ const resolvedTheme = colorScheme as 'light' | 'dark';
8
+
9
+ // Default to light theme tokens if resolvedTheme is dark or invalid
10
+ const activeTokens = resolvedTheme === 'dark' ? designTokens.dark : designTokens.light;
11
+
12
+ return {
13
+ theme: colorScheme,
14
+ setTheme: setColorScheme,
15
+ resolvedTheme,
16
+ tokens: activeTokens,
17
+ };
18
+ }
package/src/index.ts CHANGED
@@ -314,6 +314,7 @@ export type { ToastProps } from './toast';
314
314
  export { ThemeProvider } from './theme-provider';
315
315
 
316
316
  // Theme Toggle
317
+ export { useTheme } from './hooks/use-theme';
317
318
  export { ThemeToggle } from './theme-toggle';
318
319
 
319
320
  // Toaster
@@ -9,8 +9,8 @@ export const ScrollArea = React.forwardRef<ScrollView, ScrollAreaBaseProps>(
9
9
  <ScrollView
10
10
  ref={ref}
11
11
  className={cn('flex-1', className)}
12
- showsVerticalScrollIndicator={false}
13
- showsHorizontalScrollIndicator={false}
12
+ showsVerticalScrollIndicator={true}
13
+ showsHorizontalScrollIndicator={true}
14
14
  {...props}
15
15
  >
16
16
  <View>{children}</View>
@@ -21,6 +21,8 @@ export const ScrollArea = React.forwardRef<ScrollView, ScrollAreaBaseProps>(
21
21
  ScrollArea.displayName = 'ScrollArea';
22
22
 
23
23
  export const ScrollBar: React.FC<ScrollBarBaseProps> = () => {
24
+ // Natively, we rely on the ScrollView's built-in indicators.
25
+ // This component is a no-op shim for contract compatibility.
24
26
  return null;
25
27
  };
26
28
  ScrollBar.displayName = 'ScrollBar';
package/src/search.tsx CHANGED
@@ -1,17 +1,79 @@
1
- import { Text, View } from 'react-native';
1
+ import { SearchBaseProps, SearchTriggerBaseProps } from '@gv-tech/ui-core';
2
+ import { Search as SearchIcon } from 'lucide-react-native';
3
+ import * as React from 'react';
4
+ import { Platform, Text, View } from 'react-native';
5
+ import { Button } from './button';
6
+ import { Dialog } from './dialog';
7
+ import { cn } from './lib/utils';
2
8
 
3
- export const Search = () => {
4
- return (
5
- <View>
6
- <Text>Search is not yet implemented for React Native</Text>
7
- </View>
9
+ export type SearchProps = SearchBaseProps;
10
+
11
+ export function Search({ children, open: customOpen, onOpenChange }: SearchProps) {
12
+ const [open, setOpen] = React.useState(false);
13
+ const isControlled = customOpen !== undefined;
14
+ const isOpen = isControlled ? (customOpen as boolean) : open;
15
+
16
+ const setIsOpen = React.useCallback(
17
+ (value: boolean) => {
18
+ if (isControlled) {
19
+ onOpenChange?.(value);
20
+ } else {
21
+ setOpen(value);
22
+ }
23
+ },
24
+ [isControlled, onOpenChange],
8
25
  );
9
- };
10
26
 
11
- export const SearchTrigger = () => {
12
27
  return (
13
- <View>
14
- <Text>SearchTrigger is not yet implemented for React Native</Text>
15
- </View>
28
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
29
+ {children}
30
+ </Dialog>
16
31
  );
17
- };
32
+ }
33
+
34
+ export interface SearchTriggerProps
35
+ extends Omit<React.ComponentPropsWithoutRef<typeof Button>, 'variant'>, SearchTriggerBaseProps {}
36
+
37
+ export const SearchTrigger = React.forwardRef<React.ElementRef<typeof Button>, SearchTriggerProps>(
38
+ ({ className, placeholder, variant = 'default', responsive = false, ...props }, ref) => {
39
+ const defaultPlaceholder = variant === 'compact' ? 'Search...' : 'Search docs...';
40
+ const activePlaceholder = placeholder || defaultPlaceholder;
41
+
42
+ return (
43
+ <Button
44
+ variant="outline"
45
+ className={cn(
46
+ 'relative h-12 flex-row justify-start pl-3 text-sm transition-all sm:h-9',
47
+ variant === 'default'
48
+ ? 'w-full pr-12'
49
+ : cn('w-12 px-0 sm:w-9 sm:justify-center', responsive && 'md:w-48 md:justify-start md:px-3 md:pr-12'),
50
+ className,
51
+ )}
52
+ ref={ref}
53
+ {...props}
54
+ >
55
+ <View className="flex-row items-center gap-2">
56
+ <SearchIcon className="text-muted-foreground shrink-0" size={18} />
57
+ <Text
58
+ className={cn(
59
+ 'text-muted-foreground truncate',
60
+ variant === 'compact' && (responsive ? 'hidden md:flex' : 'hidden'),
61
+ )}
62
+ >
63
+ {activePlaceholder}
64
+ </Text>
65
+ </View>
66
+ <View
67
+ className={cn(
68
+ 'bg-muted absolute top-2 right-2 hidden h-6 flex-row items-center gap-1 rounded border px-1.5 opacity-100',
69
+ variant === 'default' && Platform.OS !== 'android' && Platform.OS !== 'ios' && 'sm:flex',
70
+ variant === 'compact' && responsive && Platform.OS !== 'android' && Platform.OS !== 'ios' && 'md:flex',
71
+ )}
72
+ >
73
+ <Text className="text-muted-foreground font-mono text-[10px] font-medium">⌘K</Text>
74
+ </View>
75
+ </Button>
76
+ );
77
+ },
78
+ );
79
+ SearchTrigger.displayName = 'SearchTrigger';
package/src/sonner.tsx CHANGED
@@ -1,7 +1,11 @@
1
1
  import type { SonnerBaseProps } from '@gv-tech/ui-core';
2
2
  import * as React from 'react';
3
- import { View } from 'react-native';
3
+ import { ToastProvider, ToastViewport } from './toast';
4
4
 
5
- export const Toaster: React.FC<SonnerBaseProps> = ({ className }) => {
6
- return <View className={className} />;
5
+ export const Toaster: React.FC<SonnerBaseProps> = () => {
6
+ return (
7
+ <ToastProvider>
8
+ <ToastViewport />
9
+ </ToastProvider>
10
+ );
7
11
  };
@@ -1,28 +1,29 @@
1
1
  import { ThemeToggleBaseProps } from '@gv-tech/ui-core';
2
2
  import { Moon, Sun, SunMoon } from 'lucide-react-native';
3
- import { useColorScheme } from 'nativewind';
4
3
  import { View } from 'react-native';
5
4
  import { Button } from './button';
6
5
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './dropdown-menu';
6
+ import { useTheme } from './hooks/use-theme';
7
7
  import { cn } from './lib/utils';
8
8
  import { Text } from './text';
9
9
 
10
10
  export type ThemeToggleProps = ThemeToggleBaseProps;
11
11
 
12
12
  export function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, className }: ThemeToggleProps) {
13
- const { colorScheme, setColorScheme } = useColorScheme();
13
+ const { theme, setTheme, resolvedTheme } = useTheme();
14
14
 
15
- const currentTheme = (customTheme ?? colorScheme) as 'light' | 'dark' | 'system';
15
+ const currentTheme = (customTheme ?? theme) as 'light' | 'dark' | 'system';
16
16
 
17
- // Determine if dark based on currentTheme
18
- const isDark = currentTheme === 'dark';
17
+ // Determine the effective theme for icon rendering
18
+ const effectiveTheme = customTheme ? customTheme : resolvedTheme;
19
+ const isDark = effectiveTheme === 'dark';
19
20
  const isSystem = currentTheme === 'system';
20
21
 
21
22
  const handleThemeChange = (newTheme: 'light' | 'dark' | 'system') => {
22
23
  if (onThemeChange) {
23
24
  onThemeChange(newTheme);
24
25
  } else {
25
- setColorScheme(newTheme);
26
+ setTheme(newTheme);
26
27
  }
27
28
  };
28
29
 
@@ -61,15 +62,15 @@ export function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, cl
61
62
  </Button>
62
63
  </DropdownMenuTrigger>
63
64
  <DropdownMenuContent align="end">
64
- <DropdownMenuItem onPress={() => handleThemeChange('light')}>
65
+ <DropdownMenuItem onSelect={() => handleThemeChange('light')}>
65
66
  <Sun size={14} className="text-foreground mr-2" />
66
67
  <Text>Light</Text>
67
68
  </DropdownMenuItem>
68
- <DropdownMenuItem onPress={() => handleThemeChange('dark')}>
69
+ <DropdownMenuItem onSelect={() => handleThemeChange('dark')}>
69
70
  <Moon size={14} className="text-foreground mr-2" />
70
71
  <Text>Dark</Text>
71
72
  </DropdownMenuItem>
72
- <DropdownMenuItem onPress={() => handleThemeChange('system')}>
73
+ <DropdownMenuItem onSelect={() => handleThemeChange('system')}>
73
74
  <SunMoon size={14} className="text-foreground mr-2" />
74
75
  <Text>System</Text>
75
76
  </DropdownMenuItem>