@beyondcorp/beyond-ui-native 0.1.2 → 0.1.3

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 (67) hide show
  1. package/package.json +11 -5
  2. package/src/components/accordion/accordion.tsx +245 -0
  3. package/src/components/accordion/index.ts +1 -0
  4. package/src/components/alert/alert.tsx +118 -0
  5. package/src/components/alert/index.ts +1 -0
  6. package/src/components/avatar/avatar.tsx +125 -0
  7. package/src/components/avatar/index.ts +1 -0
  8. package/src/components/badge/badge.tsx +89 -0
  9. package/src/components/badge/index.ts +1 -0
  10. package/src/components/bottom-sheet/bottom-sheet.tsx +336 -0
  11. package/src/components/bottom-sheet/index.ts +1 -0
  12. package/src/components/button/button.tsx +172 -0
  13. package/src/components/button/index.ts +1 -0
  14. package/src/components/card/card.tsx +121 -0
  15. package/src/components/card/index.ts +1 -0
  16. package/src/components/checkbox/checkbox.tsx +100 -0
  17. package/src/components/checkbox/index.ts +1 -0
  18. package/src/components/data-table/data-table.tsx +277 -0
  19. package/src/components/data-table/index.ts +1 -0
  20. package/src/components/dialog/dialog.tsx +278 -0
  21. package/src/components/dialog/index.ts +1 -0
  22. package/src/components/input/index.ts +1 -0
  23. package/src/components/input/input.tsx +130 -0
  24. package/src/components/radio/index.ts +1 -0
  25. package/src/components/radio/radio.tsx +117 -0
  26. package/src/components/select/index.ts +1 -0
  27. package/src/components/select/select.tsx +375 -0
  28. package/src/components/separator/index.ts +1 -0
  29. package/src/components/separator/separator.tsx +28 -0
  30. package/src/components/skeleton/index.ts +1 -0
  31. package/src/components/skeleton/skeleton.tsx +43 -0
  32. package/src/components/spinner/index.ts +1 -0
  33. package/src/components/spinner/spinner.tsx +46 -0
  34. package/src/components/stats-card/index.ts +1 -0
  35. package/src/components/stats-card/stats-card.tsx +92 -0
  36. package/src/components/switch/index.ts +1 -0
  37. package/src/components/switch/switch.tsx +80 -0
  38. package/src/components/tabs/index.ts +1 -0
  39. package/src/components/tabs/tabs.tsx +172 -0
  40. package/src/components/text/heading.tsx +50 -0
  41. package/src/components/text/index.ts +2 -0
  42. package/src/components/text/text.tsx +62 -0
  43. package/src/components/textarea/index.ts +1 -0
  44. package/src/components/textarea/textarea.tsx +87 -0
  45. package/src/components/toast/index.ts +2 -0
  46. package/src/components/toast/toast.tsx +136 -0
  47. package/src/components/toast/types.ts +32 -0
  48. package/src/index.ts +42 -0
  49. package/src/primitives/box.tsx +25 -0
  50. package/src/primitives/index.ts +2 -0
  51. package/src/primitives/pressable.tsx +49 -0
  52. package/src/providers/BeyondUIProvider.tsx +100 -0
  53. package/src/templates/auth/ForgotPasswordTemplate.tsx +132 -0
  54. package/src/templates/auth/SignInTemplate.tsx +213 -0
  55. package/src/templates/auth/SignUpTemplate.tsx +233 -0
  56. package/src/templates/auth/index.ts +3 -0
  57. package/src/templates/dashboard/DashboardTemplate.tsx +205 -0
  58. package/src/templates/dashboard/index.ts +1 -0
  59. package/src/templates/index.ts +3 -0
  60. package/src/templates/settings/SettingsTemplate.tsx +226 -0
  61. package/src/templates/settings/index.ts +1 -0
  62. package/src/theme/preset.ts +45 -0
  63. package/src/theme/tokens.ts +144 -0
  64. package/src/types/nativewind-env.d.ts +26 -0
  65. package/src/utilities/accessibility.ts +43 -0
  66. package/src/utilities/cn.ts +9 -0
  67. package/src/utilities/index.ts +2 -0
@@ -0,0 +1,100 @@
1
+ import * as React from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ Pressable,
6
+ type PressableProps,
7
+ type GestureResponderEvent,
8
+ } from "react-native";
9
+ import { cn } from "../../utilities/cn";
10
+
11
+ export interface CheckboxProps extends Omit<PressableProps, "onPress"> {
12
+ checked?: boolean;
13
+ defaultChecked?: boolean;
14
+ onCheckedChange?: (checked: boolean) => void;
15
+ disabled?: boolean;
16
+ className?: string;
17
+ indicatorClassName?: string;
18
+ label?: string;
19
+ labelClassName?: string;
20
+ onPress?: (e: GestureResponderEvent) => void;
21
+ }
22
+
23
+ export const Checkbox = React.forwardRef<
24
+ React.ElementRef<typeof Pressable>,
25
+ CheckboxProps
26
+ >(function Checkbox(
27
+ {
28
+ checked: controlledChecked,
29
+ defaultChecked = false,
30
+ onCheckedChange,
31
+ disabled = false,
32
+ className,
33
+ indicatorClassName,
34
+ label,
35
+ labelClassName,
36
+ onPress,
37
+ ...props
38
+ },
39
+ ref
40
+ ) {
41
+ const [uncontrolledChecked, setUncontrolledChecked] = React.useState(defaultChecked);
42
+ const isControlled = controlledChecked !== undefined;
43
+ const isChecked = isControlled ? controlledChecked : uncontrolledChecked;
44
+
45
+ const handlePress = (e: GestureResponderEvent) => {
46
+ if (disabled) return;
47
+ const nextChecked = !isChecked;
48
+ if (!isControlled) {
49
+ setUncontrolledChecked(nextChecked);
50
+ }
51
+ onCheckedChange?.(nextChecked);
52
+ onPress?.(e);
53
+ };
54
+
55
+ const content = (
56
+ <Pressable
57
+ ref={ref}
58
+ role="checkbox"
59
+ accessibilityRole="checkbox"
60
+ accessibilityState={{ checked: isChecked, disabled: !!disabled }}
61
+ disabled={disabled}
62
+ onPress={handlePress}
63
+ className={cn(
64
+ "h-6 w-6 shrink-0 items-center justify-center rounded-md border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900",
65
+ isChecked && "bg-primary-600 border-primary-600 dark:bg-primary-600 dark:border-primary-600",
66
+ disabled && "opacity-50",
67
+ className
68
+ )}
69
+ {...props}
70
+ >
71
+ {isChecked ? (
72
+ <View className={cn("items-center justify-center", indicatorClassName)}>
73
+ <Text className="text-white text-xs font-bold leading-none">✓</Text>
74
+ </View>
75
+ ) : null}
76
+ </Pressable>
77
+ );
78
+
79
+ if (!label) {
80
+ return content;
81
+ }
82
+
83
+ return (
84
+ <View className="flex-row items-center gap-2.5">
85
+ {content}
86
+ <Text
87
+ onPress={!disabled ? handlePress : undefined}
88
+ className={cn(
89
+ "text-sm font-medium text-neutral-800 dark:text-neutral-200",
90
+ disabled && "opacity-50",
91
+ labelClassName
92
+ )}
93
+ >
94
+ {label}
95
+ </Text>
96
+ </View>
97
+ );
98
+ });
99
+
100
+ Checkbox.displayName = "Checkbox";
@@ -0,0 +1 @@
1
+ export * from "./checkbox";
@@ -0,0 +1,277 @@
1
+ import * as React from "react";
2
+ import {
3
+ View,
4
+ ScrollView,
5
+ Pressable,
6
+ type ViewProps,
7
+ } from "react-native";
8
+ import { cn } from "../../utilities/cn";
9
+ import { Text } from "../text/text";
10
+ import { Input } from "../input/input";
11
+ import { Button } from "../button/button";
12
+ import { Card, CardContent } from "../card/card";
13
+ import { Skeleton } from "../skeleton/skeleton";
14
+
15
+ export interface Column<T> {
16
+ key: keyof T | string;
17
+ title: string;
18
+ sortable?: boolean;
19
+ align?: "left" | "center" | "right";
20
+ width?: number;
21
+ render?: (value: any, item: T) => React.ReactNode;
22
+ }
23
+
24
+ export interface DataTableProps<T extends Record<string, any>> extends ViewProps {
25
+ columns: Column<T>[];
26
+ data: T[];
27
+ keyExtractor?: (item: T, index: number) => string;
28
+ mode?: "table" | "card";
29
+ searchable?: boolean;
30
+ searchPlaceholder?: string;
31
+ searchKeys?: (keyof T)[];
32
+ loading?: boolean;
33
+ pageSize?: number;
34
+ emptyText?: string;
35
+ onRowPress?: (item: T) => void;
36
+ className?: string;
37
+ }
38
+
39
+ export function DataTable<T extends Record<string, any>>({
40
+ columns,
41
+ data,
42
+ keyExtractor = (_, i) => String(i),
43
+ mode = "card",
44
+ searchable = true,
45
+ searchPlaceholder = "Search records...",
46
+ searchKeys,
47
+ loading = false,
48
+ pageSize = 5,
49
+ emptyText = "No records found",
50
+ onRowPress,
51
+ className,
52
+ ...props
53
+ }: DataTableProps<T>) {
54
+ const [search, setSearch] = React.useState("");
55
+ const [sortKey, setSortKey] = React.useState<string | null>(null);
56
+ const [sortDirection, setSortDirection] = React.useState<"asc" | "desc">("asc");
57
+ const [currentPage, setCurrentPage] = React.useState(1);
58
+
59
+ // Filter
60
+ const filteredData = React.useMemo(() => {
61
+ if (!search.trim()) return data;
62
+ const query = search.toLowerCase();
63
+ const keys = searchKeys || (columns.map((c) => c.key) as (keyof T)[]);
64
+
65
+ return data.filter((item) => {
66
+ return keys.some((k) => {
67
+ const val = item[k];
68
+ if (val == null) return false;
69
+ return String(val).toLowerCase().includes(query);
70
+ });
71
+ });
72
+ }, [data, search, searchKeys, columns]);
73
+
74
+ // Sort
75
+ const sortedData = React.useMemo(() => {
76
+ if (!sortKey) return filteredData;
77
+ return [...filteredData].sort((a, b) => {
78
+ const valA = a[sortKey];
79
+ const valB = b[sortKey];
80
+ if (valA === valB) return 0;
81
+ if (valA == null) return 1;
82
+ if (valB == null) return -1;
83
+
84
+ if (typeof valA === "number" && typeof valB === "number") {
85
+ return sortDirection === "asc" ? valA - valB : valB - valA;
86
+ }
87
+ const strA = String(valA).toLowerCase();
88
+ const strB = String(valB).toLowerCase();
89
+ return sortDirection === "asc"
90
+ ? strA.localeCompare(strB)
91
+ : strB.localeCompare(strA);
92
+ });
93
+ }, [filteredData, sortKey, sortDirection]);
94
+
95
+ // Pagination
96
+ const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
97
+ const paginatedData = React.useMemo(() => {
98
+ const start = (currentPage - 1) * pageSize;
99
+ return sortedData.slice(start, start + pageSize);
100
+ }, [sortedData, currentPage, pageSize]);
101
+
102
+ const handleSort = (colKey: string) => {
103
+ if (sortKey === colKey) {
104
+ if (sortDirection === "asc") {
105
+ setSortDirection("desc");
106
+ } else {
107
+ setSortKey(null);
108
+ setSortDirection("asc");
109
+ }
110
+ } else {
111
+ setSortKey(colKey);
112
+ setSortDirection("asc");
113
+ }
114
+ };
115
+
116
+ return (
117
+ <View className={cn("w-full gap-4", className)} {...props}>
118
+ {/* Search Input */}
119
+ {searchable && (
120
+ <Input
121
+ placeholder={searchPlaceholder}
122
+ value={search}
123
+ onChangeText={(val) => {
124
+ setSearch(val);
125
+ setCurrentPage(1);
126
+ }}
127
+ className="bg-card"
128
+ />
129
+ )}
130
+
131
+ {/* Loading Skeleton */}
132
+ {loading ? (
133
+ <View className="gap-3">
134
+ {[1, 2, 3].map((idx) => (
135
+ <Skeleton key={idx} className="h-20 w-full rounded-2xl" />
136
+ ))}
137
+ </View>
138
+ ) : paginatedData.length === 0 ? (
139
+ <Card className="items-center justify-center p-8">
140
+ <Text className="text-muted text-base">{emptyText}</Text>
141
+ </Card>
142
+ ) : mode === "card" ? (
143
+ /* Mobile Card Mode */
144
+ <View className="gap-3">
145
+ {paginatedData.map((item, idx) => (
146
+ <Pressable
147
+ key={keyExtractor(item, idx)}
148
+ disabled={!onRowPress}
149
+ onPress={() => onRowPress?.(item)}
150
+ className="active:opacity-80"
151
+ >
152
+ <Card className="p-4 gap-2">
153
+ {columns.map((col) => {
154
+ const rawVal = item[col.key as string];
155
+ const renderedVal = col.render ? col.render(rawVal, item) : String(rawVal ?? "—");
156
+
157
+ return (
158
+ <View
159
+ key={String(col.key)}
160
+ className="flex-row items-center justify-between py-1 border-b border-border/40 last:border-b-0"
161
+ >
162
+ <Text className="text-xs font-semibold text-muted uppercase tracking-wider">
163
+ {col.title}
164
+ </Text>
165
+ <View className="shrink">
166
+ {typeof renderedVal === "string" ? (
167
+ <Text className="text-sm font-medium text-foreground">
168
+ {renderedVal}
169
+ </Text>
170
+ ) : (
171
+ renderedVal
172
+ )}
173
+ </View>
174
+ </View>
175
+ );
176
+ })}
177
+ </Card>
178
+ </Pressable>
179
+ ))}
180
+ </View>
181
+ ) : (
182
+ /* Horizontal Table Mode */
183
+ <ScrollView horizontal showsHorizontalScrollIndicator={false}>
184
+ <View className="rounded-2xl border border-border bg-card overflow-hidden">
185
+ {/* Header */}
186
+ <View className="flex-row bg-muted/10 border-b border-border">
187
+ {columns.map((col) => (
188
+ <Pressable
189
+ key={String(col.key)}
190
+ disabled={!col.sortable}
191
+ onPress={() => handleSort(String(col.key))}
192
+ style={{ width: col.width || 120 }}
193
+ className="px-4 py-3 flex-row items-center justify-between"
194
+ >
195
+ <Text className="text-xs font-bold text-foreground uppercase tracking-wider">
196
+ {col.title}
197
+ </Text>
198
+ {col.sortable && (
199
+ <Text className="text-xs text-muted">
200
+ {sortKey === col.key
201
+ ? sortDirection === "asc"
202
+ ? "▲"
203
+ : "▼"
204
+ : "⇅"}
205
+ </Text>
206
+ )}
207
+ </Pressable>
208
+ ))}
209
+ </View>
210
+
211
+ {/* Rows */}
212
+ {paginatedData.map((item, idx) => (
213
+ <Pressable
214
+ key={keyExtractor(item, idx)}
215
+ disabled={!onRowPress}
216
+ onPress={() => onRowPress?.(item)}
217
+ className="flex-row border-b border-border/60 active:bg-muted/10"
218
+ >
219
+ {columns.map((col) => {
220
+ const rawVal = item[col.key as string];
221
+ const renderedVal = col.render ? col.render(rawVal, item) : String(rawVal ?? "—");
222
+
223
+ return (
224
+ <View
225
+ key={String(col.key)}
226
+ style={{ width: col.width || 120 }}
227
+ className="px-4 py-3 justify-center"
228
+ >
229
+ {typeof renderedVal === "string" ? (
230
+ <Text className="text-sm text-foreground">{renderedVal}</Text>
231
+ ) : (
232
+ renderedVal
233
+ )}
234
+ </View>
235
+ );
236
+ })}
237
+ </Pressable>
238
+ ))}
239
+ </View>
240
+ </ScrollView>
241
+ )}
242
+
243
+ {/* Pagination Footer */}
244
+ {totalPages > 1 && (
245
+ <View className="flex-row items-center justify-between pt-2">
246
+ <Text className="text-xs text-muted">
247
+ Showing {(currentPage - 1) * pageSize + 1} -{" "}
248
+ {Math.min(currentPage * pageSize, sortedData.length)} of{" "}
249
+ {sortedData.length}
250
+ </Text>
251
+
252
+ <View className="flex-row items-center gap-2">
253
+ <Button
254
+ variant="outline"
255
+ size="sm"
256
+ disabled={currentPage <= 1}
257
+ onPress={() => setCurrentPage((p) => Math.max(1, p - 1))}
258
+ >
259
+ Prev
260
+ </Button>
261
+ <Text className="text-xs font-medium text-foreground px-1">
262
+ {currentPage} / {totalPages}
263
+ </Text>
264
+ <Button
265
+ variant="outline"
266
+ size="sm"
267
+ disabled={currentPage >= totalPages}
268
+ onPress={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
269
+ >
270
+ Next
271
+ </Button>
272
+ </View>
273
+ </View>
274
+ )}
275
+ </View>
276
+ );
277
+ }
@@ -0,0 +1 @@
1
+ export * from "./data-table";
@@ -0,0 +1,278 @@
1
+ import * as React from "react";
2
+ import {
3
+ Modal,
4
+ View,
5
+ Text,
6
+ Pressable,
7
+ StyleSheet,
8
+ type ViewProps,
9
+ type TextProps,
10
+ type PressableProps,
11
+ type GestureResponderEvent,
12
+ } from "react-native";
13
+ import { cn } from "../../utilities/cn";
14
+
15
+ export interface DialogContextValue {
16
+ open: boolean;
17
+ onOpenChange: (open: boolean) => void;
18
+ }
19
+
20
+ export const DialogContext = React.createContext<DialogContextValue | null>(null);
21
+
22
+ export function useDialogContext(): DialogContextValue {
23
+ const context = React.useContext(DialogContext);
24
+ if (!context) {
25
+ throw new Error("Dialog compound components must be used within a Dialog component");
26
+ }
27
+ return context;
28
+ }
29
+
30
+ export interface DialogProps {
31
+ open?: boolean;
32
+ defaultOpen?: boolean;
33
+ onOpenChange?: (open: boolean) => void;
34
+ children: React.ReactNode;
35
+ }
36
+
37
+ export function Dialog({
38
+ open: controlledOpen,
39
+ defaultOpen = false,
40
+ onOpenChange,
41
+ children,
42
+ }: DialogProps) {
43
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
44
+ const isControlled = controlledOpen !== undefined;
45
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
46
+
47
+ const handleOpenChange = React.useCallback(
48
+ (nextOpen: boolean) => {
49
+ if (!isControlled) {
50
+ setUncontrolledOpen(nextOpen);
51
+ }
52
+ onOpenChange?.(nextOpen);
53
+ },
54
+ [isControlled, onOpenChange]
55
+ );
56
+
57
+ return (
58
+ <DialogContext.Provider value={{ open, onOpenChange: handleOpenChange }}>
59
+ {children}
60
+ </DialogContext.Provider>
61
+ );
62
+ }
63
+ Dialog.displayName = "Dialog";
64
+
65
+ export interface DialogTriggerProps extends PressableProps {
66
+ asChild?: boolean;
67
+ className?: string;
68
+ children: React.ReactNode;
69
+ }
70
+
71
+ export const DialogTrigger = React.forwardRef<View, DialogTriggerProps>(
72
+ function DialogTrigger({ asChild, children, onPress, ...props }, ref) {
73
+ const { open, onOpenChange } = useDialogContext();
74
+
75
+ const handlePress = (e: GestureResponderEvent) => {
76
+ onOpenChange(!open);
77
+ onPress?.(e);
78
+ };
79
+
80
+ if (asChild && React.isValidElement(children)) {
81
+ return React.cloneElement(children as React.ReactElement<any>, {
82
+ ref,
83
+ onPress: (e: GestureResponderEvent) => {
84
+ (children.props as any)?.onPress?.(e);
85
+ handlePress(e);
86
+ },
87
+ });
88
+ }
89
+
90
+ return (
91
+ <Pressable ref={ref} onPress={handlePress} role="button" {...props}>
92
+ {children}
93
+ </Pressable>
94
+ );
95
+ }
96
+ );
97
+ DialogTrigger.displayName = "DialogTrigger";
98
+
99
+ export interface DialogPortalProps {
100
+ children: React.ReactNode;
101
+ forceMount?: boolean;
102
+ }
103
+
104
+ export function DialogPortal({ children, forceMount }: DialogPortalProps) {
105
+ const { open, onOpenChange } = useDialogContext();
106
+
107
+ if (!open && !forceMount) {
108
+ return null;
109
+ }
110
+
111
+ return (
112
+ <Modal
113
+ transparent
114
+ visible={open}
115
+ animationType="fade"
116
+ onRequestClose={() => onOpenChange(false)}
117
+ statusBarTranslucent
118
+ >
119
+ {children}
120
+ </Modal>
121
+ );
122
+ }
123
+ DialogPortal.displayName = "DialogPortal";
124
+
125
+ export interface DialogOverlayProps extends ViewProps {
126
+ className?: string;
127
+ closeOnPress?: boolean;
128
+ }
129
+
130
+ export const DialogOverlay = React.forwardRef<View, DialogOverlayProps>(
131
+ function DialogOverlay({ className, closeOnPress = true, ...props }, ref) {
132
+ const { onOpenChange } = useDialogContext();
133
+
134
+ return (
135
+ <Pressable
136
+ ref={ref}
137
+ style={StyleSheet.absoluteFill}
138
+ className={cn("z-50 bg-black/60", className)}
139
+ onPress={() => {
140
+ if (closeOnPress) {
141
+ onOpenChange(false);
142
+ }
143
+ }}
144
+ {...props}
145
+ />
146
+ );
147
+ }
148
+ );
149
+ DialogOverlay.displayName = "DialogOverlay";
150
+
151
+ export interface DialogContentProps extends ViewProps {
152
+ className?: string;
153
+ overlayClassName?: string;
154
+ children: React.ReactNode;
155
+ }
156
+
157
+ export const DialogContent = React.forwardRef<View, DialogContentProps>(
158
+ function DialogContent({ className, overlayClassName, children, ...props }, ref) {
159
+ return (
160
+ <DialogPortal>
161
+ <View
162
+ pointerEvents="box-none"
163
+ style={StyleSheet.absoluteFill}
164
+ className="z-50 items-center justify-center p-4"
165
+ >
166
+ <DialogOverlay className={overlayClassName} />
167
+ <View
168
+ ref={ref}
169
+ role="dialog"
170
+ aria-modal={true}
171
+ className={cn(
172
+ "z-50 w-full max-w-lg rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-2xl",
173
+ className
174
+ )}
175
+ {...props}
176
+ >
177
+ {children}
178
+ </View>
179
+ </View>
180
+ </DialogPortal>
181
+ );
182
+ }
183
+ );
184
+ DialogContent.displayName = "DialogContent";
185
+
186
+ export function DialogHeader({
187
+ className,
188
+ ...props
189
+ }: ViewProps & { className?: string }) {
190
+ return (
191
+ <View
192
+ className={cn("flex-col space-y-2 text-center sm:text-left", className)}
193
+ {...props}
194
+ />
195
+ );
196
+ }
197
+ DialogHeader.displayName = "DialogHeader";
198
+
199
+ export const DialogTitle = React.forwardRef<Text, TextProps & { className?: string }>(
200
+ function DialogTitle({ className, ...props }, ref) {
201
+ return (
202
+ <Text
203
+ ref={ref}
204
+ role="heading"
205
+ aria-level={2}
206
+ className={cn(
207
+ "text-xl font-bold tracking-tight text-neutral-900 dark:text-neutral-50",
208
+ className
209
+ )}
210
+ {...props}
211
+ />
212
+ );
213
+ }
214
+ );
215
+ DialogTitle.displayName = "DialogTitle";
216
+
217
+ export const DialogDescription = React.forwardRef<Text, TextProps & { className?: string }>(
218
+ function DialogDescription({ className, ...props }, ref) {
219
+ return (
220
+ <Text
221
+ ref={ref}
222
+ className={cn(
223
+ "text-sm text-neutral-500 dark:text-neutral-400 mt-1",
224
+ className
225
+ )}
226
+ {...props}
227
+ />
228
+ );
229
+ }
230
+ );
231
+ DialogDescription.displayName = "DialogDescription";
232
+
233
+ export function DialogFooter({
234
+ className,
235
+ ...props
236
+ }: ViewProps & { className?: string }) {
237
+ return (
238
+ <View
239
+ className={cn("flex-row justify-end space-x-2 mt-6", className)}
240
+ {...props}
241
+ />
242
+ );
243
+ }
244
+ DialogFooter.displayName = "DialogFooter";
245
+
246
+ export interface DialogCloseProps extends PressableProps {
247
+ asChild?: boolean;
248
+ className?: string;
249
+ children: React.ReactNode;
250
+ }
251
+
252
+ export const DialogClose = React.forwardRef<View, DialogCloseProps>(
253
+ function DialogClose({ asChild, children, onPress, ...props }, ref) {
254
+ const { onOpenChange } = useDialogContext();
255
+
256
+ const handlePress = (e: GestureResponderEvent) => {
257
+ onOpenChange(false);
258
+ onPress?.(e);
259
+ };
260
+
261
+ if (asChild && React.isValidElement(children)) {
262
+ return React.cloneElement(children as React.ReactElement<any>, {
263
+ ref,
264
+ onPress: (e: GestureResponderEvent) => {
265
+ (children.props as any)?.onPress?.(e);
266
+ handlePress(e);
267
+ },
268
+ });
269
+ }
270
+
271
+ return (
272
+ <Pressable ref={ref} onPress={handlePress} role="button" {...props}>
273
+ {children}
274
+ </Pressable>
275
+ );
276
+ }
277
+ );
278
+ DialogClose.displayName = "DialogClose";
@@ -0,0 +1 @@
1
+ export * from "./dialog";
@@ -0,0 +1 @@
1
+ export * from "./input";