@gv-tech/ui-native 2.25.2 → 2.25.4

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.25.2",
3
+ "version": "2.25.4",
4
4
  "description": "React Native implementations of the GV Tech design system components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,6 +37,7 @@
37
37
  "@gv-tech/design-tokens": "^2.12.0",
38
38
  "@gv-tech/ui-core": "^2.12.0",
39
39
  "@react-native-community/datetimepicker": "^9.1.0",
40
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
40
41
  "@rn-primitives/accordion": "^1.2.0",
41
42
  "@rn-primitives/alert-dialog": "^1.2.0",
42
43
  "@rn-primitives/aspect-ratio": "^1.2.0",
@@ -1,6 +1,6 @@
1
1
  import * as AlertDialogPrimitive from '@rn-primitives/alert-dialog';
2
2
  import * as React from 'react';
3
- import { StyleSheet, View, type ViewStyle } from 'react-native';
3
+ import { Platform, StyleSheet, View, type ViewStyle } from 'react-native';
4
4
  import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
5
5
 
6
6
  import { buttonVariants } from './button';
@@ -43,16 +43,23 @@ const AlertDialogContent: React.ForwardRefExoticComponent<
43
43
  return (
44
44
  <AlertDialogPortal hostName={portalHost}>
45
45
  <AlertDialogOverlay className={overlayClassName} style={overlayStyle} />
46
- <AlertDialogPrimitive.Content ref={ref} asChild {...props}>
47
- <Animated.View
48
- entering={FadeIn.duration(150)}
49
- exiting={FadeOut.duration(150)}
50
- className={cn(
51
- 'border-border bg-background z-50 w-full max-w-lg gap-4 rounded-xl border p-6 shadow-lg sm:rounded-lg',
52
- className,
53
- )}
54
- />
55
- </AlertDialogPrimitive.Content>
46
+ <View
47
+ pointerEvents="box-none"
48
+ className={cn('absolute inset-0 z-50 flex items-center justify-center p-4', Platform.OS === 'web' && 'fixed')}
49
+ >
50
+ <AlertDialogPrimitive.Content ref={ref} asChild {...props}>
51
+ <Animated.View
52
+ entering={FadeIn.duration(150)}
53
+ exiting={FadeOut.duration(150)}
54
+ className={cn(
55
+ 'border-border bg-background w-full max-w-lg gap-4 rounded-xl border p-6 shadow-lg sm:rounded-lg',
56
+ className,
57
+ )}
58
+ >
59
+ {props.children}
60
+ </Animated.View>
61
+ </AlertDialogPrimitive.Content>
62
+ </View>
56
63
  </AlertDialogPortal>
57
64
  );
58
65
  });
package/src/dialog.tsx CHANGED
@@ -60,63 +60,40 @@ export type DialogContentRef = React.ComponentRef<typeof DialogPrimitive.Content
60
60
  const DialogContent: React.ForwardRefExoticComponent<DialogContentProps & React.RefAttributes<DialogContentRef>> =
61
61
  React.forwardRef<DialogContentRef, DialogContentProps>(
62
62
  ({ className, children, portalHost, overlayClassName, overlayStyle, ...props }, ref) => {
63
- const PlatformWrapper = React.useCallback(({ children }: { children: React.ReactNode }) => {
64
- if (Platform.OS === 'web') {
65
- return <>{children}</>;
66
- }
67
- return (
68
- <View
69
- pointerEvents="box-none"
70
- style={{
71
- position: 'absolute',
72
- top: 0,
73
- right: 0,
74
- bottom: 0,
75
- left: 0,
76
- zIndex: 50,
77
- alignItems: 'center',
78
- justifyContent: 'center',
79
- padding: 16,
80
- }}
81
- >
82
- {children}
83
- </View>
84
- );
85
- }, []);
86
-
87
63
  return (
88
64
  <DialogPortal hostName={portalHost}>
89
65
  <DialogOverlay className={overlayClassName} style={overlayStyle} />
90
- <PlatformWrapper>
91
- <DialogPrimitive.Content ref={ref} {...props}>
92
- <View
93
- pointerEvents="box-none"
94
- className="absolute inset-0 z-50 flex items-center justify-center"
95
- style={Platform.OS === 'web' ? ({ position: 'fixed' } as unknown as ViewStyle) : undefined}
66
+ {/* Centering wrapper that is full screen */}
67
+ <View
68
+ pointerEvents="box-none"
69
+ className={cn(
70
+ 'absolute inset-0 z-50 flex items-center justify-center p-4',
71
+ Platform.OS === 'web' && 'fixed',
72
+ )}
73
+ >
74
+ <DialogPrimitive.Content ref={ref} asChild {...props}>
75
+ <Animated.View
76
+ entering={FadeIn.duration(150)}
77
+ exiting={FadeOut.duration(150)}
78
+ className={cn(
79
+ 'border-border bg-background w-full max-w-lg gap-4 rounded-xl border p-6 shadow-lg sm:rounded-lg',
80
+ className,
81
+ )}
96
82
  >
97
- <Animated.View
98
- entering={FadeIn.duration(150)}
99
- exiting={FadeOut.duration(150)}
100
- className={cn(
101
- 'border-border bg-background w-full max-w-lg gap-4 rounded-xl border p-6 shadow-lg sm:rounded-lg',
102
- className,
103
- )}
83
+ {children}
84
+ <DialogPrimitive.Close
85
+ className={
86
+ 'ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none'
87
+ }
104
88
  >
105
- {children}
106
- <DialogPrimitive.Close
107
- className={
108
- 'ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none'
109
- }
110
- >
111
- <X size={18} className="text-muted-foreground" />
112
- <View className="sr-only">
113
- <DialogPrimitive.Title>Close</DialogPrimitive.Title>
114
- </View>
115
- </DialogPrimitive.Close>
116
- </Animated.View>
117
- </View>
89
+ <X size={18} className="text-muted-foreground" />
90
+ <View className="sr-only">
91
+ <DialogPrimitive.Title>Close</DialogPrimitive.Title>
92
+ </View>
93
+ </DialogPrimitive.Close>
94
+ </Animated.View>
118
95
  </DialogPrimitive.Content>
119
- </PlatformWrapper>
96
+ </View>
120
97
  </DialogPortal>
121
98
  );
122
99
  },
@@ -9,6 +9,7 @@ import type {
9
9
  DropdownMenuSubContentBaseProps,
10
10
  DropdownMenuSubTriggerBaseProps,
11
11
  } from '@gv-tech/ui-core';
12
+ import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu';
12
13
  import * as DropdownMenuPrimitive from '@rn-primitives/dropdown-menu';
13
14
  import { Check, ChevronRight, Circle } from 'lucide-react-native';
14
15
  import * as React from 'react';
@@ -78,6 +79,23 @@ export const DropdownMenuItem = React.forwardRef<
78
79
  React.ComponentRef<typeof DropdownMenuPrimitive.Item>,
79
80
  DropdownMenuItemBaseProps
80
81
  >(({ className, children, inset, onSelect, ...props }, ref) => {
82
+ if (Platform.OS === 'web') {
83
+ return (
84
+ <RadixDropdownMenu.Item
85
+ ref={ref as React.Ref<HTMLDivElement>}
86
+ onSelect={onSelect}
87
+ className={cn(
88
+ 'focus:bg-accent focus:text-accent-foreground active:bg-accent active:text-accent-foreground relative flex cursor-default flex-row items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none',
89
+ inset && 'pl-8',
90
+ className,
91
+ )}
92
+ {...props}
93
+ >
94
+ {children}
95
+ </RadixDropdownMenu.Item>
96
+ );
97
+ }
98
+
81
99
  return (
82
100
  <DropdownMenuPrimitive.Item
83
101
  ref={ref}
@@ -1,7 +1,14 @@
1
1
  import { theme as designTokens } from '@gv-tech/design-tokens';
2
+ import * as React from 'react';
2
3
  import { useColorScheme } from 'react-native';
4
+ import { ThemeContext } from '../theme-provider';
3
5
 
4
6
  export function useTheme() {
7
+ const context = React.useContext(ThemeContext);
8
+ if (context) {
9
+ return context;
10
+ }
11
+
5
12
  const colorScheme = useColorScheme();
6
13
 
7
14
  const resolvedTheme = colorScheme as 'light' | 'dark';
@@ -1,24 +1,48 @@
1
+ import { theme as designTokens } from '@gv-tech/design-tokens';
1
2
  import * as React from 'react';
2
- import { View, ViewProps } from 'react-native';
3
- import { useTheme } from './hooks/use-theme';
3
+ import { useColorScheme, View, ViewProps } from 'react-native';
4
4
  import { cn } from './lib/utils';
5
5
 
6
+ export interface ThemeContextValue {
7
+ theme: 'light' | 'dark' | 'system';
8
+ resolvedTheme: 'light' | 'dark';
9
+ tokens: typeof designTokens.light | typeof designTokens.dark;
10
+ }
11
+
12
+ export const ThemeContext = React.createContext<ThemeContextValue | null>(null);
13
+
6
14
  export interface ThemeProviderProps extends ViewProps {
7
15
  children: React.ReactNode;
16
+ value?: 'light' | 'dark' | 'system';
8
17
  [key: string]: unknown;
9
18
  }
10
19
 
11
- export function ThemeProvider({ children, className, style, ...props }: ThemeProviderProps) {
12
- const { theme, tokens } = useTheme();
13
- const isDark = theme === 'dark';
20
+ export function ThemeProvider({ children, className, style, value, ...props }: ThemeProviderProps) {
21
+ const systemScheme = useColorScheme();
22
+ const theme = value || 'system';
23
+ const resolvedTheme = theme === 'system' ? (systemScheme === 'dark' ? 'dark' : 'light') : theme;
24
+ const tokens = resolvedTheme === 'dark' ? designTokens.dark : designTokens.light;
25
+
26
+ const isDark = resolvedTheme === 'dark';
27
+
28
+ const contextValue = React.useMemo<ThemeContextValue>(
29
+ () => ({
30
+ theme,
31
+ resolvedTheme,
32
+ tokens,
33
+ }),
34
+ [theme, resolvedTheme, tokens],
35
+ );
14
36
 
15
37
  return (
16
- <View
17
- className={cn('flex-1', isDark ? 'dark' : '', className)}
18
- style={[{ backgroundColor: tokens.background }, style]}
19
- {...props}
20
- >
21
- {children}
22
- </View>
38
+ <ThemeContext.Provider value={contextValue}>
39
+ <View
40
+ className={cn('flex-1', isDark ? 'dark' : '', className)}
41
+ style={[{ backgroundColor: tokens.background }, style]}
42
+ {...props}
43
+ >
44
+ {children}
45
+ </View>
46
+ </ThemeContext.Provider>
23
47
  );
24
48
  }
@@ -15,7 +15,7 @@ iconWithClassName(SunMoon);
15
15
  export type ThemeToggleProps = ThemeToggleBaseProps;
16
16
 
17
17
  export function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, className }: ThemeToggleProps) {
18
- const { theme, resolvedTheme } = useTheme();
18
+ const { theme, resolvedTheme, tokens } = useTheme();
19
19
 
20
20
  const currentTheme = (customTheme ?? theme) as 'light' | 'dark' | 'system';
21
21
 
@@ -45,7 +45,7 @@ export function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, cl
45
45
  !isSystem && !isDark ? 'rotate-0 opacity-100' : 'absolute -rotate-90 opacity-0',
46
46
  )}
47
47
  >
48
- <Sun size={18} className="text-foreground" />
48
+ <Sun size={18} color={tokens.foreground} />
49
49
  </View>
50
50
  <View
51
51
  className={cn(
@@ -53,7 +53,7 @@ export function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, cl
53
53
  !isSystem && isDark ? 'rotate-0 opacity-100' : 'absolute rotate-90 opacity-0',
54
54
  )}
55
55
  >
56
- <Moon size={18} className="text-foreground" />
56
+ <Moon size={18} color={tokens.foreground} />
57
57
  </View>
58
58
  <View
59
59
  className={cn(
@@ -61,7 +61,7 @@ export function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, cl
61
61
  isSystem ? 'rotate-0 opacity-100' : 'absolute rotate-90 opacity-0',
62
62
  )}
63
63
  >
64
- <SunMoon size={18} className="text-foreground" />
64
+ <SunMoon size={18} color={tokens.foreground} />
65
65
  </View>
66
66
  </View>
67
67
  );
@@ -69,22 +69,25 @@ export function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, cl
69
69
  if (variant === 'ternary') {
70
70
  return (
71
71
  <DropdownMenu>
72
- <DropdownMenuTrigger asChild>
73
- <Button variant="ghost" size="icon" className={cn('relative h-9 w-9', className)}>
74
- <IconToggle />
75
- </Button>
72
+ <DropdownMenuTrigger
73
+ className={cn(
74
+ 'relative h-9 w-9 flex-row items-center justify-center rounded-md bg-transparent transition-colors active:opacity-80',
75
+ className,
76
+ )}
77
+ >
78
+ <IconToggle />
76
79
  </DropdownMenuTrigger>
77
80
  <DropdownMenuContent align="end">
78
81
  <DropdownMenuItem onSelect={() => handleThemeChange('light')}>
79
- <Sun size={14} className="text-foreground mr-2" />
82
+ <Sun size={14} color={tokens.foreground} className="mr-2" />
80
83
  <Text>Light</Text>
81
84
  </DropdownMenuItem>
82
85
  <DropdownMenuItem onSelect={() => handleThemeChange('dark')}>
83
- <Moon size={14} className="text-foreground mr-2" />
86
+ <Moon size={14} color={tokens.foreground} className="mr-2" />
84
87
  <Text>Dark</Text>
85
88
  </DropdownMenuItem>
86
89
  <DropdownMenuItem onSelect={() => handleThemeChange('system')}>
87
- <SunMoon size={14} className="text-foreground mr-2" />
90
+ <SunMoon size={14} color={tokens.foreground} className="mr-2" />
88
91
  <Text>System</Text>
89
92
  </DropdownMenuItem>
90
93
  </DropdownMenuContent>