@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beyondcorp/beyond-ui-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Accessible, NativeWind-first React Native component library for Beyond Softwares",
|
|
5
5
|
"author": "Beyond Corp",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,12 +28,15 @@
|
|
|
28
28
|
],
|
|
29
29
|
"main": "./dist/index.js",
|
|
30
30
|
"module": "./dist/index.mjs",
|
|
31
|
+
"react-native": "./src/index.ts",
|
|
31
32
|
"types": "./dist/index.d.ts",
|
|
32
33
|
"exports": {
|
|
33
34
|
".": {
|
|
34
35
|
"types": "./dist/index.d.ts",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
36
|
+
"react-native": "./src/index.ts",
|
|
37
|
+
"import": "./src/index.ts",
|
|
38
|
+
"require": "./dist/index.js",
|
|
39
|
+
"default": "./src/index.ts"
|
|
37
40
|
},
|
|
38
41
|
"./preset": {
|
|
39
42
|
"types": "./dist/preset.d.ts",
|
|
@@ -42,12 +45,15 @@
|
|
|
42
45
|
},
|
|
43
46
|
"./utilities": {
|
|
44
47
|
"types": "./dist/utilities.d.ts",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
48
|
+
"react-native": "./src/utilities/index.ts",
|
|
49
|
+
"import": "./src/utilities/index.ts",
|
|
50
|
+
"require": "./dist/utilities.js",
|
|
51
|
+
"default": "./src/utilities/index.ts"
|
|
47
52
|
}
|
|
48
53
|
},
|
|
49
54
|
"files": [
|
|
50
55
|
"dist",
|
|
56
|
+
"src",
|
|
51
57
|
"README.md",
|
|
52
58
|
"LICENSE"
|
|
53
59
|
],
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Pressable,
|
|
5
|
+
Animated,
|
|
6
|
+
type ViewProps,
|
|
7
|
+
type PressableProps,
|
|
8
|
+
} from "react-native";
|
|
9
|
+
import { cn } from "../../utilities/cn";
|
|
10
|
+
import { Text } from "../text/text";
|
|
11
|
+
|
|
12
|
+
type AccordionType = "single" | "multiple";
|
|
13
|
+
|
|
14
|
+
interface AccordionContextValue {
|
|
15
|
+
type: AccordionType;
|
|
16
|
+
value: string[];
|
|
17
|
+
toggleItem: (itemValue: string) => void;
|
|
18
|
+
collapsible: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const AccordionContext = React.createContext<AccordionContextValue | null>(null);
|
|
22
|
+
|
|
23
|
+
function useAccordionContext() {
|
|
24
|
+
const context = React.useContext(AccordionContext);
|
|
25
|
+
if (!context) {
|
|
26
|
+
throw new Error("Accordion components must be used within an Accordion");
|
|
27
|
+
}
|
|
28
|
+
return context;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AccordionProps extends ViewProps {
|
|
32
|
+
type?: AccordionType;
|
|
33
|
+
value?: string | string[];
|
|
34
|
+
defaultValue?: string | string[];
|
|
35
|
+
onValueChange?: (value: string | string[]) => void;
|
|
36
|
+
collapsible?: boolean;
|
|
37
|
+
className?: string;
|
|
38
|
+
children: React.ReactNode;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Accordion = React.forwardRef<View, AccordionProps>(
|
|
42
|
+
(
|
|
43
|
+
{
|
|
44
|
+
type = "single",
|
|
45
|
+
value: controlledValue,
|
|
46
|
+
defaultValue,
|
|
47
|
+
onValueChange,
|
|
48
|
+
collapsible = true,
|
|
49
|
+
className,
|
|
50
|
+
children,
|
|
51
|
+
...props
|
|
52
|
+
},
|
|
53
|
+
ref
|
|
54
|
+
) => {
|
|
55
|
+
const toArray = (v?: string | string[]): string[] => {
|
|
56
|
+
if (!v) return [];
|
|
57
|
+
return Array.isArray(v) ? v : [v];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState<string[]>(
|
|
61
|
+
toArray(defaultValue)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const isControlled = controlledValue !== undefined;
|
|
65
|
+
const currentValues = isControlled ? toArray(controlledValue) : uncontrolledValue;
|
|
66
|
+
|
|
67
|
+
const toggleItem = React.useCallback(
|
|
68
|
+
(itemValue: string) => {
|
|
69
|
+
let nextValues: string[];
|
|
70
|
+
const isCurrentOpen = currentValues.includes(itemValue);
|
|
71
|
+
|
|
72
|
+
if (type === "single") {
|
|
73
|
+
if (isCurrentOpen) {
|
|
74
|
+
nextValues = collapsible ? [] : [itemValue];
|
|
75
|
+
} else {
|
|
76
|
+
nextValues = [itemValue];
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
if (isCurrentOpen) {
|
|
80
|
+
nextValues = currentValues.filter((v) => v !== itemValue);
|
|
81
|
+
} else {
|
|
82
|
+
nextValues = [...currentValues, itemValue];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!isControlled) {
|
|
87
|
+
setUncontrolledValue(nextValues);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (onValueChange) {
|
|
91
|
+
onValueChange(type === "single" ? nextValues[0] ?? "" : nextValues);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
[currentValues, type, collapsible, isControlled, onValueChange]
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<AccordionContext.Provider
|
|
99
|
+
value={{
|
|
100
|
+
type,
|
|
101
|
+
value: currentValues,
|
|
102
|
+
toggleItem,
|
|
103
|
+
collapsible,
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<View
|
|
107
|
+
ref={ref}
|
|
108
|
+
className={cn("w-full divide-y divide-border", className)}
|
|
109
|
+
{...props}
|
|
110
|
+
>
|
|
111
|
+
{children}
|
|
112
|
+
</View>
|
|
113
|
+
</AccordionContext.Provider>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
Accordion.displayName = "Accordion";
|
|
118
|
+
|
|
119
|
+
// --- Accordion Item ---
|
|
120
|
+
|
|
121
|
+
interface AccordionItemContextValue {
|
|
122
|
+
value: string;
|
|
123
|
+
isOpen: boolean;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const AccordionItemContext = React.createContext<AccordionItemContextValue | null>(null);
|
|
127
|
+
|
|
128
|
+
function useAccordionItemContext() {
|
|
129
|
+
const context = React.useContext(AccordionItemContext);
|
|
130
|
+
if (!context) {
|
|
131
|
+
throw new Error("AccordionItem subcomponents must be used within an AccordionItem");
|
|
132
|
+
}
|
|
133
|
+
return context;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface AccordionItemProps extends ViewProps {
|
|
137
|
+
value: string;
|
|
138
|
+
className?: string;
|
|
139
|
+
children: React.ReactNode;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const AccordionItem = React.forwardRef<View, AccordionItemProps>(
|
|
143
|
+
({ value, className, children, ...props }, ref) => {
|
|
144
|
+
const { value: selectedValues } = useAccordionContext();
|
|
145
|
+
const isOpen = selectedValues.includes(value);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<AccordionItemContext.Provider value={{ value, isOpen }}>
|
|
149
|
+
<View
|
|
150
|
+
ref={ref}
|
|
151
|
+
className={cn("border-b border-border py-1", className)}
|
|
152
|
+
{...props}
|
|
153
|
+
>
|
|
154
|
+
{children}
|
|
155
|
+
</View>
|
|
156
|
+
</AccordionItemContext.Provider>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
AccordionItem.displayName = "AccordionItem";
|
|
161
|
+
|
|
162
|
+
// --- Accordion Trigger ---
|
|
163
|
+
|
|
164
|
+
export interface AccordionTriggerProps extends PressableProps {
|
|
165
|
+
className?: string;
|
|
166
|
+
children: React.ReactNode;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const AccordionTrigger = React.forwardRef<View, AccordionTriggerProps>(
|
|
170
|
+
({ className, children, ...props }, ref) => {
|
|
171
|
+
const { toggleItem } = useAccordionContext();
|
|
172
|
+
const { value, isOpen } = useAccordionItemContext();
|
|
173
|
+
|
|
174
|
+
const rotationAnim = React.useRef(new Animated.Value(isOpen ? 1 : 0)).current;
|
|
175
|
+
|
|
176
|
+
React.useEffect(() => {
|
|
177
|
+
Animated.timing(rotationAnim, {
|
|
178
|
+
toValue: isOpen ? 1 : 0,
|
|
179
|
+
duration: 200,
|
|
180
|
+
useNativeDriver: true,
|
|
181
|
+
}).start();
|
|
182
|
+
}, [isOpen, rotationAnim]);
|
|
183
|
+
|
|
184
|
+
const rotate = rotationAnim.interpolate({
|
|
185
|
+
inputRange: [0, 1],
|
|
186
|
+
outputRange: ["0deg", "180deg"],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<Pressable
|
|
191
|
+
ref={ref}
|
|
192
|
+
onPress={() => toggleItem(value)}
|
|
193
|
+
accessibilityRole="button"
|
|
194
|
+
accessibilityState={{ expanded: isOpen }}
|
|
195
|
+
className={cn(
|
|
196
|
+
"flex-row items-center justify-between py-3 px-1 active:opacity-70",
|
|
197
|
+
className
|
|
198
|
+
)}
|
|
199
|
+
{...props}
|
|
200
|
+
>
|
|
201
|
+
<View className="flex-1 pr-2">
|
|
202
|
+
{typeof children === "string" ? (
|
|
203
|
+
<Text className="text-base font-medium text-foreground">{children}</Text>
|
|
204
|
+
) : (
|
|
205
|
+
children
|
|
206
|
+
)}
|
|
207
|
+
</View>
|
|
208
|
+
<Animated.View style={{ transform: [{ rotate }] }}>
|
|
209
|
+
<Text className="text-muted text-base font-semibold">▼</Text>
|
|
210
|
+
</Animated.View>
|
|
211
|
+
</Pressable>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
AccordionTrigger.displayName = "AccordionTrigger";
|
|
216
|
+
|
|
217
|
+
// --- Accordion Content ---
|
|
218
|
+
|
|
219
|
+
export interface AccordionContentProps extends ViewProps {
|
|
220
|
+
className?: string;
|
|
221
|
+
children: React.ReactNode;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const AccordionContent = React.forwardRef<View, AccordionContentProps>(
|
|
225
|
+
({ className, children, ...props }, ref) => {
|
|
226
|
+
const { isOpen } = useAccordionItemContext();
|
|
227
|
+
|
|
228
|
+
if (!isOpen) return null;
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<View
|
|
232
|
+
ref={ref}
|
|
233
|
+
className={cn("pt-1 pb-3 px-1 text-sm text-muted", className)}
|
|
234
|
+
{...props}
|
|
235
|
+
>
|
|
236
|
+
{typeof children === "string" ? (
|
|
237
|
+
<Text className="text-sm text-muted leading-relaxed">{children}</Text>
|
|
238
|
+
) : (
|
|
239
|
+
children
|
|
240
|
+
)}
|
|
241
|
+
</View>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
AccordionContent.displayName = "AccordionContent";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./accordion";
|
|
@@ -0,0 +1,118 @@
|
|
|
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 alertVariants = cva(
|
|
7
|
+
"w-full rounded-2xl border p-4 flex-col gap-1",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
info: "bg-primary-50 border-primary-200 dark:bg-primary-950/40 dark:border-primary-800",
|
|
12
|
+
success: "bg-success-50 border-success-200 dark:bg-success-950/40 dark:border-success-800",
|
|
13
|
+
warning: "bg-warning-50 border-warning-200 dark:bg-warning-950/40 dark:border-warning-800",
|
|
14
|
+
danger: "bg-danger-50 border-danger-200 dark:bg-danger-950/40 dark:border-danger-800",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
variant: "info",
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export const alertTitleVariants = cva("text-base font-bold tracking-tight", {
|
|
24
|
+
variants: {
|
|
25
|
+
variant: {
|
|
26
|
+
info: "text-primary-800 dark:text-primary-300",
|
|
27
|
+
success: "text-success-800 dark:text-success-300",
|
|
28
|
+
warning: "text-warning-800 dark:text-warning-300",
|
|
29
|
+
danger: "text-danger-800 dark:text-danger-300",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
variant: "info",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const alertDescriptionVariants = cva("text-sm", {
|
|
38
|
+
variants: {
|
|
39
|
+
variant: {
|
|
40
|
+
info: "text-primary-700 dark:text-primary-400",
|
|
41
|
+
success: "text-success-700 dark:text-success-400",
|
|
42
|
+
warning: "text-warning-700 dark:text-warning-400",
|
|
43
|
+
danger: "text-danger-700 dark:text-danger-400",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultVariants: {
|
|
47
|
+
variant: "info",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
type AlertVariantProps = VariantProps<typeof alertVariants>;
|
|
52
|
+
|
|
53
|
+
interface AlertContextValue {
|
|
54
|
+
variant: AlertVariantProps["variant"];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const AlertContext = React.createContext<AlertContextValue>({ variant: "info" });
|
|
58
|
+
|
|
59
|
+
export interface AlertProps extends ViewProps, AlertVariantProps {
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function Alert({
|
|
64
|
+
variant = "info",
|
|
65
|
+
className,
|
|
66
|
+
children,
|
|
67
|
+
...props
|
|
68
|
+
}: AlertProps) {
|
|
69
|
+
return (
|
|
70
|
+
<AlertContext.Provider value={{ variant }}>
|
|
71
|
+
<View
|
|
72
|
+
accessibilityRole="alert"
|
|
73
|
+
className={cn(alertVariants({ variant }), className)}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</View>
|
|
78
|
+
</AlertContext.Provider>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Alert.displayName = "Alert";
|
|
83
|
+
|
|
84
|
+
export function AlertTitle({
|
|
85
|
+
className,
|
|
86
|
+
children,
|
|
87
|
+
...props
|
|
88
|
+
}: TextProps & { className?: string }) {
|
|
89
|
+
const { variant } = React.useContext(AlertContext);
|
|
90
|
+
return (
|
|
91
|
+
<Text
|
|
92
|
+
className={cn(alertTitleVariants({ variant }), className)}
|
|
93
|
+
{...props}
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
</Text>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
AlertTitle.displayName = "Alert.Title";
|
|
101
|
+
|
|
102
|
+
export function AlertDescription({
|
|
103
|
+
className,
|
|
104
|
+
children,
|
|
105
|
+
...props
|
|
106
|
+
}: TextProps & { className?: string }) {
|
|
107
|
+
const { variant } = React.useContext(AlertContext);
|
|
108
|
+
return (
|
|
109
|
+
<Text
|
|
110
|
+
className={cn(alertDescriptionVariants({ variant }), className)}
|
|
111
|
+
{...props}
|
|
112
|
+
>
|
|
113
|
+
{children}
|
|
114
|
+
</Text>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
AlertDescription.displayName = "Alert.Description";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./alert";
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { View, Text, Image as NativeImage, type ImageProps, type ViewProps } from "react-native";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utilities/cn";
|
|
5
|
+
|
|
6
|
+
export const avatarVariants = cva(
|
|
7
|
+
"relative flex shrink-0 overflow-hidden rounded-full items-center justify-center bg-neutral-200 dark:bg-neutral-800",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: "h-8 w-8",
|
|
12
|
+
md: "h-10 w-10",
|
|
13
|
+
lg: "h-14 w-14",
|
|
14
|
+
xl: "h-20 w-20",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
size: "md",
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export const avatarTextVariants = cva("font-semibold text-neutral-700 dark:text-neutral-300", {
|
|
24
|
+
variants: {
|
|
25
|
+
size: {
|
|
26
|
+
sm: "text-xs",
|
|
27
|
+
md: "text-sm",
|
|
28
|
+
lg: "text-lg",
|
|
29
|
+
xl: "text-2xl",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
size: "md",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
interface AvatarContextValue {
|
|
38
|
+
size: "sm" | "md" | "lg" | "xl";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const AvatarContext = React.createContext<AvatarContextValue>({ size: "md" });
|
|
42
|
+
|
|
43
|
+
export interface AvatarProps
|
|
44
|
+
extends ViewProps,
|
|
45
|
+
VariantProps<typeof avatarVariants> {
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function Avatar({
|
|
50
|
+
className,
|
|
51
|
+
size = "md",
|
|
52
|
+
children,
|
|
53
|
+
...props
|
|
54
|
+
}: AvatarProps) {
|
|
55
|
+
const currentSize = size ?? "md";
|
|
56
|
+
return (
|
|
57
|
+
<AvatarContext.Provider value={{ size: currentSize }}>
|
|
58
|
+
<View
|
|
59
|
+
className={cn(avatarVariants({ size: currentSize }), className)}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</View>
|
|
64
|
+
</AvatarContext.Provider>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
Avatar.displayName = "Avatar";
|
|
68
|
+
|
|
69
|
+
export interface AvatarImageProps extends ImageProps {
|
|
70
|
+
className?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function AvatarImage({ className, ...props }: AvatarImageProps) {
|
|
74
|
+
const [hasError, setHasError] = React.useState(false);
|
|
75
|
+
|
|
76
|
+
if (hasError) return null;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<NativeImage
|
|
80
|
+
onError={() => setHasError(true)}
|
|
81
|
+
className={cn("h-full w-full rounded-full object-cover", className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
AvatarImage.displayName = "Avatar.Image";
|
|
87
|
+
|
|
88
|
+
export interface AvatarFallbackProps extends ViewProps {
|
|
89
|
+
children?: React.ReactNode;
|
|
90
|
+
text?: string;
|
|
91
|
+
className?: string;
|
|
92
|
+
textClassName?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function AvatarFallback({
|
|
96
|
+
children,
|
|
97
|
+
text,
|
|
98
|
+
className,
|
|
99
|
+
textClassName,
|
|
100
|
+
...props
|
|
101
|
+
}: AvatarFallbackProps) {
|
|
102
|
+
const { size } = React.useContext(AvatarContext);
|
|
103
|
+
const content = text ?? children;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<View
|
|
107
|
+
className={cn(
|
|
108
|
+
"h-full w-full items-center justify-center rounded-full bg-neutral-200 dark:bg-neutral-800",
|
|
109
|
+
className
|
|
110
|
+
)}
|
|
111
|
+
{...props}
|
|
112
|
+
>
|
|
113
|
+
{typeof content === "string" ? (
|
|
114
|
+
<Text
|
|
115
|
+
className={cn(avatarTextVariants({ size }), textClassName)}
|
|
116
|
+
>
|
|
117
|
+
{content}
|
|
118
|
+
</Text>
|
|
119
|
+
) : (
|
|
120
|
+
content
|
|
121
|
+
)}
|
|
122
|
+
</View>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
AvatarFallback.displayName = "Avatar.Fallback";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./avatar";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { View, Text, type ViewProps } from "react-native";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utilities/cn";
|
|
5
|
+
|
|
6
|
+
export const badgeVariants = cva(
|
|
7
|
+
"inline-flex flex-row items-center justify-center rounded-full self-start",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
primary: "bg-primary-600 dark:bg-primary-500",
|
|
12
|
+
secondary: "bg-secondary-600 dark:bg-secondary-500",
|
|
13
|
+
danger: "bg-danger-600 dark:bg-danger-500",
|
|
14
|
+
destructive: "bg-danger-600 dark:bg-danger-500",
|
|
15
|
+
success: "bg-success-600 dark:bg-success-500",
|
|
16
|
+
warning: "bg-warning-600 dark:bg-warning-500",
|
|
17
|
+
outline: "border border-neutral-300 dark:border-neutral-700 bg-transparent",
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
sm: "px-2 py-0.5",
|
|
21
|
+
md: "px-2.5 py-1",
|
|
22
|
+
lg: "px-3.5 py-1.5",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "primary",
|
|
27
|
+
size: "md",
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export const badgeTextVariants = cva("font-semibold text-center", {
|
|
33
|
+
variants: {
|
|
34
|
+
variant: {
|
|
35
|
+
primary: "text-white",
|
|
36
|
+
secondary: "text-white",
|
|
37
|
+
danger: "text-white",
|
|
38
|
+
destructive: "text-white",
|
|
39
|
+
success: "text-white",
|
|
40
|
+
warning: "text-white",
|
|
41
|
+
outline: "text-neutral-700 dark:text-neutral-300",
|
|
42
|
+
},
|
|
43
|
+
size: {
|
|
44
|
+
sm: "text-[10px]",
|
|
45
|
+
md: "text-xs",
|
|
46
|
+
lg: "text-sm",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
defaultVariants: {
|
|
50
|
+
variant: "primary",
|
|
51
|
+
size: "md",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export interface BadgeProps
|
|
56
|
+
extends ViewProps,
|
|
57
|
+
VariantProps<typeof badgeVariants> {
|
|
58
|
+
children?: React.ReactNode;
|
|
59
|
+
className?: string;
|
|
60
|
+
textClassName?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function Badge({
|
|
64
|
+
children,
|
|
65
|
+
variant = "primary",
|
|
66
|
+
size = "md",
|
|
67
|
+
className,
|
|
68
|
+
textClassName,
|
|
69
|
+
...props
|
|
70
|
+
}: BadgeProps) {
|
|
71
|
+
return (
|
|
72
|
+
<View
|
|
73
|
+
className={cn(badgeVariants({ variant, size }), className)}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
{typeof children === "string" || typeof children === "number" ? (
|
|
77
|
+
<Text
|
|
78
|
+
className={cn(badgeTextVariants({ variant, size }), textClassName)}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
</Text>
|
|
82
|
+
) : (
|
|
83
|
+
children
|
|
84
|
+
)}
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
Badge.displayName = "Badge";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./badge";
|