@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.
- package/package.json +11 -5
- package/src/components/accordion/accordion.tsx +245 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.tsx +118 -0
- package/src/components/alert/index.ts +1 -0
- package/src/components/avatar/avatar.tsx +125 -0
- package/src/components/avatar/index.ts +1 -0
- package/src/components/badge/badge.tsx +89 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +336 -0
- package/src/components/bottom-sheet/index.ts +1 -0
- package/src/components/button/button.tsx +172 -0
- package/src/components/button/index.ts +1 -0
- package/src/components/card/card.tsx +121 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/checkbox/checkbox.tsx +100 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/data-table/data-table.tsx +277 -0
- package/src/components/data-table/index.ts +1 -0
- package/src/components/dialog/dialog.tsx +278 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.tsx +130 -0
- package/src/components/radio/index.ts +1 -0
- package/src/components/radio/radio.tsx +117 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.tsx +375 -0
- package/src/components/separator/index.ts +1 -0
- package/src/components/separator/separator.tsx +28 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.tsx +43 -0
- package/src/components/spinner/index.ts +1 -0
- package/src/components/spinner/spinner.tsx +46 -0
- package/src/components/stats-card/index.ts +1 -0
- package/src/components/stats-card/stats-card.tsx +92 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.tsx +80 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.tsx +172 -0
- package/src/components/text/heading.tsx +50 -0
- package/src/components/text/index.ts +2 -0
- package/src/components/text/text.tsx +62 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.tsx +87 -0
- package/src/components/toast/index.ts +2 -0
- package/src/components/toast/toast.tsx +136 -0
- package/src/components/toast/types.ts +32 -0
- package/src/index.ts +42 -0
- package/src/primitives/box.tsx +25 -0
- package/src/primitives/index.ts +2 -0
- package/src/primitives/pressable.tsx +49 -0
- package/src/providers/BeyondUIProvider.tsx +100 -0
- package/src/templates/auth/ForgotPasswordTemplate.tsx +132 -0
- package/src/templates/auth/SignInTemplate.tsx +213 -0
- package/src/templates/auth/SignUpTemplate.tsx +233 -0
- package/src/templates/auth/index.ts +3 -0
- package/src/templates/dashboard/DashboardTemplate.tsx +205 -0
- package/src/templates/dashboard/index.ts +1 -0
- package/src/templates/index.ts +3 -0
- package/src/templates/settings/SettingsTemplate.tsx +226 -0
- package/src/templates/settings/index.ts +1 -0
- package/src/theme/preset.ts +45 -0
- package/src/theme/tokens.ts +144 -0
- package/src/types/nativewind-env.d.ts +26 -0
- package/src/utilities/accessibility.ts +43 -0
- package/src/utilities/cn.ts +9 -0
- package/src/utilities/index.ts +2 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
View,
|
|
5
|
+
Pressable,
|
|
6
|
+
Animated,
|
|
7
|
+
PanResponder,
|
|
8
|
+
Dimensions,
|
|
9
|
+
type ViewProps,
|
|
10
|
+
type PressableProps,
|
|
11
|
+
} from "react-native";
|
|
12
|
+
import { cn } from "../../utilities/cn";
|
|
13
|
+
import { Text } from "../text/text";
|
|
14
|
+
import { Heading } from "../text/heading";
|
|
15
|
+
|
|
16
|
+
interface BottomSheetContextValue {
|
|
17
|
+
open: boolean;
|
|
18
|
+
setOpen: (open: boolean) => void;
|
|
19
|
+
closeSheet: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const BottomSheetContext = React.createContext<BottomSheetContextValue | null>(null);
|
|
23
|
+
|
|
24
|
+
function useBottomSheetContext() {
|
|
25
|
+
const context = React.useContext(BottomSheetContext);
|
|
26
|
+
if (!context) {
|
|
27
|
+
throw new Error("BottomSheet components must be used within a BottomSheet");
|
|
28
|
+
}
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BottomSheetProps {
|
|
33
|
+
open?: boolean;
|
|
34
|
+
defaultOpen?: boolean;
|
|
35
|
+
onOpenChange?: (open: boolean) => void;
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const BottomSheet: React.FC<BottomSheetProps> = ({
|
|
40
|
+
open: controlledOpen,
|
|
41
|
+
defaultOpen = false,
|
|
42
|
+
onOpenChange,
|
|
43
|
+
children,
|
|
44
|
+
}) => {
|
|
45
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
|
46
|
+
const isControlled = controlledOpen !== undefined;
|
|
47
|
+
const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
|
|
48
|
+
|
|
49
|
+
const setOpen = React.useCallback(
|
|
50
|
+
(nextOpen: boolean) => {
|
|
51
|
+
if (!isControlled) {
|
|
52
|
+
setUncontrolledOpen(nextOpen);
|
|
53
|
+
}
|
|
54
|
+
onOpenChange?.(nextOpen);
|
|
55
|
+
},
|
|
56
|
+
[isControlled, onOpenChange]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const closeSheet = React.useCallback(() => {
|
|
60
|
+
setOpen(false);
|
|
61
|
+
}, [setOpen]);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<BottomSheetContext.Provider value={{ open: isOpen, setOpen, closeSheet }}>
|
|
65
|
+
{children}
|
|
66
|
+
</BottomSheetContext.Provider>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// --- BottomSheetTrigger ---
|
|
71
|
+
|
|
72
|
+
export interface BottomSheetTriggerProps extends PressableProps {
|
|
73
|
+
asChild?: boolean;
|
|
74
|
+
className?: string;
|
|
75
|
+
children: React.ReactNode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const BottomSheetTrigger = React.forwardRef<View, BottomSheetTriggerProps>(
|
|
79
|
+
({ asChild, children, onPress, ...props }, ref) => {
|
|
80
|
+
const { setOpen } = useBottomSheetContext();
|
|
81
|
+
|
|
82
|
+
const handlePress = (e: any) => {
|
|
83
|
+
setOpen(true);
|
|
84
|
+
onPress?.(e);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (asChild && React.isValidElement(children)) {
|
|
88
|
+
return React.cloneElement(children as React.ReactElement<any>, {
|
|
89
|
+
ref,
|
|
90
|
+
onPress: handlePress,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Pressable ref={ref} onPress={handlePress} {...props}>
|
|
96
|
+
{children}
|
|
97
|
+
</Pressable>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
BottomSheetTrigger.displayName = "BottomSheetTrigger";
|
|
102
|
+
|
|
103
|
+
// --- BottomSheetContent ---
|
|
104
|
+
|
|
105
|
+
export interface BottomSheetContentProps extends ViewProps {
|
|
106
|
+
className?: string;
|
|
107
|
+
children: React.ReactNode;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const BottomSheetContent = React.forwardRef<View, BottomSheetContentProps>(
|
|
111
|
+
({ className, children, ...props }, ref) => {
|
|
112
|
+
const { open, closeSheet } = useBottomSheetContext();
|
|
113
|
+
const screenHeight = Dimensions.get("window").height;
|
|
114
|
+
|
|
115
|
+
const translateY = React.useRef(new Animated.Value(screenHeight)).current;
|
|
116
|
+
const backdropOpacity = React.useRef(new Animated.Value(0)).current;
|
|
117
|
+
|
|
118
|
+
React.useEffect(() => {
|
|
119
|
+
if (open) {
|
|
120
|
+
Animated.parallel([
|
|
121
|
+
Animated.spring(translateY, {
|
|
122
|
+
toValue: 0,
|
|
123
|
+
useNativeDriver: true,
|
|
124
|
+
bounciness: 4,
|
|
125
|
+
}),
|
|
126
|
+
Animated.timing(backdropOpacity, {
|
|
127
|
+
toValue: 1,
|
|
128
|
+
duration: 250,
|
|
129
|
+
useNativeDriver: true,
|
|
130
|
+
}),
|
|
131
|
+
]).start();
|
|
132
|
+
} else {
|
|
133
|
+
Animated.parallel([
|
|
134
|
+
Animated.timing(translateY, {
|
|
135
|
+
toValue: screenHeight,
|
|
136
|
+
duration: 200,
|
|
137
|
+
useNativeDriver: true,
|
|
138
|
+
}),
|
|
139
|
+
Animated.timing(backdropOpacity, {
|
|
140
|
+
toValue: 0,
|
|
141
|
+
duration: 200,
|
|
142
|
+
useNativeDriver: true,
|
|
143
|
+
}),
|
|
144
|
+
]).start();
|
|
145
|
+
}
|
|
146
|
+
}, [open, translateY, backdropOpacity, screenHeight]);
|
|
147
|
+
|
|
148
|
+
// PanResponder for drag down to dismiss
|
|
149
|
+
const panResponder = React.useRef(
|
|
150
|
+
PanResponder.create({
|
|
151
|
+
onStartShouldSetPanResponder: () => true,
|
|
152
|
+
onMoveShouldSetPanResponder: (_, gestureState) => gestureState.dy > 5,
|
|
153
|
+
onPanResponderMove: (_, gestureState) => {
|
|
154
|
+
if (gestureState.dy > 0) {
|
|
155
|
+
translateY.setValue(gestureState.dy);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
onPanResponderRelease: (_, gestureState) => {
|
|
159
|
+
if (gestureState.dy > 120 || gestureState.vy > 0.8) {
|
|
160
|
+
closeSheet();
|
|
161
|
+
} else {
|
|
162
|
+
Animated.spring(translateY, {
|
|
163
|
+
toValue: 0,
|
|
164
|
+
useNativeDriver: true,
|
|
165
|
+
bounciness: 4,
|
|
166
|
+
}).start();
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
).current;
|
|
171
|
+
|
|
172
|
+
if (!open) return null;
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<Modal
|
|
176
|
+
transparent
|
|
177
|
+
visible={open}
|
|
178
|
+
animationType="none"
|
|
179
|
+
onRequestClose={closeSheet}
|
|
180
|
+
statusBarTranslucent
|
|
181
|
+
>
|
|
182
|
+
<View className="flex-1 justify-end">
|
|
183
|
+
{/* Backdrop */}
|
|
184
|
+
<Animated.View
|
|
185
|
+
style={{ opacity: backdropOpacity }}
|
|
186
|
+
className="absolute inset-0 bg-black/60"
|
|
187
|
+
>
|
|
188
|
+
<Pressable className="flex-1" onPress={closeSheet} />
|
|
189
|
+
</Animated.View>
|
|
190
|
+
|
|
191
|
+
{/* Sheet Container */}
|
|
192
|
+
<Animated.View
|
|
193
|
+
ref={ref}
|
|
194
|
+
style={{ transform: [{ translateY }] }}
|
|
195
|
+
className={cn(
|
|
196
|
+
"w-full bg-card rounded-t-3xl border-t border-border shadow-2xl pt-2 pb-8 px-6 max-h-[85%]",
|
|
197
|
+
className
|
|
198
|
+
)}
|
|
199
|
+
{...props}
|
|
200
|
+
>
|
|
201
|
+
{/* Drag Handle Bar */}
|
|
202
|
+
<View {...panResponder.panHandlers} className="w-full py-2 items-center">
|
|
203
|
+
<View className="w-12 h-1.5 rounded-full bg-muted/40" />
|
|
204
|
+
</View>
|
|
205
|
+
|
|
206
|
+
{children}
|
|
207
|
+
</Animated.View>
|
|
208
|
+
</View>
|
|
209
|
+
</Modal>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
BottomSheetContent.displayName = "BottomSheetContent";
|
|
214
|
+
|
|
215
|
+
// --- BottomSheetHeader ---
|
|
216
|
+
|
|
217
|
+
export interface BottomSheetHeaderProps extends ViewProps {
|
|
218
|
+
className?: string;
|
|
219
|
+
children: React.ReactNode;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const BottomSheetHeader = React.forwardRef<View, BottomSheetHeaderProps>(
|
|
223
|
+
({ className, children, ...props }, ref) => (
|
|
224
|
+
<View ref={ref} className={cn("mb-4 gap-1", className)} {...props}>
|
|
225
|
+
{children}
|
|
226
|
+
</View>
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
BottomSheetHeader.displayName = "BottomSheetHeader";
|
|
230
|
+
|
|
231
|
+
// --- BottomSheetTitle ---
|
|
232
|
+
|
|
233
|
+
export interface BottomSheetTitleProps {
|
|
234
|
+
className?: string;
|
|
235
|
+
children: React.ReactNode;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const BottomSheetTitle: React.FC<BottomSheetTitleProps> = ({
|
|
239
|
+
className,
|
|
240
|
+
children,
|
|
241
|
+
}) => {
|
|
242
|
+
return (
|
|
243
|
+
<Heading level={4} className={cn("text-xl font-bold text-foreground", className)}>
|
|
244
|
+
{children}
|
|
245
|
+
</Heading>
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
BottomSheetTitle.displayName = "BottomSheetTitle";
|
|
249
|
+
|
|
250
|
+
// --- BottomSheetDescription ---
|
|
251
|
+
|
|
252
|
+
export interface BottomSheetDescriptionProps {
|
|
253
|
+
className?: string;
|
|
254
|
+
children: React.ReactNode;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const BottomSheetDescription: React.FC<BottomSheetDescriptionProps> = ({
|
|
258
|
+
className,
|
|
259
|
+
children,
|
|
260
|
+
}) => {
|
|
261
|
+
return (
|
|
262
|
+
<Text className={cn("text-sm text-muted", className)}>
|
|
263
|
+
{children}
|
|
264
|
+
</Text>
|
|
265
|
+
);
|
|
266
|
+
};
|
|
267
|
+
BottomSheetDescription.displayName = "BottomSheetDescription";
|
|
268
|
+
|
|
269
|
+
// --- BottomSheetBody ---
|
|
270
|
+
|
|
271
|
+
export interface BottomSheetBodyProps extends ViewProps {
|
|
272
|
+
className?: string;
|
|
273
|
+
children: React.ReactNode;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export const BottomSheetBody = React.forwardRef<View, BottomSheetBodyProps>(
|
|
277
|
+
({ className, children, ...props }, ref) => (
|
|
278
|
+
<View ref={ref} className={cn("py-2", className)} {...props}>
|
|
279
|
+
{children}
|
|
280
|
+
</View>
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
BottomSheetBody.displayName = "BottomSheetBody";
|
|
284
|
+
|
|
285
|
+
// --- BottomSheetFooter ---
|
|
286
|
+
|
|
287
|
+
export interface BottomSheetFooterProps extends ViewProps {
|
|
288
|
+
className?: string;
|
|
289
|
+
children: React.ReactNode;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export const BottomSheetFooter = React.forwardRef<View, BottomSheetFooterProps>(
|
|
293
|
+
({ className, children, ...props }, ref) => (
|
|
294
|
+
<View
|
|
295
|
+
ref={ref}
|
|
296
|
+
className={cn("mt-6 flex-row items-center justify-end gap-3", className)}
|
|
297
|
+
{...props}
|
|
298
|
+
>
|
|
299
|
+
{children}
|
|
300
|
+
</View>
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
BottomSheetFooter.displayName = "BottomSheetFooter";
|
|
304
|
+
|
|
305
|
+
// --- BottomSheetClose ---
|
|
306
|
+
|
|
307
|
+
export interface BottomSheetCloseProps extends PressableProps {
|
|
308
|
+
asChild?: boolean;
|
|
309
|
+
className?: string;
|
|
310
|
+
children: React.ReactNode;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export const BottomSheetClose = React.forwardRef<View, BottomSheetCloseProps>(
|
|
314
|
+
({ asChild, children, onPress, ...props }, ref) => {
|
|
315
|
+
const { closeSheet } = useBottomSheetContext();
|
|
316
|
+
|
|
317
|
+
const handlePress = (e: any) => {
|
|
318
|
+
closeSheet();
|
|
319
|
+
onPress?.(e);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
if (asChild && React.isValidElement(children)) {
|
|
323
|
+
return React.cloneElement(children as React.ReactElement<any>, {
|
|
324
|
+
ref,
|
|
325
|
+
onPress: handlePress,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<Pressable ref={ref} onPress={handlePress} {...props}>
|
|
331
|
+
{children}
|
|
332
|
+
</Pressable>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
BottomSheetClose.displayName = "BottomSheetClose";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./bottom-sheet";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Pressable,
|
|
5
|
+
Text,
|
|
6
|
+
View,
|
|
7
|
+
type PressableProps,
|
|
8
|
+
type TextProps,
|
|
9
|
+
} from "react-native";
|
|
10
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
11
|
+
import { cn } from "../../utilities/cn";
|
|
12
|
+
|
|
13
|
+
export const buttonVariants = cva(
|
|
14
|
+
"flex-row items-center justify-center rounded-xl transition-colors",
|
|
15
|
+
{
|
|
16
|
+
variants: {
|
|
17
|
+
variant: {
|
|
18
|
+
primary: "bg-primary-600 active:bg-primary-700 dark:bg-primary-600 dark:active:bg-primary-700",
|
|
19
|
+
secondary: "bg-secondary-600 active:bg-secondary-700 dark:bg-secondary-600 dark:active:bg-secondary-700",
|
|
20
|
+
danger: "bg-danger-600 active:bg-danger-700 dark:bg-danger-600 dark:active:bg-danger-700",
|
|
21
|
+
destructive: "bg-danger-600 active:bg-danger-700 dark:bg-danger-600 dark:active:bg-danger-700",
|
|
22
|
+
success: "bg-success-600 active:bg-success-700 dark:bg-success-600 dark:active:bg-success-700",
|
|
23
|
+
warning: "bg-warning-600 active:bg-warning-700 dark:bg-warning-600 dark:active:bg-warning-700",
|
|
24
|
+
outline: "border border-neutral-300 dark:border-neutral-700 bg-transparent active:bg-neutral-100 dark:active:bg-neutral-800",
|
|
25
|
+
ghost: "bg-transparent active:bg-neutral-100 dark:active:bg-neutral-800",
|
|
26
|
+
link: "bg-transparent underline",
|
|
27
|
+
},
|
|
28
|
+
size: {
|
|
29
|
+
sm: "min-h-8 px-3 py-1.5 rounded-lg",
|
|
30
|
+
md: "min-h-10 px-4 py-2 rounded-xl",
|
|
31
|
+
lg: "min-h-12 px-6 py-2.5 rounded-xl",
|
|
32
|
+
xl: "min-h-14 px-8 py-3 rounded-2xl",
|
|
33
|
+
icon: "min-h-10 min-w-10 p-0 rounded-xl",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
variant: "primary",
|
|
38
|
+
size: "md",
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
export const buttonTextVariants = cva("font-semibold text-center select-none", {
|
|
44
|
+
variants: {
|
|
45
|
+
variant: {
|
|
46
|
+
primary: "text-white",
|
|
47
|
+
secondary: "text-white",
|
|
48
|
+
danger: "text-white",
|
|
49
|
+
destructive: "text-white",
|
|
50
|
+
success: "text-white",
|
|
51
|
+
warning: "text-white",
|
|
52
|
+
outline: "text-neutral-800 dark:text-neutral-100",
|
|
53
|
+
ghost: "text-neutral-800 dark:text-neutral-100",
|
|
54
|
+
link: "text-primary-600 dark:text-primary-400 underline",
|
|
55
|
+
},
|
|
56
|
+
size: {
|
|
57
|
+
sm: "text-xs",
|
|
58
|
+
md: "text-sm",
|
|
59
|
+
lg: "text-base font-semibold",
|
|
60
|
+
xl: "text-lg font-bold",
|
|
61
|
+
icon: "text-sm",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
defaultVariants: {
|
|
65
|
+
variant: "primary",
|
|
66
|
+
size: "md",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
type ButtonVariantProps = VariantProps<typeof buttonVariants>;
|
|
71
|
+
|
|
72
|
+
export interface ButtonProps
|
|
73
|
+
extends Omit<PressableProps, "children">,
|
|
74
|
+
ButtonVariantProps {
|
|
75
|
+
children?: React.ReactNode;
|
|
76
|
+
loading?: boolean;
|
|
77
|
+
disabled?: boolean;
|
|
78
|
+
leftIcon?: React.ReactNode;
|
|
79
|
+
rightIcon?: React.ReactNode;
|
|
80
|
+
className?: string;
|
|
81
|
+
textClassName?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ButtonContextValue {
|
|
85
|
+
variant: ButtonVariantProps["variant"];
|
|
86
|
+
size: ButtonVariantProps["size"];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const ButtonContext = React.createContext<ButtonContextValue>({
|
|
90
|
+
variant: "primary",
|
|
91
|
+
size: "md",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const Button = React.forwardRef<View, ButtonProps>(function Button(
|
|
95
|
+
{
|
|
96
|
+
children,
|
|
97
|
+
variant = "primary",
|
|
98
|
+
size = "md",
|
|
99
|
+
loading = false,
|
|
100
|
+
disabled = false,
|
|
101
|
+
leftIcon,
|
|
102
|
+
rightIcon,
|
|
103
|
+
className,
|
|
104
|
+
textClassName,
|
|
105
|
+
...props
|
|
106
|
+
},
|
|
107
|
+
ref
|
|
108
|
+
) {
|
|
109
|
+
const isDisabled = disabled || loading;
|
|
110
|
+
const isLightText = variant === "outline" || variant === "ghost" || variant === "link";
|
|
111
|
+
const spinnerColor = isLightText ? "#2563eb" : "#ffffff";
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<ButtonContext.Provider value={{ variant, size }}>
|
|
115
|
+
<Pressable
|
|
116
|
+
ref={ref}
|
|
117
|
+
accessibilityRole="button"
|
|
118
|
+
accessibilityState={{ disabled: !!isDisabled, busy: loading }}
|
|
119
|
+
disabled={isDisabled}
|
|
120
|
+
className={cn(
|
|
121
|
+
buttonVariants({ variant, size }),
|
|
122
|
+
isDisabled && "opacity-50",
|
|
123
|
+
className
|
|
124
|
+
)}
|
|
125
|
+
{...props}
|
|
126
|
+
>
|
|
127
|
+
{loading ? (
|
|
128
|
+
<ActivityIndicator color={spinnerColor} className="mr-2" size="small" />
|
|
129
|
+
) : leftIcon ? (
|
|
130
|
+
<View className="mr-2">{leftIcon}</View>
|
|
131
|
+
) : null}
|
|
132
|
+
|
|
133
|
+
{typeof children === "string" || typeof children === "number" ? (
|
|
134
|
+
<Text
|
|
135
|
+
className={cn(
|
|
136
|
+
buttonTextVariants({ variant, size }),
|
|
137
|
+
textClassName
|
|
138
|
+
)}
|
|
139
|
+
>
|
|
140
|
+
{children}
|
|
141
|
+
</Text>
|
|
142
|
+
) : (
|
|
143
|
+
children
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{rightIcon && !loading ? (
|
|
147
|
+
<View className="ml-2">{rightIcon}</View>
|
|
148
|
+
) : null}
|
|
149
|
+
</Pressable>
|
|
150
|
+
</ButtonContext.Provider>
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
Button.displayName = "Button";
|
|
155
|
+
|
|
156
|
+
export function ButtonText({
|
|
157
|
+
children,
|
|
158
|
+
className,
|
|
159
|
+
...props
|
|
160
|
+
}: TextProps & { className?: string }) {
|
|
161
|
+
const { variant, size } = React.useContext(ButtonContext);
|
|
162
|
+
return (
|
|
163
|
+
<Text
|
|
164
|
+
className={cn(buttonTextVariants({ variant, size }), className)}
|
|
165
|
+
{...props}
|
|
166
|
+
>
|
|
167
|
+
{children}
|
|
168
|
+
</Text>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
ButtonText.displayName = "Button.Text";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./button";
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { View, Text, type ViewProps, type TextProps } from "react-native";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utilities/cn";
|
|
5
|
+
|
|
6
|
+
export const cardVariants = cva(
|
|
7
|
+
"w-full rounded-2xl border bg-white dark:bg-neutral-900 overflow-hidden",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "border-neutral-200 dark:border-neutral-800 shadow-sm",
|
|
12
|
+
elevated: "border-neutral-200/80 dark:border-neutral-800 shadow-md",
|
|
13
|
+
outlined: "border-2 border-primary-200 dark:border-primary-800",
|
|
14
|
+
ghost: "border-transparent bg-transparent shadow-none",
|
|
15
|
+
},
|
|
16
|
+
padding: {
|
|
17
|
+
none: "p-0",
|
|
18
|
+
sm: "p-3",
|
|
19
|
+
md: "p-4",
|
|
20
|
+
lg: "p-6",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
variant: "default",
|
|
25
|
+
padding: "none",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export interface CardProps
|
|
31
|
+
extends ViewProps,
|
|
32
|
+
VariantProps<typeof cardVariants> {
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const Card = React.forwardRef<View, CardProps>(function Card(
|
|
37
|
+
{ className, variant, padding, ...props },
|
|
38
|
+
ref
|
|
39
|
+
) {
|
|
40
|
+
return (
|
|
41
|
+
<View
|
|
42
|
+
ref={ref}
|
|
43
|
+
className={cn(cardVariants({ variant, padding }), className)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
Card.displayName = "Card";
|
|
49
|
+
|
|
50
|
+
export const CardHeader = React.forwardRef<
|
|
51
|
+
View,
|
|
52
|
+
ViewProps & { className?: string }
|
|
53
|
+
>(function CardHeader({ className, ...props }, ref) {
|
|
54
|
+
return (
|
|
55
|
+
<View
|
|
56
|
+
ref={ref}
|
|
57
|
+
className={cn("flex-col space-y-1.5 p-4 pb-2", className)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
CardHeader.displayName = "Card.Header";
|
|
63
|
+
|
|
64
|
+
export const CardTitle = React.forwardRef<
|
|
65
|
+
Text,
|
|
66
|
+
TextProps & { className?: string }
|
|
67
|
+
>(function CardTitle({ className, ...props }, ref) {
|
|
68
|
+
return (
|
|
69
|
+
<Text
|
|
70
|
+
ref={ref}
|
|
71
|
+
className={cn(
|
|
72
|
+
"text-lg font-bold tracking-tight text-neutral-900 dark:text-neutral-50",
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
CardTitle.displayName = "Card.Title";
|
|
80
|
+
|
|
81
|
+
export const CardDescription = React.forwardRef<
|
|
82
|
+
Text,
|
|
83
|
+
TextProps & { className?: string }
|
|
84
|
+
>(function CardDescription({ className, ...props }, ref) {
|
|
85
|
+
return (
|
|
86
|
+
<Text
|
|
87
|
+
ref={ref}
|
|
88
|
+
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
CardDescription.displayName = "Card.Description";
|
|
94
|
+
|
|
95
|
+
export const CardContent = React.forwardRef<
|
|
96
|
+
View,
|
|
97
|
+
ViewProps & { className?: string }
|
|
98
|
+
>(function CardContent({ className, ...props }, ref) {
|
|
99
|
+
return (
|
|
100
|
+
<View
|
|
101
|
+
ref={ref}
|
|
102
|
+
className={cn("p-4 pt-1", className)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
CardContent.displayName = "Card.Content";
|
|
108
|
+
|
|
109
|
+
export const CardFooter = React.forwardRef<
|
|
110
|
+
View,
|
|
111
|
+
ViewProps & { className?: string }
|
|
112
|
+
>(function CardFooter({ className, ...props }, ref) {
|
|
113
|
+
return (
|
|
114
|
+
<View
|
|
115
|
+
ref={ref}
|
|
116
|
+
className={cn("flex-row items-center p-4 pt-0", className)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
CardFooter.displayName = "Card.Footer";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./card";
|