@beyondcorp/beyond-ui-native 0.1.2 → 0.1.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.
Files changed (71) hide show
  1. package/dist/index.js +78 -61
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +78 -61
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +11 -5
  6. package/src/components/accordion/accordion.tsx +245 -0
  7. package/src/components/accordion/index.ts +1 -0
  8. package/src/components/alert/alert.tsx +118 -0
  9. package/src/components/alert/index.ts +1 -0
  10. package/src/components/avatar/avatar.tsx +125 -0
  11. package/src/components/avatar/index.ts +1 -0
  12. package/src/components/badge/badge.tsx +89 -0
  13. package/src/components/badge/index.ts +1 -0
  14. package/src/components/bottom-sheet/bottom-sheet.tsx +336 -0
  15. package/src/components/bottom-sheet/index.ts +1 -0
  16. package/src/components/button/button.tsx +172 -0
  17. package/src/components/button/index.ts +1 -0
  18. package/src/components/card/card.tsx +121 -0
  19. package/src/components/card/index.ts +1 -0
  20. package/src/components/checkbox/checkbox.tsx +100 -0
  21. package/src/components/checkbox/index.ts +1 -0
  22. package/src/components/data-table/data-table.tsx +276 -0
  23. package/src/components/data-table/index.ts +1 -0
  24. package/src/components/dialog/dialog.tsx +278 -0
  25. package/src/components/dialog/index.ts +1 -0
  26. package/src/components/input/index.ts +1 -0
  27. package/src/components/input/input.tsx +130 -0
  28. package/src/components/radio/index.ts +1 -0
  29. package/src/components/radio/radio.tsx +117 -0
  30. package/src/components/select/index.ts +1 -0
  31. package/src/components/select/select.tsx +377 -0
  32. package/src/components/separator/index.ts +1 -0
  33. package/src/components/separator/separator.tsx +28 -0
  34. package/src/components/skeleton/index.ts +1 -0
  35. package/src/components/skeleton/skeleton.tsx +43 -0
  36. package/src/components/spinner/index.ts +1 -0
  37. package/src/components/spinner/spinner.tsx +46 -0
  38. package/src/components/stats-card/index.ts +1 -0
  39. package/src/components/stats-card/stats-card.tsx +92 -0
  40. package/src/components/switch/index.ts +1 -0
  41. package/src/components/switch/switch.tsx +80 -0
  42. package/src/components/tabs/index.ts +1 -0
  43. package/src/components/tabs/tabs.tsx +172 -0
  44. package/src/components/text/heading.tsx +50 -0
  45. package/src/components/text/index.ts +2 -0
  46. package/src/components/text/text.tsx +62 -0
  47. package/src/components/textarea/index.ts +1 -0
  48. package/src/components/textarea/textarea.tsx +87 -0
  49. package/src/components/toast/index.ts +2 -0
  50. package/src/components/toast/toast.tsx +136 -0
  51. package/src/components/toast/types.ts +32 -0
  52. package/src/index.ts +42 -0
  53. package/src/primitives/box.tsx +25 -0
  54. package/src/primitives/index.ts +2 -0
  55. package/src/primitives/pressable.tsx +49 -0
  56. package/src/providers/BeyondUIProvider.tsx +119 -0
  57. package/src/templates/auth/ForgotPasswordTemplate.tsx +132 -0
  58. package/src/templates/auth/SignInTemplate.tsx +213 -0
  59. package/src/templates/auth/SignUpTemplate.tsx +233 -0
  60. package/src/templates/auth/index.ts +3 -0
  61. package/src/templates/dashboard/DashboardTemplate.tsx +204 -0
  62. package/src/templates/dashboard/index.ts +1 -0
  63. package/src/templates/index.ts +3 -0
  64. package/src/templates/settings/SettingsTemplate.tsx +226 -0
  65. package/src/templates/settings/index.ts +1 -0
  66. package/src/theme/preset.ts +45 -0
  67. package/src/theme/tokens.ts +144 -0
  68. package/src/types/nativewind-env.d.ts +26 -0
  69. package/src/utilities/accessibility.ts +43 -0
  70. package/src/utilities/cn.ts +9 -0
  71. 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,276 @@
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
+ />
128
+ )}
129
+
130
+ {/* Loading Skeleton */}
131
+ {loading ? (
132
+ <View className="gap-3">
133
+ {[1, 2, 3].map((idx) => (
134
+ <Skeleton key={idx} className="h-20 w-full rounded-2xl" />
135
+ ))}
136
+ </View>
137
+ ) : paginatedData.length === 0 ? (
138
+ <Card className="items-center justify-center p-8">
139
+ <Text className="text-neutral-500 dark:text-neutral-400 text-base">{emptyText}</Text>
140
+ </Card>
141
+ ) : mode === "card" ? (
142
+ /* Mobile Card Mode */
143
+ <View className="gap-3">
144
+ {paginatedData.map((item, idx) => (
145
+ <Pressable
146
+ key={keyExtractor(item, idx)}
147
+ disabled={!onRowPress}
148
+ onPress={() => onRowPress?.(item)}
149
+ className="active:opacity-80"
150
+ >
151
+ <Card className="p-4 gap-2">
152
+ {columns.map((col) => {
153
+ const rawVal = item[col.key as string];
154
+ const renderedVal = col.render ? col.render(rawVal, item) : String(rawVal ?? "—");
155
+
156
+ return (
157
+ <View
158
+ key={String(col.key)}
159
+ className="flex-row items-center justify-between py-1 border-b border-neutral-200 dark:border-neutral-800 last:border-b-0"
160
+ >
161
+ <Text className="text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">
162
+ {col.title}
163
+ </Text>
164
+ <View className="shrink">
165
+ {typeof renderedVal === "string" ? (
166
+ <Text className="text-sm font-medium text-neutral-900 dark:text-neutral-100">
167
+ {renderedVal}
168
+ </Text>
169
+ ) : (
170
+ renderedVal
171
+ )}
172
+ </View>
173
+ </View>
174
+ );
175
+ })}
176
+ </Card>
177
+ </Pressable>
178
+ ))}
179
+ </View>
180
+ ) : (
181
+ /* Horizontal Table Mode */
182
+ <ScrollView horizontal showsHorizontalScrollIndicator={false}>
183
+ <View className="rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 overflow-hidden">
184
+ {/* Header */}
185
+ <View className="flex-row bg-neutral-100 dark:bg-neutral-800/60 border-b border-neutral-200 dark:border-neutral-800">
186
+ {columns.map((col) => (
187
+ <Pressable
188
+ key={String(col.key)}
189
+ disabled={!col.sortable}
190
+ onPress={() => handleSort(String(col.key))}
191
+ style={{ width: col.width || 120 }}
192
+ className="px-4 py-3 flex-row items-center justify-between"
193
+ >
194
+ <Text className="text-xs font-bold text-neutral-900 dark:text-neutral-100 uppercase tracking-wider">
195
+ {col.title}
196
+ </Text>
197
+ {col.sortable && (
198
+ <Text className="text-xs text-neutral-500 dark:text-neutral-400">
199
+ {sortKey === col.key
200
+ ? sortDirection === "asc"
201
+ ? "▲"
202
+ : "▼"
203
+ : "⇅"}
204
+ </Text>
205
+ )}
206
+ </Pressable>
207
+ ))}
208
+ </View>
209
+
210
+ {/* Rows */}
211
+ {paginatedData.map((item, idx) => (
212
+ <Pressable
213
+ key={keyExtractor(item, idx)}
214
+ disabled={!onRowPress}
215
+ onPress={() => onRowPress?.(item)}
216
+ className="flex-row border-b border-neutral-200 dark:border-neutral-800 active:bg-neutral-100 dark:active:bg-neutral-800"
217
+ >
218
+ {columns.map((col) => {
219
+ const rawVal = item[col.key as string];
220
+ const renderedVal = col.render ? col.render(rawVal, item) : String(rawVal ?? "—");
221
+
222
+ return (
223
+ <View
224
+ key={String(col.key)}
225
+ style={{ width: col.width || 120 }}
226
+ className="px-4 py-3 justify-center"
227
+ >
228
+ {typeof renderedVal === "string" ? (
229
+ <Text className="text-sm text-neutral-900 dark:text-neutral-100">{renderedVal}</Text>
230
+ ) : (
231
+ renderedVal
232
+ )}
233
+ </View>
234
+ );
235
+ })}
236
+ </Pressable>
237
+ ))}
238
+ </View>
239
+ </ScrollView>
240
+ )}
241
+
242
+ {/* Pagination Footer */}
243
+ {totalPages > 1 && (
244
+ <View className="flex-row items-center justify-between pt-2">
245
+ <Text className="text-xs text-neutral-500 dark:text-neutral-400">
246
+ Showing {(currentPage - 1) * pageSize + 1} -{" "}
247
+ {Math.min(currentPage * pageSize, sortedData.length)} of{" "}
248
+ {sortedData.length}
249
+ </Text>
250
+
251
+ <View className="flex-row items-center gap-2">
252
+ <Button
253
+ variant="outline"
254
+ size="sm"
255
+ disabled={currentPage <= 1}
256
+ onPress={() => setCurrentPage((p) => Math.max(1, p - 1))}
257
+ >
258
+ Prev
259
+ </Button>
260
+ <Text className="text-xs font-medium text-neutral-900 dark:text-neutral-100 px-1">
261
+ {currentPage} / {totalPages}
262
+ </Text>
263
+ <Button
264
+ variant="outline"
265
+ size="sm"
266
+ disabled={currentPage >= totalPages}
267
+ onPress={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
268
+ >
269
+ Next
270
+ </Button>
271
+ </View>
272
+ </View>
273
+ )}
274
+ </View>
275
+ );
276
+ }
@@ -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";