@gv-tech/ui-native 2.19.0 → 2.21.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/src/drawer.tsx CHANGED
@@ -1,9 +1,135 @@
1
- import { Text, View } from 'react-native';
1
+ import type {
2
+ DrawerBaseProps,
3
+ DrawerCloseBaseProps,
4
+ DrawerContentBaseProps,
5
+ DrawerDescriptionBaseProps,
6
+ DrawerFooterBaseProps,
7
+ DrawerHeaderBaseProps,
8
+ DrawerTitleBaseProps,
9
+ DrawerTriggerBaseProps,
10
+ } from '@gv-tech/ui-core';
11
+ import * as DialogPrimitive from '@rn-primitives/dialog';
12
+ import * as React from 'react';
13
+ import { StyleSheet, View, type ViewStyle } from 'react-native';
14
+ import Animated, { FadeIn, FadeOut, SlideInDown, SlideOutDown } from 'react-native-reanimated';
15
+ import { wrapTextChildren } from './lib/render-native';
16
+ import { cn } from './lib/utils';
2
17
 
3
- export const Drawer = () => {
18
+ export const Drawer: React.FC<DrawerBaseProps> = ({ children }) => {
19
+ return <DialogPrimitive.Root>{children}</DialogPrimitive.Root>;
20
+ };
21
+ Drawer.displayName = 'Drawer';
22
+
23
+ export const DrawerTrigger = React.forwardRef<
24
+ React.ElementRef<typeof DialogPrimitive.Trigger>,
25
+ DrawerTriggerBaseProps & { className?: string }
26
+ >(({ children, className, ...props }, ref) => {
4
27
  return (
5
- <View>
6
- <Text>drawer is not yet implemented for React Native</Text>
7
- </View>
28
+ <DialogPrimitive.Trigger ref={ref} className={className} {...props}>
29
+ {wrapTextChildren(children)}
30
+ </DialogPrimitive.Trigger>
8
31
  );
9
- };
32
+ });
33
+ DrawerTrigger.displayName = 'DrawerTrigger';
34
+
35
+ export const DrawerPortal = DialogPrimitive.Portal;
36
+
37
+ export type DrawerOverlayProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>;
38
+ export type DrawerOverlayRef = React.ElementRef<typeof DialogPrimitive.Overlay>;
39
+
40
+ export const DrawerOverlay: React.ForwardRefExoticComponent<
41
+ DrawerOverlayProps & React.RefAttributes<DrawerOverlayRef>
42
+ > = React.forwardRef<DrawerOverlayRef, DrawerOverlayProps>(({ className, ...props }, ref) => {
43
+ return (
44
+ <DialogPrimitive.Overlay style={StyleSheet.absoluteFill} asChild ref={ref} {...props}>
45
+ <Animated.View
46
+ entering={FadeIn.duration(150)}
47
+ exiting={FadeOut.duration(150)}
48
+ className={cn('z-50 bg-black/80', className)}
49
+ />
50
+ </DialogPrimitive.Overlay>
51
+ );
52
+ });
53
+ DrawerOverlay.displayName = 'DrawerOverlay';
54
+
55
+ export const DrawerContent = React.forwardRef<
56
+ React.ElementRef<typeof DialogPrimitive.Content>,
57
+ DrawerContentBaseProps & {
58
+ portalHost?: string;
59
+ overlayClassName?: string;
60
+ overlayStyle?: ViewStyle;
61
+ }
62
+ >(({ className, children, portalHost, overlayClassName, overlayStyle, ...props }, ref) => {
63
+ return (
64
+ <DrawerPortal hostName={portalHost}>
65
+ <DrawerOverlay className={overlayClassName} style={overlayStyle} />
66
+ <DialogPrimitive.Content ref={ref} asChild {...props}>
67
+ <Animated.View
68
+ entering={SlideInDown.duration(200)}
69
+ exiting={SlideOutDown.duration(200)}
70
+ className={cn(
71
+ 'border-border bg-background fixed inset-x-0 bottom-0 z-50 flex h-auto flex-col rounded-t-xl border p-6 pb-10 shadow-lg',
72
+ className,
73
+ )}
74
+ >
75
+ <View className="bg-muted mx-auto mb-4 h-1.5 w-12 rounded-full" />
76
+ {children}
77
+ </Animated.View>
78
+ </DialogPrimitive.Content>
79
+ </DrawerPortal>
80
+ );
81
+ });
82
+ DrawerContent.displayName = 'DrawerContent';
83
+
84
+ export const DrawerHeader = ({ className, children, ...props }: DrawerHeaderBaseProps) => (
85
+ <View className={cn('flex flex-col gap-1.5 text-center sm:text-left', className)} {...props}>
86
+ {wrapTextChildren(children)}
87
+ </View>
88
+ );
89
+ DrawerHeader.displayName = 'DrawerHeader';
90
+
91
+ export const DrawerFooter = ({ className, children, ...props }: DrawerFooterBaseProps) => (
92
+ <View className={cn('mt-auto flex flex-col gap-2', className)} {...props}>
93
+ {wrapTextChildren(children)}
94
+ </View>
95
+ );
96
+ DrawerFooter.displayName = 'DrawerFooter';
97
+
98
+ export const DrawerClose = React.forwardRef<
99
+ React.ElementRef<typeof DialogPrimitive.Close>,
100
+ DrawerCloseBaseProps & { className?: string }
101
+ >(({ children, className, ...props }, ref) => {
102
+ return (
103
+ <DialogPrimitive.Close ref={ref} className={className} {...props}>
104
+ {wrapTextChildren(children)}
105
+ </DialogPrimitive.Close>
106
+ );
107
+ });
108
+ DrawerClose.displayName = 'DrawerClose';
109
+
110
+ export const DrawerTitle = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Title>, DrawerTitleBaseProps>(
111
+ ({ className, children, ...props }, ref) => {
112
+ return (
113
+ <DialogPrimitive.Title
114
+ ref={ref}
115
+ className={cn('text-foreground text-lg leading-none font-semibold tracking-tight', className)}
116
+ {...props}
117
+ >
118
+ {wrapTextChildren(children)}
119
+ </DialogPrimitive.Title>
120
+ );
121
+ },
122
+ );
123
+ DrawerTitle.displayName = 'DrawerTitle';
124
+
125
+ export const DrawerDescription = React.forwardRef<
126
+ React.ElementRef<typeof DialogPrimitive.Description>,
127
+ DrawerDescriptionBaseProps
128
+ >(({ className, children, ...props }, ref) => {
129
+ return (
130
+ <DialogPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props}>
131
+ {wrapTextChildren(children)}
132
+ </DialogPrimitive.Description>
133
+ );
134
+ });
135
+ DrawerDescription.displayName = 'DrawerDescription';
@@ -1,9 +1,221 @@
1
- import { Text, View } from 'react-native';
1
+ import type {
2
+ DropdownMenuCheckboxItemBaseProps,
3
+ DropdownMenuContentBaseProps,
4
+ DropdownMenuItemBaseProps,
5
+ DropdownMenuLabelBaseProps,
6
+ DropdownMenuRadioItemBaseProps,
7
+ DropdownMenuSeparatorBaseProps,
8
+ DropdownMenuShortcutBaseProps,
9
+ DropdownMenuSubContentBaseProps,
10
+ DropdownMenuSubTriggerBaseProps,
11
+ } from '@gv-tech/ui-core';
12
+ import * as DropdownMenuPrimitive from '@rn-primitives/dropdown-menu';
13
+ import { Check, ChevronRight, Circle } from 'lucide-react-native';
14
+ import * as React from 'react';
15
+ import { Platform, StyleSheet, View } from 'react-native';
16
+ import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
17
+ import { wrapTextChildren } from './lib/render-native';
18
+ import { cn } from './lib/utils';
19
+ import { Text } from './text';
2
20
 
3
- export const DropdownMenu = () => {
21
+ export const DropdownMenu = DropdownMenuPrimitive.Root;
22
+
23
+ export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
24
+
25
+ export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
26
+
27
+ export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
28
+
29
+ export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
30
+
31
+ export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
32
+
33
+ const DropdownMenuOverlay = React.forwardRef<
34
+ React.ElementRef<typeof DropdownMenuPrimitive.Overlay>,
35
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Overlay>
36
+ >(({ className, ...props }, ref) => {
37
+ return (
38
+ <DropdownMenuPrimitive.Overlay
39
+ style={Platform.OS !== 'web' ? StyleSheet.absoluteFill : undefined}
40
+ ref={ref}
41
+ {...props}
42
+ >
43
+ <Animated.View
44
+ entering={FadeIn.duration(100)}
45
+ exiting={FadeOut.duration(100)}
46
+ className={cn('absolute inset-0 z-50 bg-black/30', className)}
47
+ />
48
+ </DropdownMenuPrimitive.Overlay>
49
+ );
50
+ });
51
+ DropdownMenuOverlay.displayName = 'DropdownMenuOverlay';
52
+
53
+ export const DropdownMenuContent = React.forwardRef<
54
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
55
+ DropdownMenuContentBaseProps
56
+ >(({ className, children, side, ...props }, ref) => {
57
+ const nativeSide = side === 'left' || side === 'right' ? 'bottom' : side;
4
58
  return (
5
- <View>
6
- <Text>dropdown-menu is not yet implemented for React Native</Text>
7
- </View>
59
+ <DropdownMenuPortal>
60
+ <DropdownMenuOverlay />
61
+ <DropdownMenuPrimitive.Content
62
+ ref={ref}
63
+ side={nativeSide}
64
+ className={cn(
65
+ 'bg-popover border-border z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
66
+ className,
67
+ )}
68
+ {...props}
69
+ >
70
+ {children}
71
+ </DropdownMenuPrimitive.Content>
72
+ </DropdownMenuPortal>
73
+ );
74
+ });
75
+ DropdownMenuContent.displayName = 'DropdownMenuContent';
76
+
77
+ export const DropdownMenuItem = React.forwardRef<
78
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
79
+ DropdownMenuItemBaseProps
80
+ >(({ className, children, inset, ...props }, ref) => {
81
+ return (
82
+ <DropdownMenuPrimitive.Item
83
+ ref={ref}
84
+ className={cn(
85
+ '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
+ inset && 'pl-8',
87
+ className,
88
+ )}
89
+ {...props}
90
+ >
91
+ {wrapTextChildren(children, Text)}
92
+ </DropdownMenuPrimitive.Item>
93
+ );
94
+ });
95
+ DropdownMenuItem.displayName = 'DropdownMenuItem';
96
+
97
+ export const DropdownMenuCheckboxItem = React.forwardRef<
98
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
99
+ DropdownMenuCheckboxItemBaseProps
100
+ >(({ className, children, checked, onCheckedChange, ...props }, ref) => {
101
+ return (
102
+ <DropdownMenuPrimitive.CheckboxItem
103
+ ref={ref}
104
+ checked={!!checked}
105
+ onCheckedChange={onCheckedChange || (() => {})}
106
+ className={cn(
107
+ 'focus:bg-accent focus:text-accent-foreground active:bg-accent active:text-accent-foreground relative flex flex-row items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none',
108
+ className,
109
+ )}
110
+ {...props}
111
+ >
112
+ <View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
113
+ <DropdownMenuPrimitive.ItemIndicator>
114
+ <Check size={14} className="text-foreground" />
115
+ </DropdownMenuPrimitive.ItemIndicator>
116
+ </View>
117
+ {wrapTextChildren(children, Text)}
118
+ </DropdownMenuPrimitive.CheckboxItem>
119
+ );
120
+ });
121
+ DropdownMenuCheckboxItem.displayName = 'DropdownMenuCheckboxItem';
122
+
123
+ export const DropdownMenuRadioItem = React.forwardRef<
124
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
125
+ DropdownMenuRadioItemBaseProps
126
+ >(({ className, children, value, ...props }, ref) => {
127
+ return (
128
+ <DropdownMenuPrimitive.RadioItem
129
+ ref={ref}
130
+ value={value}
131
+ className={cn(
132
+ 'focus:bg-accent focus:text-accent-foreground active:bg-accent active:text-accent-foreground relative flex flex-row items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none',
133
+ className,
134
+ )}
135
+ {...props}
136
+ >
137
+ <View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
138
+ <DropdownMenuPrimitive.ItemIndicator>
139
+ <Circle size={8} className="text-foreground fill-current" />
140
+ </DropdownMenuPrimitive.ItemIndicator>
141
+ </View>
142
+ {wrapTextChildren(children, Text)}
143
+ </DropdownMenuPrimitive.RadioItem>
144
+ );
145
+ });
146
+ DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem';
147
+
148
+ export const DropdownMenuLabel = React.forwardRef<
149
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
150
+ DropdownMenuLabelBaseProps
151
+ >(({ className, children, inset, ...props }, ref) => {
152
+ return (
153
+ <DropdownMenuPrimitive.Label
154
+ ref={ref}
155
+ className={cn('text-foreground px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
156
+ {...props}
157
+ >
158
+ {wrapTextChildren(children, Text)}
159
+ </DropdownMenuPrimitive.Label>
160
+ );
161
+ });
162
+ DropdownMenuLabel.displayName = 'DropdownMenuLabel';
163
+
164
+ export const DropdownMenuSeparator = React.forwardRef<
165
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
166
+ DropdownMenuSeparatorBaseProps
167
+ >(({ className, ...props }, ref) => {
168
+ return (
169
+ <DropdownMenuPrimitive.Separator ref={ref} className={cn('bg-border -mx-1 my-1 h-px', className)} {...props} />
170
+ );
171
+ });
172
+ DropdownMenuSeparator.displayName = 'DropdownMenuSeparator';
173
+
174
+ export const DropdownMenuShortcut = ({ className, children, ...props }: DropdownMenuShortcutBaseProps) => {
175
+ return (
176
+ <Text className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)} {...props}>
177
+ {children}
178
+ </Text>
8
179
  );
9
180
  };
181
+ DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
182
+
183
+ export const DropdownMenuSubTrigger = React.forwardRef<
184
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
185
+ DropdownMenuSubTriggerBaseProps
186
+ >(({ className, children, inset, ...props }, ref) => {
187
+ return (
188
+ <DropdownMenuPrimitive.SubTrigger
189
+ ref={ref}
190
+ className={cn(
191
+ 'focus:bg-accent focus:text-accent-foreground active:bg-accent active:text-accent-foreground flex flex-row items-center rounded-sm px-2 py-1.5 text-sm outline-none',
192
+ inset && 'pl-8',
193
+ className,
194
+ )}
195
+ {...props}
196
+ >
197
+ <View className="flex flex-row items-center gap-1.5">{wrapTextChildren(children, Text)}</View>
198
+ <ChevronRight size={14} className="text-foreground ml-auto" />
199
+ </DropdownMenuPrimitive.SubTrigger>
200
+ );
201
+ });
202
+ DropdownMenuSubTrigger.displayName = 'DropdownMenuSubTrigger';
203
+
204
+ export const DropdownMenuSubContent = React.forwardRef<
205
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
206
+ DropdownMenuSubContentBaseProps
207
+ >(({ className, children, ...props }, ref) => {
208
+ return (
209
+ <DropdownMenuPrimitive.SubContent
210
+ ref={ref}
211
+ className={cn(
212
+ 'bg-popover border-border z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
213
+ className,
214
+ )}
215
+ {...props}
216
+ >
217
+ {children}
218
+ </DropdownMenuPrimitive.SubContent>
219
+ );
220
+ });
221
+ DropdownMenuSubContent.displayName = 'DropdownMenuSubContent';
@@ -0,0 +1,186 @@
1
+ import * as React from 'react';
2
+ import type { ToastProps } from '../toast';
3
+
4
+ const TOAST_LIMIT = 5;
5
+ const TOAST_REMOVE_DELAY = 1000000;
6
+
7
+ type ToasterToast = ToastProps & {
8
+ id: string;
9
+ title?: React.ReactNode;
10
+ description?: React.ReactNode;
11
+ action?: React.ReactNode;
12
+ };
13
+
14
+ const actionTypes = {
15
+ ADD_TOAST: 'ADD_TOAST',
16
+ UPDATE_TOAST: 'UPDATE_TOAST',
17
+ DISMISS_TOAST: 'DISMISS_TOAST',
18
+ REMOVE_TOAST: 'REMOVE_TOAST',
19
+ } as const;
20
+
21
+ let count = 0;
22
+
23
+ function genId() {
24
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
25
+ return count.toString();
26
+ }
27
+
28
+ type ActionType = typeof actionTypes;
29
+
30
+ type Action =
31
+ | {
32
+ type: ActionType['ADD_TOAST'];
33
+ toast: ToasterToast;
34
+ }
35
+ | {
36
+ type: ActionType['UPDATE_TOAST'];
37
+ toast: Partial<ToasterToast>;
38
+ }
39
+ | {
40
+ type: ActionType['DISMISS_TOAST'];
41
+ toastId?: ToasterToast['id'];
42
+ }
43
+ | {
44
+ type: ActionType['REMOVE_TOAST'];
45
+ toastId?: ToasterToast['id'];
46
+ };
47
+
48
+ interface State {
49
+ toasts: ToasterToast[];
50
+ }
51
+
52
+ const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
53
+
54
+ const addToRemoveQueue = (toastId: string) => {
55
+ if (toastTimeouts.has(toastId)) {
56
+ return;
57
+ }
58
+
59
+ const timeout = setTimeout(() => {
60
+ toastTimeouts.delete(toastId);
61
+ dispatch({
62
+ type: 'REMOVE_TOAST',
63
+ toastId: toastId,
64
+ });
65
+ }, TOAST_REMOVE_DELAY);
66
+
67
+ toastTimeouts.set(toastId, timeout);
68
+ };
69
+
70
+ export const reducer = (state: State, action: Action): State => {
71
+ switch (action.type) {
72
+ case actionTypes.ADD_TOAST:
73
+ return {
74
+ ...state,
75
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
76
+ };
77
+
78
+ case actionTypes.UPDATE_TOAST:
79
+ return {
80
+ ...state,
81
+ toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
82
+ };
83
+
84
+ case actionTypes.DISMISS_TOAST: {
85
+ const { toastId } = action;
86
+
87
+ if (toastId) {
88
+ addToRemoveQueue(toastId);
89
+ } else {
90
+ state.toasts.forEach((toast) => {
91
+ addToRemoveQueue(toast.id);
92
+ });
93
+ }
94
+
95
+ return {
96
+ ...state,
97
+ toasts: state.toasts.map((t) =>
98
+ t.id === toastId || toastId === undefined
99
+ ? {
100
+ ...t,
101
+ open: false,
102
+ }
103
+ : t,
104
+ ),
105
+ };
106
+ }
107
+ case actionTypes.REMOVE_TOAST:
108
+ if (action.toastId === undefined) {
109
+ return {
110
+ ...state,
111
+ toasts: [],
112
+ };
113
+ }
114
+ return {
115
+ ...state,
116
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
117
+ };
118
+ }
119
+ };
120
+
121
+ const listeners: Array<(state: State) => void> = [];
122
+
123
+ let memoryState: State = { toasts: [] };
124
+
125
+ function dispatch(action: Action) {
126
+ memoryState = reducer(memoryState, action);
127
+ listeners.forEach((listener) => {
128
+ listener(memoryState);
129
+ });
130
+ }
131
+
132
+ type Toast = Omit<ToasterToast, 'id'>;
133
+
134
+ function toast({ ...props }: Toast) {
135
+ const id = genId();
136
+
137
+ const update = (props: ToasterToast) =>
138
+ dispatch({
139
+ type: 'UPDATE_TOAST',
140
+ toast: { ...props, id },
141
+ });
142
+ const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
143
+
144
+ dispatch({
145
+ type: 'ADD_TOAST',
146
+ toast: {
147
+ ...props,
148
+ id,
149
+ open: true,
150
+ onOpenChange: (open) => {
151
+ if (!open) {
152
+ dismiss();
153
+ }
154
+ },
155
+ },
156
+ });
157
+
158
+ return {
159
+ id: id,
160
+ dismiss,
161
+ update,
162
+ };
163
+ }
164
+
165
+ function useToast() {
166
+ const [state, setState] = React.useState<State>(memoryState);
167
+
168
+ React.useEffect(() => {
169
+ listeners.push(setState);
170
+ return () => {
171
+ const index = listeners.indexOf(setState);
172
+ if (index > -1) {
173
+ listeners.splice(index, 1);
174
+ }
175
+ };
176
+ }, []);
177
+
178
+ return {
179
+ ...state,
180
+ toast,
181
+ dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
182
+ };
183
+ }
184
+
185
+ export { toast, useToast };
186
+ export type { ToasterToast };
@@ -1,9 +1,29 @@
1
- import { Text, View } from 'react-native';
1
+ import type { HoverCardContentBaseProps } from '@gv-tech/ui-core';
2
+ import * as HoverCardPrimitive from '@rn-primitives/hover-card';
3
+ import * as React from 'react';
4
+ import { cn } from './lib/utils';
2
5
 
3
- export const HoverCard = () => {
6
+ export const HoverCard = HoverCardPrimitive.Root;
7
+
8
+ export const HoverCardTrigger = HoverCardPrimitive.Trigger;
9
+
10
+ export const HoverCardContent = React.forwardRef<
11
+ React.ElementRef<typeof HoverCardPrimitive.Content>,
12
+ HoverCardContentBaseProps
13
+ >(({ className, children, ...props }, ref) => {
4
14
  return (
5
- <View>
6
- <Text>hover-card is not yet implemented for React Native</Text>
7
- </View>
15
+ <HoverCardPrimitive.Portal>
16
+ <HoverCardPrimitive.Content
17
+ ref={ref}
18
+ className={cn(
19
+ 'bg-popover text-popover-foreground border-border z-50 w-64 rounded-md border p-4 shadow-md',
20
+ className,
21
+ )}
22
+ {...props}
23
+ >
24
+ {children}
25
+ </HoverCardPrimitive.Content>
26
+ </HoverCardPrimitive.Portal>
8
27
  );
9
- };
28
+ });
29
+ HoverCardContent.displayName = HoverCardPrimitive.Content?.displayName || 'HoverCardContent';