@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.
- package/dist/index.js +78 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +78 -61
- package/dist/index.mjs.map +1 -1
- 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 +276 -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 +377 -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 +119 -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 +204 -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 @@
|
|
|
1
|
+
export * from "./spinner";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ActivityIndicator, View, type ViewProps } from "react-native";
|
|
3
|
+
import { cn } from "../../utilities/cn";
|
|
4
|
+
|
|
5
|
+
export type SpinnerColor =
|
|
6
|
+
| "primary"
|
|
7
|
+
| "secondary"
|
|
8
|
+
| "danger"
|
|
9
|
+
| "success"
|
|
10
|
+
| "warning"
|
|
11
|
+
| "white"
|
|
12
|
+
| "muted";
|
|
13
|
+
|
|
14
|
+
export interface SpinnerProps extends ViewProps {
|
|
15
|
+
size?: "sm" | "md" | "lg";
|
|
16
|
+
color?: SpinnerColor;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const colorMap: Record<SpinnerColor, string> = {
|
|
21
|
+
primary: "#2563eb",
|
|
22
|
+
secondary: "#475569",
|
|
23
|
+
danger: "#dc2626",
|
|
24
|
+
success: "#16a34a",
|
|
25
|
+
warning: "#d97706",
|
|
26
|
+
white: "#ffffff",
|
|
27
|
+
muted: "#94a3b8",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function Spinner({
|
|
31
|
+
size = "md",
|
|
32
|
+
color = "primary",
|
|
33
|
+
className,
|
|
34
|
+
...props
|
|
35
|
+
}: SpinnerProps) {
|
|
36
|
+
const nativeSize = size === "lg" ? "large" : "small";
|
|
37
|
+
const hexColor = colorMap[color] ?? colorMap.primary;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View className={cn("items-center justify-center", className)} {...props}>
|
|
41
|
+
<ActivityIndicator size={nativeSize} color={hexColor} />
|
|
42
|
+
</View>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Spinner.displayName = "Spinner";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./stats-card";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { View, Text, type ViewProps } from "react-native";
|
|
3
|
+
import { cn } from "../../utilities/cn";
|
|
4
|
+
import { Card, CardContent } from "../card/card";
|
|
5
|
+
|
|
6
|
+
export type TrendDirection = "up" | "down" | "neutral";
|
|
7
|
+
|
|
8
|
+
export interface StatsCardProps extends ViewProps {
|
|
9
|
+
title: string;
|
|
10
|
+
value: string | number;
|
|
11
|
+
trend?: {
|
|
12
|
+
direction: TrendDirection;
|
|
13
|
+
value: string | number;
|
|
14
|
+
label?: string;
|
|
15
|
+
};
|
|
16
|
+
icon?: React.ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function StatsCard({
|
|
21
|
+
title,
|
|
22
|
+
value,
|
|
23
|
+
trend,
|
|
24
|
+
icon,
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: StatsCardProps) {
|
|
28
|
+
const isUp = trend?.direction === "up";
|
|
29
|
+
const isDown = trend?.direction === "down";
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Card className={cn("w-full shadow-sm", className)} {...props}>
|
|
33
|
+
<CardContent className="p-4">
|
|
34
|
+
<View className="flex-row items-center justify-between mb-2">
|
|
35
|
+
<Text className="text-sm font-medium text-neutral-500 dark:text-neutral-400">
|
|
36
|
+
{title}
|
|
37
|
+
</Text>
|
|
38
|
+
{icon ? (
|
|
39
|
+
<View className="h-9 w-9 rounded-xl bg-neutral-100 dark:bg-neutral-800 items-center justify-center">
|
|
40
|
+
{icon}
|
|
41
|
+
</View>
|
|
42
|
+
) : null}
|
|
43
|
+
</View>
|
|
44
|
+
|
|
45
|
+
<Text className="text-2xl font-extrabold tracking-tight text-neutral-900 dark:text-neutral-50 mb-2">
|
|
46
|
+
{value}
|
|
47
|
+
</Text>
|
|
48
|
+
|
|
49
|
+
{trend ? (
|
|
50
|
+
<View className="flex-row items-center gap-1.5 flex-wrap">
|
|
51
|
+
<View
|
|
52
|
+
className={cn(
|
|
53
|
+
"flex-row items-center rounded-md px-1.5 py-0.5",
|
|
54
|
+
isUp && "bg-success-100 dark:bg-success-950/60",
|
|
55
|
+
isDown && "bg-danger-100 dark:bg-danger-950/60",
|
|
56
|
+
!isUp && !isDown && "bg-neutral-100 dark:bg-neutral-800"
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
<Text
|
|
60
|
+
className={cn(
|
|
61
|
+
"text-xs font-bold mr-1",
|
|
62
|
+
isUp && "text-success-700 dark:text-success-300",
|
|
63
|
+
isDown && "text-danger-700 dark:text-danger-300",
|
|
64
|
+
!isUp && !isDown && "text-neutral-600 dark:text-neutral-400"
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
{isUp ? "↑" : isDown ? "↓" : "—"}
|
|
68
|
+
</Text>
|
|
69
|
+
<Text
|
|
70
|
+
className={cn(
|
|
71
|
+
"text-xs font-semibold",
|
|
72
|
+
isUp && "text-success-700 dark:text-success-300",
|
|
73
|
+
isDown && "text-danger-700 dark:text-danger-300",
|
|
74
|
+
!isUp && !isDown && "text-neutral-600 dark:text-neutral-400"
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
{trend.value}
|
|
78
|
+
</Text>
|
|
79
|
+
</View>
|
|
80
|
+
{trend.label ? (
|
|
81
|
+
<Text className="text-xs text-neutral-500 dark:text-neutral-400">
|
|
82
|
+
{trend.label}
|
|
83
|
+
</Text>
|
|
84
|
+
) : null}
|
|
85
|
+
</View>
|
|
86
|
+
) : null}
|
|
87
|
+
</CardContent>
|
|
88
|
+
</Card>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
StatsCard.displayName = "StatsCard";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./switch";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
View,
|
|
5
|
+
type GestureResponderEvent,
|
|
6
|
+
type PressableProps,
|
|
7
|
+
} from "react-native";
|
|
8
|
+
import { cn } from "../../utilities/cn";
|
|
9
|
+
|
|
10
|
+
export interface SwitchProps extends Omit<PressableProps, "onPress"> {
|
|
11
|
+
checked?: boolean;
|
|
12
|
+
defaultChecked?: boolean;
|
|
13
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
thumbClassName?: string;
|
|
17
|
+
onPress?: (event: GestureResponderEvent) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Switch = React.forwardRef<
|
|
21
|
+
React.ElementRef<typeof Pressable>,
|
|
22
|
+
SwitchProps
|
|
23
|
+
>(function Switch(
|
|
24
|
+
{
|
|
25
|
+
checked: controlledChecked,
|
|
26
|
+
defaultChecked = false,
|
|
27
|
+
onCheckedChange,
|
|
28
|
+
disabled = false,
|
|
29
|
+
className,
|
|
30
|
+
thumbClassName,
|
|
31
|
+
onPress,
|
|
32
|
+
...props
|
|
33
|
+
},
|
|
34
|
+
ref
|
|
35
|
+
) {
|
|
36
|
+
const [uncontrolledChecked, setUncontrolledChecked] = React.useState(defaultChecked);
|
|
37
|
+
const isControlled = controlledChecked !== undefined;
|
|
38
|
+
const isChecked = isControlled ? controlledChecked : uncontrolledChecked;
|
|
39
|
+
|
|
40
|
+
const handlePress = (e: GestureResponderEvent) => {
|
|
41
|
+
if (disabled) return;
|
|
42
|
+
const nextChecked = !isChecked;
|
|
43
|
+
if (!isControlled) {
|
|
44
|
+
setUncontrolledChecked(nextChecked);
|
|
45
|
+
}
|
|
46
|
+
onCheckedChange?.(nextChecked);
|
|
47
|
+
onPress?.(e);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Pressable
|
|
52
|
+
ref={ref}
|
|
53
|
+
role="switch"
|
|
54
|
+
accessibilityRole="switch"
|
|
55
|
+
accessibilityState={{ checked: isChecked, disabled: !!disabled }}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
onPress={handlePress}
|
|
58
|
+
className={cn(
|
|
59
|
+
"h-7 w-12 shrink-0 flex-row items-center rounded-full p-0.5 transition-colors",
|
|
60
|
+
isChecked
|
|
61
|
+
? "bg-primary-600 dark:bg-primary-600"
|
|
62
|
+
: "bg-neutral-300 dark:bg-neutral-700",
|
|
63
|
+
disabled && "opacity-50",
|
|
64
|
+
className
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
<View
|
|
69
|
+
role="presentation"
|
|
70
|
+
className={cn(
|
|
71
|
+
"h-6 w-6 rounded-full bg-white shadow-sm transition-transform",
|
|
72
|
+
isChecked ? "translate-x-5" : "translate-x-0",
|
|
73
|
+
thumbClassName
|
|
74
|
+
)}
|
|
75
|
+
/>
|
|
76
|
+
</Pressable>
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
Switch.displayName = "Switch";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./tabs";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Pressable,
|
|
6
|
+
type ViewProps,
|
|
7
|
+
type PressableProps,
|
|
8
|
+
type GestureResponderEvent,
|
|
9
|
+
} from "react-native";
|
|
10
|
+
import { cn } from "../../utilities/cn";
|
|
11
|
+
|
|
12
|
+
export interface TabsContextValue {
|
|
13
|
+
value: string;
|
|
14
|
+
onValueChange: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const TabsContext = React.createContext<TabsContextValue | null>(null);
|
|
18
|
+
|
|
19
|
+
export function useTabsContext(): TabsContextValue {
|
|
20
|
+
const context = React.useContext(TabsContext);
|
|
21
|
+
if (!context) {
|
|
22
|
+
throw new Error("Tabs compound components must be rendered inside a Tabs component");
|
|
23
|
+
}
|
|
24
|
+
return context;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TabsProps extends ViewProps {
|
|
28
|
+
value?: string;
|
|
29
|
+
defaultValue?: string;
|
|
30
|
+
onValueChange?: (value: string) => void;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const Tabs = React.forwardRef<View, TabsProps>(function Tabs(
|
|
35
|
+
{ value: controlledValue, defaultValue = "", onValueChange, className, children, ...props },
|
|
36
|
+
ref
|
|
37
|
+
) {
|
|
38
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
39
|
+
const isControlled = controlledValue !== undefined;
|
|
40
|
+
const currentValue = isControlled ? controlledValue : internalValue;
|
|
41
|
+
|
|
42
|
+
const handleValueChange = React.useCallback(
|
|
43
|
+
(val: string) => {
|
|
44
|
+
if (!isControlled) {
|
|
45
|
+
setInternalValue(val);
|
|
46
|
+
}
|
|
47
|
+
onValueChange?.(val);
|
|
48
|
+
},
|
|
49
|
+
[isControlled, onValueChange]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<TabsContext.Provider value={{ value: currentValue, onValueChange: handleValueChange }}>
|
|
54
|
+
<View ref={ref} className={className} {...props}>
|
|
55
|
+
{children}
|
|
56
|
+
</View>
|
|
57
|
+
</TabsContext.Provider>
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
Tabs.displayName = "Tabs";
|
|
61
|
+
|
|
62
|
+
export interface TabsListProps extends ViewProps {
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const TabsList = React.forwardRef<View, TabsListProps>(function TabsList(
|
|
67
|
+
{ className, ...props },
|
|
68
|
+
ref
|
|
69
|
+
) {
|
|
70
|
+
return (
|
|
71
|
+
<View
|
|
72
|
+
ref={ref}
|
|
73
|
+
role="tablist"
|
|
74
|
+
className={cn(
|
|
75
|
+
"flex-row items-center justify-center rounded-xl bg-neutral-100 dark:bg-neutral-800 p-1",
|
|
76
|
+
className
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
TabsList.displayName = "TabsList";
|
|
83
|
+
|
|
84
|
+
export interface TabsTriggerProps extends Omit<PressableProps, "onPress"> {
|
|
85
|
+
value: string;
|
|
86
|
+
className?: string;
|
|
87
|
+
textClassName?: string;
|
|
88
|
+
children?: React.ReactNode;
|
|
89
|
+
onPress?: (e: GestureResponderEvent) => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const TabsTrigger = React.forwardRef<
|
|
93
|
+
React.ElementRef<typeof Pressable>,
|
|
94
|
+
TabsTriggerProps
|
|
95
|
+
>(function TabsTrigger({ value, className, textClassName, children, onPress, disabled, ...props }, ref) {
|
|
96
|
+
const { value: selectedValue, onValueChange } = useTabsContext();
|
|
97
|
+
const isSelected = selectedValue === value;
|
|
98
|
+
|
|
99
|
+
const handlePress = (e: GestureResponderEvent) => {
|
|
100
|
+
if (disabled) return;
|
|
101
|
+
onValueChange(value);
|
|
102
|
+
onPress?.(e);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Pressable
|
|
107
|
+
ref={ref}
|
|
108
|
+
role="tab"
|
|
109
|
+
accessibilityRole="tab"
|
|
110
|
+
accessibilityState={{ selected: isSelected, disabled: !!disabled }}
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
onPress={handlePress}
|
|
113
|
+
className={cn(
|
|
114
|
+
"flex-1 items-center justify-center rounded-lg py-2 px-3 transition-all",
|
|
115
|
+
isSelected
|
|
116
|
+
? "bg-white dark:bg-neutral-900 shadow-sm"
|
|
117
|
+
: "bg-transparent opacity-70",
|
|
118
|
+
disabled && "opacity-40",
|
|
119
|
+
className
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
{typeof children === "string" ? (
|
|
124
|
+
<Text
|
|
125
|
+
className={cn(
|
|
126
|
+
"text-sm font-semibold",
|
|
127
|
+
isSelected
|
|
128
|
+
? "text-neutral-900 dark:text-neutral-50"
|
|
129
|
+
: "text-neutral-600 dark:text-neutral-400",
|
|
130
|
+
textClassName
|
|
131
|
+
)}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
</Text>
|
|
135
|
+
) : (
|
|
136
|
+
children
|
|
137
|
+
)}
|
|
138
|
+
</Pressable>
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
TabsTrigger.displayName = "TabsTrigger";
|
|
142
|
+
|
|
143
|
+
export interface TabsContentProps extends ViewProps {
|
|
144
|
+
value: string;
|
|
145
|
+
className?: string;
|
|
146
|
+
forceMount?: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const TabsContent = React.forwardRef<View, TabsContentProps>(function TabsContent(
|
|
150
|
+
{ value, className, forceMount, children, ...props },
|
|
151
|
+
ref
|
|
152
|
+
) {
|
|
153
|
+
const { value: selectedValue } = useTabsContext();
|
|
154
|
+
const isSelected = selectedValue === value;
|
|
155
|
+
|
|
156
|
+
if (!isSelected && !forceMount) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<View
|
|
162
|
+
ref={ref}
|
|
163
|
+
role="tabpanel"
|
|
164
|
+
accessibilityElementsHidden={!isSelected}
|
|
165
|
+
className={cn("mt-3", className)}
|
|
166
|
+
{...props}
|
|
167
|
+
>
|
|
168
|
+
{children}
|
|
169
|
+
</View>
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
TabsContent.displayName = "TabsContent";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Text as NativeText, type TextProps as NativeTextProps } from "react-native";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utilities/cn";
|
|
5
|
+
|
|
6
|
+
export const headingVariants = cva("font-bold text-neutral-900 dark:text-neutral-50 tracking-tight", {
|
|
7
|
+
variants: {
|
|
8
|
+
level: {
|
|
9
|
+
h1: "text-3xl font-extrabold",
|
|
10
|
+
h2: "text-2xl font-bold",
|
|
11
|
+
h3: "text-xl font-bold",
|
|
12
|
+
h4: "text-lg font-semibold",
|
|
13
|
+
h5: "text-base font-semibold",
|
|
14
|
+
h6: "text-sm font-semibold uppercase tracking-wider",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
level: "h2",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | 1 | 2 | 3 | 4 | 5 | 6;
|
|
23
|
+
|
|
24
|
+
export interface HeadingProps
|
|
25
|
+
extends NativeTextProps,
|
|
26
|
+
Omit<VariantProps<typeof headingVariants>, "level"> {
|
|
27
|
+
level?: HeadingLevel;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Heading = React.forwardRef<NativeText, HeadingProps>(function Heading(
|
|
32
|
+
{ className, level = "h2", ...props },
|
|
33
|
+
ref
|
|
34
|
+
) {
|
|
35
|
+
const normalizedLevel =
|
|
36
|
+
typeof level === "number"
|
|
37
|
+
? (`h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6")
|
|
38
|
+
: level;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<NativeText
|
|
42
|
+
ref={ref}
|
|
43
|
+
accessibilityRole="header"
|
|
44
|
+
className={cn(headingVariants({ level: normalizedLevel }), className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
Heading.displayName = "Heading";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Text as NativeText, type TextProps as NativeTextProps } from "react-native";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utilities/cn";
|
|
5
|
+
|
|
6
|
+
export const textVariants = cva("text-neutral-900 dark:text-neutral-100", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
body: "text-base font-normal",
|
|
10
|
+
caption: "text-xs font-normal text-neutral-500 dark:text-neutral-400",
|
|
11
|
+
label: "text-sm font-medium text-neutral-700 dark:text-neutral-300",
|
|
12
|
+
muted: "text-sm font-normal text-neutral-500 dark:text-neutral-400",
|
|
13
|
+
lead: "text-lg font-normal text-neutral-600 dark:text-neutral-300",
|
|
14
|
+
},
|
|
15
|
+
color: {
|
|
16
|
+
default: "text-neutral-900 dark:text-neutral-100",
|
|
17
|
+
muted: "text-neutral-500 dark:text-neutral-400",
|
|
18
|
+
primary: "text-primary-600 dark:text-primary-400",
|
|
19
|
+
secondary: "text-secondary-600 dark:text-secondary-400",
|
|
20
|
+
danger: "text-danger-600 dark:text-danger-400",
|
|
21
|
+
success: "text-success-600 dark:text-success-400",
|
|
22
|
+
warning: "text-warning-600 dark:text-warning-400",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
xs: "text-xs",
|
|
26
|
+
sm: "text-sm",
|
|
27
|
+
base: "text-base",
|
|
28
|
+
lg: "text-lg",
|
|
29
|
+
xl: "text-xl",
|
|
30
|
+
},
|
|
31
|
+
weight: {
|
|
32
|
+
normal: "font-normal",
|
|
33
|
+
medium: "font-medium",
|
|
34
|
+
semibold: "font-semibold",
|
|
35
|
+
bold: "font-bold",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "body",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export interface TextProps
|
|
44
|
+
extends NativeTextProps,
|
|
45
|
+
VariantProps<typeof textVariants> {
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const Text = React.forwardRef<NativeText, TextProps>(function Text(
|
|
50
|
+
{ className, variant, color, size, weight, ...props },
|
|
51
|
+
ref
|
|
52
|
+
) {
|
|
53
|
+
return (
|
|
54
|
+
<NativeText
|
|
55
|
+
ref={ref}
|
|
56
|
+
className={cn(textVariants({ variant, color, size, weight }), className)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
Text.displayName = "Text";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./textarea";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Text,
|
|
4
|
+
TextInput,
|
|
5
|
+
View,
|
|
6
|
+
type TextInputProps,
|
|
7
|
+
} from "react-native";
|
|
8
|
+
import { cn } from "../../utilities/cn";
|
|
9
|
+
|
|
10
|
+
export interface TextareaProps
|
|
11
|
+
extends Omit<TextInputProps, "multiline"> {
|
|
12
|
+
label?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
helperText?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
rows?: number;
|
|
17
|
+
containerClassName?: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
labelClassName?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Textarea = React.forwardRef<TextInput, TextareaProps>(function Textarea(
|
|
23
|
+
{
|
|
24
|
+
label,
|
|
25
|
+
error,
|
|
26
|
+
helperText,
|
|
27
|
+
disabled = false,
|
|
28
|
+
rows = 4,
|
|
29
|
+
containerClassName,
|
|
30
|
+
className,
|
|
31
|
+
labelClassName,
|
|
32
|
+
placeholderTextColor,
|
|
33
|
+
accessibilityLabel,
|
|
34
|
+
...props
|
|
35
|
+
},
|
|
36
|
+
ref
|
|
37
|
+
) {
|
|
38
|
+
const minHeight = Math.max(80, rows * 22);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View className={cn("w-full gap-1.5", containerClassName)}>
|
|
42
|
+
{label ? (
|
|
43
|
+
<Text
|
|
44
|
+
className={cn(
|
|
45
|
+
"text-sm font-medium text-neutral-800 dark:text-neutral-200",
|
|
46
|
+
labelClassName
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
{label}
|
|
50
|
+
</Text>
|
|
51
|
+
) : null}
|
|
52
|
+
|
|
53
|
+
<TextInput
|
|
54
|
+
ref={ref}
|
|
55
|
+
multiline
|
|
56
|
+
textAlignVertical="top"
|
|
57
|
+
editable={!disabled}
|
|
58
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
59
|
+
accessibilityHint={error}
|
|
60
|
+
aria-invalid={!!error}
|
|
61
|
+
placeholderTextColor={placeholderTextColor ?? "#9ca3af"}
|
|
62
|
+
style={{ minHeight }}
|
|
63
|
+
className={cn(
|
|
64
|
+
"w-full rounded-xl border bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 p-3.5 text-base",
|
|
65
|
+
error
|
|
66
|
+
? "border-danger-500 dark:border-danger-500"
|
|
67
|
+
: "border-neutral-300 dark:border-neutral-700 focus:border-primary-500",
|
|
68
|
+
disabled && "opacity-50 bg-neutral-100 dark:bg-neutral-800",
|
|
69
|
+
className
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
{error ? (
|
|
75
|
+
<Text accessibilityRole="alert" className="text-xs font-medium text-danger-600 dark:text-danger-400">
|
|
76
|
+
{error}
|
|
77
|
+
</Text>
|
|
78
|
+
) : helperText ? (
|
|
79
|
+
<Text className="text-xs text-neutral-500 dark:text-neutral-400">
|
|
80
|
+
{helperText}
|
|
81
|
+
</Text>
|
|
82
|
+
) : null}
|
|
83
|
+
</View>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
Textarea.displayName = "Textarea";
|