@gv-tech/ui-native 2.22.1 → 2.23.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 (61) hide show
  1. package/dist/button-group.d.ts +9 -0
  2. package/dist/button-group.d.ts.map +1 -0
  3. package/dist/carousel.d.ts +17 -5
  4. package/dist/carousel.d.ts.map +1 -1
  5. package/dist/carousel.test.d.ts +2 -0
  6. package/dist/carousel.test.d.ts.map +1 -0
  7. package/dist/combobox.d.ts +22 -0
  8. package/dist/combobox.d.ts.map +1 -0
  9. package/dist/direction.d.ts +5 -0
  10. package/dist/direction.d.ts.map +1 -0
  11. package/dist/empty.d.ts +11 -0
  12. package/dist/empty.d.ts.map +1 -0
  13. package/dist/field.d.ts +15 -0
  14. package/dist/field.d.ts.map +1 -0
  15. package/dist/form.d.ts +30 -1
  16. package/dist/form.d.ts.map +1 -1
  17. package/dist/form.test.d.ts +2 -0
  18. package/dist/form.test.d.ts.map +1 -0
  19. package/dist/hooks/use-theme.d.ts +16 -0
  20. package/dist/hooks/use-theme.d.ts.map +1 -1
  21. package/dist/index.d.ts +12 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/input-group.d.ts +12 -0
  24. package/dist/input-group.d.ts.map +1 -0
  25. package/dist/input-otp.d.ts +9 -0
  26. package/dist/input-otp.d.ts.map +1 -0
  27. package/dist/item.d.ts +15 -0
  28. package/dist/item.d.ts.map +1 -0
  29. package/dist/kbd.d.ts +7 -0
  30. package/dist/kbd.d.ts.map +1 -0
  31. package/dist/native-select.d.ts +8 -0
  32. package/dist/native-select.d.ts.map +1 -0
  33. package/dist/scroll-area.d.ts.map +1 -1
  34. package/dist/search.d.ts +9 -2
  35. package/dist/search.d.ts.map +1 -1
  36. package/dist/sidebar.d.ts +37 -0
  37. package/dist/sidebar.d.ts.map +1 -0
  38. package/dist/sonner.d.ts.map +1 -1
  39. package/dist/spinner.d.ts +6 -0
  40. package/dist/spinner.d.ts.map +1 -0
  41. package/dist/ui-native.cjs +2 -2
  42. package/dist/ui-native.mjs +1702 -907
  43. package/package.json +10 -5
  44. package/src/button-group.tsx +30 -0
  45. package/src/carousel.tsx +191 -14
  46. package/src/combobox.tsx +125 -0
  47. package/src/direction.tsx +12 -0
  48. package/src/empty.tsx +38 -0
  49. package/src/field.tsx +89 -0
  50. package/src/form.tsx +166 -4
  51. package/src/index.ts +108 -0
  52. package/src/input-group.tsx +54 -0
  53. package/src/input-otp.tsx +34 -0
  54. package/src/item.tsx +74 -0
  55. package/src/kbd.tsx +16 -0
  56. package/src/native-select.tsx +30 -0
  57. package/src/scroll-area.tsx +4 -2
  58. package/src/search.tsx +74 -12
  59. package/src/sidebar.tsx +209 -0
  60. package/src/sonner.tsx +7 -3
  61. package/src/spinner.tsx +9 -0
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
  };
package/src/index.ts CHANGED
@@ -327,3 +327,111 @@ export {
327
327
  TableOfContentsHeading,
328
328
  TableOfContentsList,
329
329
  } from './table-of-contents';
330
+
331
+ // Button Group
332
+ export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants } from './button-group';
333
+
334
+ // Direction
335
+ export { DirectionProvider, useDirection } from './direction';
336
+
337
+ // Empty
338
+ export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from './empty';
339
+
340
+ // Spinner
341
+ export { Spinner } from './spinner';
342
+
343
+ // Field
344
+ export {
345
+ Field,
346
+ FieldContent,
347
+ FieldDescription,
348
+ FieldError,
349
+ FieldGroup,
350
+ FieldLabel,
351
+ FieldLegend,
352
+ FieldSeparator,
353
+ FieldSet,
354
+ FieldTitle,
355
+ } from './field';
356
+
357
+ // Input Group
358
+ export {
359
+ InputGroup,
360
+ InputGroupAddon,
361
+ InputGroupButton,
362
+ InputGroupInput,
363
+ InputGroupText,
364
+ InputGroupTextarea,
365
+ } from './input-group';
366
+
367
+ // Native Select
368
+ export { NativeSelect, NativeSelectOptGroup, NativeSelectOption } from './native-select';
369
+
370
+ // Kbd
371
+ export { Kbd, KbdGroup } from './kbd';
372
+
373
+ // Item
374
+ export {
375
+ Item,
376
+ ItemActions,
377
+ ItemContent,
378
+ ItemDescription,
379
+ ItemFooter,
380
+ ItemGroup,
381
+ ItemHeader,
382
+ ItemMedia,
383
+ ItemSeparator,
384
+ ItemTitle,
385
+ } from './item';
386
+
387
+ // Combobox
388
+ export {
389
+ Combobox,
390
+ ComboboxChip,
391
+ ComboboxChips,
392
+ ComboboxChipsInput,
393
+ ComboboxClear,
394
+ ComboboxCollection,
395
+ ComboboxContent,
396
+ ComboboxEmpty,
397
+ ComboboxGroup,
398
+ ComboboxInput,
399
+ ComboboxItem,
400
+ ComboboxLabel,
401
+ ComboboxList,
402
+ ComboboxSeparator,
403
+ ComboboxTrigger,
404
+ ComboboxValue,
405
+ useComboboxAnchor,
406
+ } from './combobox';
407
+
408
+ // Input OTP
409
+ export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './input-otp';
410
+
411
+ // Sidebar
412
+ export {
413
+ Sidebar,
414
+ SidebarContent,
415
+ SidebarFooter,
416
+ SidebarGroup,
417
+ SidebarGroupAction,
418
+ SidebarGroupContent,
419
+ SidebarGroupLabel,
420
+ SidebarHeader,
421
+ SidebarInput,
422
+ SidebarInset,
423
+ SidebarMenu,
424
+ SidebarMenuAction,
425
+ SidebarMenuBadge,
426
+ SidebarMenuButton,
427
+ SidebarMenuItem,
428
+ SidebarMenuSkeleton,
429
+ SidebarMenuSub,
430
+ SidebarMenuSubButton,
431
+ SidebarMenuSubItem,
432
+ SidebarProvider,
433
+ SidebarRail,
434
+ SidebarSeparator,
435
+ SidebarTrigger,
436
+ useSidebar,
437
+ } from './sidebar';
@@ -0,0 +1,54 @@
1
+ import type {
2
+ InputGroupAddonBaseProps,
3
+ InputGroupBaseProps,
4
+ InputGroupButtonBaseProps,
5
+ InputGroupInputBaseProps,
6
+ InputGroupTextareaBaseProps,
7
+ InputGroupTextBaseProps,
8
+ } from '@gv-tech/ui-core';
9
+ import * as React from 'react';
10
+ import { Text, TextInput, View } from 'react-native';
11
+ import { Button } from './button';
12
+ import { cn } from './lib/utils';
13
+
14
+ function InputGroup({ className, ...props }: React.ComponentProps<typeof View> & InputGroupBaseProps) {
15
+ return <View className={cn('border-input flex flex-row items-center rounded-md border', className)} {...props} />;
16
+ }
17
+
18
+ function InputGroupAddon({ className, align, ...props }: React.ComponentProps<typeof View> & InputGroupAddonBaseProps) {
19
+ return <View className={cn('p-2', className)} {...props} />;
20
+ }
21
+
22
+ function InputGroupButton({
23
+ className,
24
+ type,
25
+ variant,
26
+ size,
27
+ ...props
28
+ }: React.ComponentProps<typeof Button> & InputGroupButtonBaseProps) {
29
+ return (
30
+ <Button
31
+ variant={variant as React.ComponentProps<typeof Button>['variant']}
32
+ size={size as React.ComponentProps<typeof Button>['size']}
33
+ className={className}
34
+ {...(props as Record<string, unknown>)}
35
+ />
36
+ );
37
+ }
38
+
39
+ function InputGroupText({ className, ...props }: React.ComponentProps<typeof Text> & InputGroupTextBaseProps) {
40
+ return <Text className={cn('text-muted-foreground text-sm', className)} {...props} />;
41
+ }
42
+
43
+ function InputGroupInput({ className, ...props }: React.ComponentProps<typeof TextInput> & InputGroupInputBaseProps) {
44
+ return <TextInput className={cn('text-foreground flex-1 px-3 py-2', className)} {...props} />;
45
+ }
46
+
47
+ function InputGroupTextarea({
48
+ className,
49
+ ...props
50
+ }: React.ComponentProps<typeof TextInput> & InputGroupTextareaBaseProps) {
51
+ return <TextInput multiline className={cn('text-foreground flex-1 px-3 py-2', className)} {...props} />;
52
+ }
53
+
54
+ export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea };
@@ -0,0 +1,34 @@
1
+ import type {
2
+ InputOTPBaseProps,
3
+ InputOTPGroupBaseProps,
4
+ InputOTPSeparatorBaseProps,
5
+ InputOTPSlotBaseProps,
6
+ } from '@gv-tech/ui-core';
7
+ import * as React from 'react';
8
+ import { Text, View } from 'react-native';
9
+ import { cn } from './lib/utils';
10
+
11
+ function InputOTP({ className, containerClassName, ...props }: React.ComponentProps<typeof View> & InputOTPBaseProps) {
12
+ return (
13
+ <View
14
+ className={cn('border-destructive/50 rounded-md border border-dashed p-4', containerClassName, className)}
15
+ {...props}
16
+ >
17
+ <Text className="text-destructive font-mono text-xs">InputOTP (Not Implemented)</Text>
18
+ </View>
19
+ );
20
+ }
21
+
22
+ function InputOTPGroup({ className, ...props }: React.ComponentProps<typeof View> & InputOTPGroupBaseProps) {
23
+ return <View className={className} {...props} />;
24
+ }
25
+
26
+ function InputOTPSlot({ className, index, ...props }: React.ComponentProps<typeof View> & InputOTPSlotBaseProps) {
27
+ return <View className={className} {...props} />;
28
+ }
29
+
30
+ function InputOTPSeparator({ className, ...props }: React.ComponentProps<typeof View> & InputOTPSeparatorBaseProps) {
31
+ return <View className={className} {...props} />;
32
+ }
33
+
34
+ export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot };
package/src/item.tsx ADDED
@@ -0,0 +1,74 @@
1
+ import type {
2
+ ItemActionsBaseProps,
3
+ ItemBaseProps,
4
+ ItemContentBaseProps,
5
+ ItemDescriptionBaseProps,
6
+ ItemFooterBaseProps,
7
+ ItemGroupBaseProps,
8
+ ItemHeaderBaseProps,
9
+ ItemMediaBaseProps,
10
+ ItemSeparatorBaseProps,
11
+ ItemTitleBaseProps,
12
+ } from '@gv-tech/ui-core';
13
+ import * as React from 'react';
14
+ import { Text, View } from 'react-native';
15
+ import { cn } from './lib/utils';
16
+
17
+ function Item({ className, variant, size, ...props }: React.ComponentProps<typeof View> & ItemBaseProps) {
18
+ return (
19
+ <View className={cn('flex flex-row flex-wrap items-center gap-2 rounded-lg border p-3', className)} {...props} />
20
+ );
21
+ }
22
+
23
+ function ItemActions({ className, ...props }: React.ComponentProps<typeof View> & ItemActionsBaseProps) {
24
+ return <View className={cn('flex flex-row items-center gap-2', className)} {...props} />;
25
+ }
26
+
27
+ function ItemContent({ className, ...props }: React.ComponentProps<typeof View> & ItemContentBaseProps) {
28
+ return <View className={cn('flex flex-1 flex-col gap-1', className)} {...props} />;
29
+ }
30
+
31
+ function ItemDescription({ className, ...props }: React.ComponentProps<typeof Text> & ItemDescriptionBaseProps) {
32
+ return <Text className={cn('text-muted-foreground text-sm', className)} {...props} />;
33
+ }
34
+
35
+ function ItemFooter({ className, ...props }: React.ComponentProps<typeof View> & ItemFooterBaseProps) {
36
+ return <View className={cn('flex w-full flex-row items-center justify-between gap-2', className)} {...props} />;
37
+ }
38
+
39
+ function ItemGroup({ className, ...props }: React.ComponentProps<typeof View> & ItemGroupBaseProps) {
40
+ return <View className={cn('flex flex-col gap-4', className)} {...props} />;
41
+ }
42
+
43
+ function ItemHeader({ className, ...props }: React.ComponentProps<typeof View> & ItemHeaderBaseProps) {
44
+ return <View className={cn('flex w-full flex-row items-center justify-between gap-2', className)} {...props} />;
45
+ }
46
+
47
+ function ItemMedia({ className, variant, ...props }: React.ComponentProps<typeof View> & ItemMediaBaseProps) {
48
+ return <View className={cn('flex shrink-0 items-center justify-center gap-2', className)} {...props} />;
49
+ }
50
+
51
+ function ItemSeparator({
52
+ className,
53
+ orientation,
54
+ ...props
55
+ }: React.ComponentProps<typeof View> & ItemSeparatorBaseProps) {
56
+ return <View className={cn('bg-border my-2 h-px w-full', className)} {...props} />;
57
+ }
58
+
59
+ function ItemTitle({ className, ...props }: React.ComponentProps<typeof Text> & ItemTitleBaseProps) {
60
+ return <Text className={cn('text-sm font-medium', className)} {...props} />;
61
+ }
62
+
63
+ export {
64
+ Item,
65
+ ItemActions,
66
+ ItemContent,
67
+ ItemDescription,
68
+ ItemFooter,
69
+ ItemGroup,
70
+ ItemHeader,
71
+ ItemMedia,
72
+ ItemSeparator,
73
+ ItemTitle,
74
+ };
package/src/kbd.tsx ADDED
@@ -0,0 +1,16 @@
1
+ import type { KbdBaseProps, KbdGroupBaseProps } from '@gv-tech/ui-core';
2
+ import * as React from 'react';
3
+ import { Text, View } from 'react-native';
4
+ import { cn } from './lib/utils';
5
+
6
+ function Kbd({ className, ...props }: React.ComponentProps<typeof Text> & KbdBaseProps) {
7
+ return (
8
+ <Text className={cn('bg-muted text-muted-foreground rounded-sm px-1 font-mono text-xs', className)} {...props} />
9
+ );
10
+ }
11
+
12
+ function KbdGroup({ className, ...props }: React.ComponentProps<typeof View> & KbdGroupBaseProps) {
13
+ return <View className={cn('flex flex-row items-center gap-1', className)} {...props} />;
14
+ }
15
+
16
+ export { Kbd, KbdGroup };
@@ -0,0 +1,30 @@
1
+ import type {
2
+ NativeSelectBaseProps,
3
+ NativeSelectOptGroupBaseProps,
4
+ NativeSelectOptionBaseProps,
5
+ } from '@gv-tech/ui-core';
6
+ import * as React from 'react';
7
+ import { Text, View } from 'react-native';
8
+ import { cn } from './lib/utils';
9
+
10
+ function NativeSelect({ className, size, ...props }: React.ComponentProps<typeof View> & NativeSelectBaseProps) {
11
+ // Native select relies on @react-native-picker/picker, providing shim
12
+ return (
13
+ <View className={cn('border-input rounded-md border p-2', className)} {...props}>
14
+ <Text className="text-muted-foreground">Select Not Implemented (Use Picker)</Text>
15
+ </View>
16
+ );
17
+ }
18
+
19
+ function NativeSelectOption({ className, ...props }: React.ComponentProps<typeof View> & NativeSelectOptionBaseProps) {
20
+ return <View className={className} {...props} />;
21
+ }
22
+
23
+ function NativeSelectOptGroup({
24
+ className,
25
+ ...props
26
+ }: React.ComponentProps<typeof View> & NativeSelectOptGroupBaseProps) {
27
+ return <View className={className} {...props} />;
28
+ }
29
+
30
+ export { NativeSelect, NativeSelectOptGroup, NativeSelectOption };
@@ -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';