@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.
- package/dist/button-group.d.ts +9 -0
- package/dist/button-group.d.ts.map +1 -0
- package/dist/carousel.d.ts +17 -5
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.test.d.ts +2 -0
- package/dist/carousel.test.d.ts.map +1 -0
- package/dist/combobox.d.ts +22 -0
- package/dist/combobox.d.ts.map +1 -0
- package/dist/direction.d.ts +5 -0
- package/dist/direction.d.ts.map +1 -0
- package/dist/empty.d.ts +11 -0
- package/dist/empty.d.ts.map +1 -0
- package/dist/field.d.ts +15 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/form.d.ts +30 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/form.test.d.ts +2 -0
- package/dist/form.test.d.ts.map +1 -0
- package/dist/hooks/use-theme.d.ts +16 -0
- package/dist/hooks/use-theme.d.ts.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/input-group.d.ts +12 -0
- package/dist/input-group.d.ts.map +1 -0
- package/dist/input-otp.d.ts +9 -0
- package/dist/input-otp.d.ts.map +1 -0
- package/dist/item.d.ts +15 -0
- package/dist/item.d.ts.map +1 -0
- package/dist/kbd.d.ts +7 -0
- package/dist/kbd.d.ts.map +1 -0
- package/dist/native-select.d.ts +8 -0
- package/dist/native-select.d.ts.map +1 -0
- package/dist/scroll-area.d.ts.map +1 -1
- package/dist/search.d.ts +9 -2
- package/dist/search.d.ts.map +1 -1
- package/dist/sidebar.d.ts +37 -0
- package/dist/sidebar.d.ts.map +1 -0
- package/dist/sonner.d.ts.map +1 -1
- package/dist/spinner.d.ts +6 -0
- package/dist/spinner.d.ts.map +1 -0
- package/dist/ui-native.cjs +2 -2
- package/dist/ui-native.mjs +1702 -907
- package/package.json +10 -5
- package/src/button-group.tsx +30 -0
- package/src/carousel.tsx +191 -14
- package/src/combobox.tsx +125 -0
- package/src/direction.tsx +12 -0
- package/src/empty.tsx +38 -0
- package/src/field.tsx +89 -0
- package/src/form.tsx +166 -4
- package/src/index.ts +108 -0
- package/src/input-group.tsx +54 -0
- package/src/input-otp.tsx +34 -0
- package/src/item.tsx +74 -0
- package/src/kbd.tsx +16 -0
- package/src/native-select.tsx +30 -0
- package/src/scroll-area.tsx +4 -2
- package/src/search.tsx +74 -12
- package/src/sidebar.tsx +209 -0
- package/src/sonner.tsx +7 -3
- 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
|
-
|
|
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
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
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 };
|
package/src/scroll-area.tsx
CHANGED
|
@@ -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={
|
|
13
|
-
showsHorizontalScrollIndicator={
|
|
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 {
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
<
|
|
14
|
-
|
|
15
|
-
</
|
|
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';
|