parent
361038f2f2
commit
c41f48db92
@ -0,0 +1,67 @@
|
||||
import { Text } from "./components/ui/text";
|
||||
import { VStack } from "./components/ui/vstack";
|
||||
import { Button, ButtonGroup } from "./components/ui/button";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { HStack } from "./components/ui/hstack";
|
||||
import React from "react";
|
||||
import { FeatherIconNames } from "@expo/vector-icons/build/Feather";
|
||||
import { Box } from "./components/ui/box";
|
||||
import { Heading } from "./components/ui/heading";
|
||||
import { Link } from "expo-router";
|
||||
import { LinkText } from "./components/ui/link";
|
||||
import LoginForm from "./components/form/LoginForm";
|
||||
import Screen from "./components/Screen";
|
||||
|
||||
const socialNetworkButtons: ISocialNetworkButtons[] = [
|
||||
{ icon: "instagram" },
|
||||
{ icon: "facebook" },
|
||||
{ icon: "linkedin" },
|
||||
];
|
||||
|
||||
interface ISocialNetworkButtons {
|
||||
icon: FeatherIconNames;
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<Screen>
|
||||
<Box className="h-full justify-center">
|
||||
<VStack space="2xl">
|
||||
<VStack space="sm">
|
||||
<Heading className="text-center" size="3xl">
|
||||
Connexion à Optifit
|
||||
</Heading>
|
||||
<Text size="lg" className="text-center">
|
||||
Personnalise ton expérience du sport avec Optifit, ton nouveau
|
||||
coach IA.
|
||||
</Text>
|
||||
</VStack>
|
||||
<LoginForm />
|
||||
<ButtonGroup className="justify-center" flexDirection="row">
|
||||
{socialNetworkButtons.map((socialNetworkButton) => (
|
||||
<Button
|
||||
key={socialNetworkButton.icon}
|
||||
size="xl"
|
||||
variant="outline"
|
||||
action="primary"
|
||||
>
|
||||
<Feather name={socialNetworkButton.icon} size={27} />
|
||||
</Button>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
<VStack>
|
||||
<HStack className="justify-center items-center" space="xs">
|
||||
<Text>Tu n'as pas encore de compte ?</Text>
|
||||
<Link href="/SigninPage">
|
||||
<LinkText bold={true}>Inscris-toi !</LinkText>
|
||||
</Link>
|
||||
</HStack>
|
||||
<Link className="text-center" href="/ResetPasswordPage">
|
||||
<LinkText bold={true}>Mot de passe oublié ?</LinkText>
|
||||
</Link>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
</Screen>
|
||||
);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import { Text } from "./components/ui/text";
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ButtonIcon,
|
||||
ButtonText,
|
||||
} from "./components/ui/button";
|
||||
import { VStack } from "./components/ui/vstack";
|
||||
import React from "react";
|
||||
import {
|
||||
ArrowRightIcon,
|
||||
LockIcon,
|
||||
MailIcon,
|
||||
MessageCircleIcon,
|
||||
} from "./components/ui/icon";
|
||||
import BackButton from "./components/BackButton";
|
||||
import Screen from "./components/Screen";
|
||||
import { Heading } from "./components/ui/heading";
|
||||
import { Link } from "expo-router";
|
||||
|
||||
const resetButtons: IResetButton[] = [
|
||||
{ icon: MailIcon, text: "Envoyer par email" },
|
||||
{ icon: LockIcon, text: "Envoyer par 2FA" },
|
||||
{ icon: MessageCircleIcon, text: "Envoyer par SMS" },
|
||||
];
|
||||
|
||||
interface IResetButton {
|
||||
icon: React.ElementType;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export default function ResetPasswordPage() {
|
||||
return (
|
||||
<Screen>
|
||||
<BackButton link={"/LoginPage"} />
|
||||
<VStack space="2xl">
|
||||
<VStack space="sm">
|
||||
<Heading className="text-center" size="3xl">
|
||||
Réinitialisation de ton mot de passe
|
||||
</Heading>
|
||||
<Text size="lg" className="text-center">
|
||||
Selectionne une méthode pour recevoir ton code temporaire
|
||||
</Text>
|
||||
</VStack>
|
||||
<ButtonGroup space="xl">
|
||||
{resetButtons.map((resetButton) => (
|
||||
<Link href={"/CodeSentPage"} asChild>
|
||||
<Button
|
||||
className="justify-between"
|
||||
key={resetButton.text}
|
||||
size="2xl"
|
||||
action="primary"
|
||||
variant="outline"
|
||||
>
|
||||
<ButtonIcon as={resetButton.icon} />
|
||||
<ButtonText>{resetButton.text}</ButtonText>
|
||||
<ButtonIcon as={ArrowRightIcon} />
|
||||
</Button>
|
||||
</Link>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
</VStack>
|
||||
</Screen>
|
||||
);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { Text } from "./components/ui/text";
|
||||
import { VStack } from "./components/ui/vstack";
|
||||
import { Box } from "./components/ui/box";
|
||||
import { HStack } from "./components/ui/hstack";
|
||||
import React from "react";
|
||||
import SigninForm from "./components/form/SigninForm";
|
||||
import Screen from "./components/Screen";
|
||||
import { Heading } from "./components/ui/heading";
|
||||
import { Link } from "expo-router";
|
||||
import { LinkText } from "./components/ui/link";
|
||||
|
||||
export default function SigninPage() {
|
||||
return (
|
||||
<Screen>
|
||||
<Box className="h-full justify-center">
|
||||
<VStack space="2xl">
|
||||
<VStack space="sm">
|
||||
<Heading className="text-center" size="2xl">
|
||||
Inscris-toi gratuitement
|
||||
</Heading>
|
||||
<Text size="lg" className="text-center">
|
||||
Génère un programme personnalisé en quelques clics et 1 minute !
|
||||
</Text>
|
||||
</VStack>
|
||||
<SigninForm />
|
||||
<HStack className="justify-center items-center" space="xs">
|
||||
<Text>Tu as déjà un compte ?</Text>
|
||||
<Link href="/LoginPage">
|
||||
<LinkText bold={true}>Connectes-toi !</LinkText>
|
||||
</Link>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
</Screen>
|
||||
);
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import { Slot } from "expo-router";
|
||||
import "../global.css";
|
||||
import {GluestackUIProvider} from "@/app/components/ui/gluestack-ui-provider";
|
||||
import { GluestackUIProvider } from "./components/ui/gluestack-ui-provider";
|
||||
import React from "react";
|
||||
import NavBar from "@/app/components/NavBar";
|
||||
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<GluestackUIProvider>
|
||||
<NavBar/>
|
||||
|
||||
</GluestackUIProvider>
|
||||
);
|
||||
return (
|
||||
<GluestackUIProvider>
|
||||
<NavBar />
|
||||
<Slot />
|
||||
</GluestackUIProvider>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { Button, ButtonActions } from "./ui/button";
|
||||
import { AntDesignIconNames } from "@expo/vector-icons/build/AntDesign";
|
||||
import { Href, Link } from "expo-router";
|
||||
|
||||
type props = {
|
||||
icon?: AntDesignIconNames;
|
||||
link: Href;
|
||||
action?: ButtonActions;
|
||||
};
|
||||
|
||||
export default function BackButton({ icon, link, action }: props) {
|
||||
return (
|
||||
<Button className="h-16 w-16 mb-4" action={action ?? "secondary"}>
|
||||
<Link href={link}>
|
||||
<AntDesign
|
||||
name={icon ?? "arrowleft"}
|
||||
size={30}
|
||||
color={action == "primary" ? "white" : "black"}
|
||||
/>
|
||||
</Link>
|
||||
</Button>
|
||||
);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Box } from "./ui/box";
|
||||
|
||||
type props = PropsWithChildren;
|
||||
|
||||
export default function Screen({ children }: props) {
|
||||
return <Box className={"h-full p-4"}>{children}</Box>;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import {
|
||||
FormControlError,
|
||||
FormControlErrorIcon,
|
||||
FormControlErrorText,
|
||||
} from "../ui/form-control";
|
||||
import { AlertCircleIcon } from "../ui/icon";
|
||||
|
||||
type props = { text: string };
|
||||
|
||||
export default function FormError({ text }: props) {
|
||||
return (
|
||||
<FormControlError className="mb-2 border-[1px] border-red-500 p-4 rounded-3xl bg-red-100 gap-2">
|
||||
<FormControlErrorIcon as={AlertCircleIcon} />
|
||||
<FormControlErrorText className="text-black font-bold">
|
||||
{text}
|
||||
</FormControlErrorText>
|
||||
</FormControlError>
|
||||
);
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import { Button, ButtonText, ButtonIcon } from "../ui/button";
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormControlLabelText,
|
||||
} from "../ui/form-control";
|
||||
import { MailIcon, ArrowRightIcon } from "../ui/icon";
|
||||
import { Input, InputIcon, InputField } from "../ui/input";
|
||||
import { VStack } from "../ui/vstack";
|
||||
import React from "react";
|
||||
import PasswordField from "./PasswordField";
|
||||
import FormError from "./FormError";
|
||||
|
||||
export default function LoginForm() {
|
||||
const REQUIRED_ERROR = "Au moins un des champs requis est vide !";
|
||||
const [emailValue, setEmailValue] = React.useState("");
|
||||
const [isEmailInvalid, setIsEmailInvalid] = React.useState(false);
|
||||
const [passwordValue, setPasswordValue] = React.useState("");
|
||||
const [isPasswordInvalid, setIsPasswordInvalid] = React.useState(false);
|
||||
const [isFormInvalid, setIsFormInvalid] = React.useState(false);
|
||||
const handleSubmit = () => {
|
||||
//check for empty fields
|
||||
setIsEmailInvalid(emailValue == "");
|
||||
setIsPasswordInvalid(passwordValue == "");
|
||||
setIsFormInvalid(isEmailInvalid || isPasswordInvalid);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl className="gap-4" isInvalid={isFormInvalid}>
|
||||
<FormControl isInvalid={isEmailInvalid}>
|
||||
<FormControlLabel>
|
||||
<FormControlLabelText bold={true}>Adresse mail</FormControlLabelText>
|
||||
</FormControlLabel>
|
||||
<Input variant="outline" size="xl">
|
||||
<InputIcon color="black" as={MailIcon} />
|
||||
<InputField
|
||||
value={emailValue}
|
||||
onChangeText={setEmailValue}
|
||||
placeholder="Test@optifit.com"
|
||||
/>
|
||||
</Input>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={isPasswordInvalid}>
|
||||
<FormControlLabel>
|
||||
<FormControlLabelText bold={true}>Mot de passe</FormControlLabelText>
|
||||
</FormControlLabel>
|
||||
<PasswordField value={passwordValue} onChangeText={setPasswordValue} />
|
||||
</FormControl>
|
||||
<VStack>
|
||||
<FormError text={REQUIRED_ERROR} />
|
||||
<Button size="xl" onPress={handleSubmit}>
|
||||
<ButtonText>Se connecter</ButtonText>
|
||||
<ButtonIcon as={ArrowRightIcon} />
|
||||
</Button>
|
||||
</VStack>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import { LockIcon, EyeIcon, EyeOffIcon } from "../ui/icon";
|
||||
import { Input, InputIcon, InputField, InputSlot } from "../ui/input";
|
||||
import { VariantProps } from "@gluestack-ui/nativewind-utils";
|
||||
|
||||
type props = React.ComponentProps<typeof Input> &
|
||||
VariantProps<typeof InputField>;
|
||||
|
||||
export default function PasswordField({ value, onChangeText }: props) {
|
||||
const [showPassword, setShowPassword] = React.useState(false);
|
||||
const handleState = () => {
|
||||
setShowPassword((showState) => {
|
||||
return !showState;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Input variant="outline" size="xl">
|
||||
<InputIcon color="black" as={LockIcon} />
|
||||
<InputField
|
||||
value={value}
|
||||
onChangeText={onChangeText}
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆⋆"
|
||||
/>
|
||||
<InputSlot className="pr-3" onPress={handleState}>
|
||||
<InputIcon as={showPassword ? EyeIcon : EyeOffIcon} />
|
||||
</InputSlot>
|
||||
</Input>
|
||||
);
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
import { Button, ButtonText, ButtonIcon } from "../ui/button";
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormControlLabelText,
|
||||
} from "../ui/form-control";
|
||||
import { MailIcon, ArrowRightIcon } from "../ui/icon";
|
||||
import { Input, InputIcon, InputField } from "../ui/input";
|
||||
import { VStack } from "../ui/vstack";
|
||||
import React from "react";
|
||||
import FormError from "./FormError";
|
||||
import PasswordField from "./PasswordField";
|
||||
|
||||
export default function SigninForm() {
|
||||
const REQUIRED = "Au moins un des champs requis est vide !";
|
||||
const INVALID_EMAIL = "Adresse mail invalide !";
|
||||
// TODO Définir ce qu'est un mdp valide
|
||||
const INVALID_PASSWORD = "Mot de passe invalide !";
|
||||
const NOT_MATCHING_PASSWORD = "Les mots de passe ne correspondent pas !";
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
|
||||
const [emailValue, setEmailValue] = React.useState("");
|
||||
const [isEmailInvalid, setIsEmailInvalid] = React.useState(false);
|
||||
const [passwordValue, setPasswordValue] = React.useState("");
|
||||
const [isPasswordInvalid, setIsPasswordInvalid] = React.useState(false);
|
||||
const [passwordConfirmValue, setPasswordConfirmValue] = React.useState("");
|
||||
const [isPasswordConfirmInvalid, setIsPasswordConfirmInvalid] =
|
||||
React.useState(false);
|
||||
const [isFormInvalid, setIsFormInvalid] = React.useState(false);
|
||||
const [formError, setFormError] = React.useState("");
|
||||
|
||||
function validateForm() {
|
||||
setFormError("");
|
||||
setIsFormInvalid(false);
|
||||
}
|
||||
|
||||
function invalidateForm(error: string) {
|
||||
setFormError(error);
|
||||
setIsFormInvalid(true);
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
//check for valid email block
|
||||
if (emailValue != "") {
|
||||
if (emailRegex.test(emailValue)) {
|
||||
setIsEmailInvalid(false);
|
||||
} else {
|
||||
setIsEmailInvalid(true);
|
||||
invalidateForm(INVALID_EMAIL);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setIsEmailInvalid(true);
|
||||
invalidateForm(REQUIRED);
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO : check for valid password block
|
||||
if (passwordValue != "") {
|
||||
setIsPasswordInvalid(false);
|
||||
} else {
|
||||
setIsPasswordInvalid(true);
|
||||
invalidateForm(REQUIRED);
|
||||
return;
|
||||
}
|
||||
|
||||
//check for valid password confirmation
|
||||
if (passwordConfirmValue != "") {
|
||||
if (passwordConfirmValue == passwordValue) {
|
||||
setIsPasswordConfirmInvalid(false);
|
||||
validateForm();
|
||||
} else {
|
||||
setIsPasswordConfirmInvalid(true);
|
||||
invalidateForm(NOT_MATCHING_PASSWORD);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setIsPasswordConfirmInvalid(true);
|
||||
invalidateForm(REQUIRED);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl className="gap-4" isInvalid={isFormInvalid}>
|
||||
<FormControl isInvalid={isEmailInvalid}>
|
||||
<FormControlLabel>
|
||||
<FormControlLabelText bold={true}>Adresse mail</FormControlLabelText>
|
||||
</FormControlLabel>
|
||||
<Input variant="outline" size="xl">
|
||||
<InputIcon color="black" as={MailIcon} />
|
||||
<InputField
|
||||
value={emailValue}
|
||||
onChangeText={setEmailValue}
|
||||
placeholder="Test@optifit.com"
|
||||
/>
|
||||
</Input>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={isPasswordInvalid}>
|
||||
<FormControlLabel>
|
||||
<FormControlLabelText bold={true}>Mot de passe</FormControlLabelText>
|
||||
</FormControlLabel>
|
||||
<PasswordField value={passwordValue} onChangeText={setPasswordValue} />
|
||||
</FormControl>
|
||||
<FormControl isInvalid={isPasswordConfirmInvalid}>
|
||||
<FormControlLabel>
|
||||
<FormControlLabelText bold={true}>
|
||||
Confirmation du mot de passe
|
||||
</FormControlLabelText>
|
||||
</FormControlLabel>
|
||||
<PasswordField
|
||||
value={passwordConfirmValue}
|
||||
onChangeText={setPasswordConfirmValue}
|
||||
/>
|
||||
</FormControl>
|
||||
<VStack>
|
||||
<FormError text={formError} />
|
||||
<Button size="xl" onPress={handleSubmit}>
|
||||
<ButtonText>S'inscrire</ButtonText>
|
||||
<ButtonIcon as={ArrowRightIcon} />
|
||||
</Button>
|
||||
</VStack>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
|
||||
import { View, ViewProps } from 'react-native';
|
||||
import { cardStyle } from './styles';
|
||||
|
||||
type ICardProps = ViewProps &
|
||||
VariantProps<typeof cardStyle> & { className?: string };
|
||||
|
||||
const Card = React.forwardRef<React.ElementRef<typeof View>, ICardProps>(
|
||||
({ className, size = 'md', variant = 'elevated', ...props }, ref) => {
|
||||
return (
|
||||
<View
|
||||
className={cardStyle({ size, variant, class: className })}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Card.displayName = 'Card';
|
||||
|
||||
export { Card };
|
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { cardStyle } from './styles';
|
||||
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
|
||||
|
||||
type ICardProps = React.ComponentPropsWithoutRef<'div'> &
|
||||
VariantProps<typeof cardStyle>;
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, ICardProps>(
|
||||
({ className, size = 'md', variant = 'elevated', ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
className={cardStyle({ size, variant, class: className })}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Card.displayName = 'Card';
|
||||
|
||||
export { Card };
|
@ -0,0 +1,20 @@
|
||||
import { tva } from '@gluestack-ui/nativewind-utils/tva';
|
||||
import { isWeb } from '@gluestack-ui/nativewind-utils/IsWeb';
|
||||
const baseStyle = isWeb ? 'flex flex-col relative z-0' : '';
|
||||
|
||||
export const cardStyle = tva({
|
||||
base: baseStyle,
|
||||
variants: {
|
||||
size: {
|
||||
sm: 'p-3 rounded',
|
||||
md: 'p-4 rounded-md',
|
||||
lg: 'p-6 rounded-xl',
|
||||
},
|
||||
variant: {
|
||||
elevated: 'bg-background-0',
|
||||
outline: 'border border-outline-200 ',
|
||||
ghost: 'rounded-none',
|
||||
filled: 'bg-background-50',
|
||||
},
|
||||
},
|
||||
});
|
@ -0,0 +1,468 @@
|
||||
'use client';
|
||||
import { Text, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { createFormControl } from '@gluestack-ui/form-control';
|
||||
import { tva } from '@gluestack-ui/nativewind-utils/tva';
|
||||
import {
|
||||
withStyleContext,
|
||||
useStyleContext,
|
||||
} from '@gluestack-ui/nativewind-utils/withStyleContext';
|
||||
import { cssInterop } from 'nativewind';
|
||||
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
|
||||
import { PrimitiveIcon, UIIcon } from '@gluestack-ui/icon';
|
||||
|
||||
const SCOPE = 'FORM_CONTROL';
|
||||
|
||||
const formControlStyle = tva({
|
||||
base: 'flex flex-col',
|
||||
variants: {
|
||||
size: {
|
||||
sm: '',
|
||||
md: '',
|
||||
lg: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const formControlErrorIconStyle = tva({
|
||||
base: 'text-error-700 fill-none',
|
||||
variants: {
|
||||
size: {
|
||||
'2xs': 'h-3 w-3',
|
||||
'xs': 'h-3.5 w-3.5',
|
||||
'sm': 'h-4 w-4',
|
||||
'md': 'h-[18px] w-[18px]',
|
||||
'lg': 'h-5 w-5',
|
||||
'xl': 'h-6 w-6',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const formControlErrorStyle = tva({
|
||||
base: 'flex flex-row justify-start items-center mt-1 gap-1',
|
||||
});
|
||||
|
||||
const formControlErrorTextStyle = tva({
|
||||
base: 'text-error-700',
|
||||
variants: {
|
||||
isTruncated: {
|
||||
true: 'web:truncate',
|
||||
},
|
||||
bold: {
|
||||
true: 'font-bold',
|
||||
},
|
||||
underline: {
|
||||
true: 'underline',
|
||||
},
|
||||
strikeThrough: {
|
||||
true: 'line-through',
|
||||
},
|
||||
size: {
|
||||
'2xs': 'text-2xs',
|
||||
'xs': 'text-xs',
|
||||
'sm': 'text-sm',
|
||||
'md': 'text-base',
|
||||
'lg': 'text-lg',
|
||||
'xl': 'text-xl',
|
||||
'2xl': 'text-2xl',
|
||||
'3xl': 'text-3xl',
|
||||
'4xl': 'text-4xl',
|
||||
'5xl': 'text-5xl',
|
||||
'6xl': 'text-6xl',
|
||||
},
|
||||
sub: {
|
||||
true: 'text-xs',
|
||||
},
|
||||
italic: {
|
||||
true: 'italic',
|
||||
},
|
||||
highlight: {
|
||||
true: 'bg-yellow-500',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const formControlHelperStyle = tva({
|
||||
base: 'flex flex-row justify-start items-center mt-1',
|
||||
});
|
||||
|
||||
const formControlHelperTextStyle = tva({
|
||||
base: 'text-typography-500',
|
||||
variants: {
|
||||
isTruncated: {
|
||||
true: 'web:truncate',
|
||||
},
|
||||
bold: {
|
||||
true: 'font-bold',
|
||||
},
|
||||
underline: {
|
||||
true: 'underline',
|
||||
},
|
||||
strikeThrough: {
|
||||
true: 'line-through',
|
||||
},
|
||||
size: {
|
||||
'2xs': 'text-2xs',
|
||||
'xs': 'text-xs',
|
||||
'sm': 'text-xs',
|
||||
'md': 'text-sm',
|
||||
'lg': 'text-base',
|
||||
'xl': 'text-xl',
|
||||
'2xl': 'text-2xl',
|
||||
'3xl': 'text-3xl',
|
||||
'4xl': 'text-4xl',
|
||||
'5xl': 'text-5xl',
|
||||
'6xl': 'text-6xl',
|
||||
},
|
||||
sub: {
|
||||
true: 'text-xs',
|
||||
},
|
||||
italic: {
|
||||
true: 'italic',
|
||||
},
|
||||
highlight: {
|
||||
true: 'bg-yellow-500',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const formControlLabelStyle = tva({
|
||||
base: 'flex flex-row justify-start items-center mb-1',
|
||||
});
|
||||
|
||||
const formControlLabelTextStyle = tva({
|
||||
base: 'font-medium text-typography-900',
|
||||
variants: {
|
||||
isTruncated: {
|
||||
true: 'web:truncate',
|
||||
},
|
||||
bold: {
|
||||
true: 'font-bold',
|
||||
},
|
||||
underline: {
|
||||
true: 'underline',
|
||||
},
|
||||
strikeThrough: {
|
||||
true: 'line-through',
|
||||
},
|
||||
size: {
|
||||
'2xs': 'text-2xs',
|
||||
'xs': 'text-xs',
|
||||
'sm': 'text-sm',
|
||||
'md': 'text-base',
|
||||
'lg': 'text-lg',
|
||||
'xl': 'text-xl',
|
||||
'2xl': 'text-2xl',
|
||||
'3xl': 'text-3xl',
|
||||
'4xl': 'text-4xl',
|
||||
'5xl': 'text-5xl',
|
||||
'6xl': 'text-6xl',
|
||||
},
|
||||
sub: {
|
||||
true: 'text-xs',
|
||||
},
|
||||
italic: {
|
||||
true: 'italic',
|
||||
},
|
||||
highlight: {
|
||||
true: 'bg-yellow-500',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const formControlLabelAstrickStyle = tva({
|
||||
base: 'font-medium text-typography-900',
|
||||
variants: {
|
||||
isTruncated: {
|
||||
true: 'web:truncate',
|
||||
},
|
||||
bold: {
|
||||
true: 'font-bold',
|
||||
},
|
||||
underline: {
|
||||
true: 'underline',
|
||||
},
|
||||
strikeThrough: {
|
||||
true: 'line-through',
|
||||
},
|
||||
size: {
|
||||
'2xs': 'text-2xs',
|
||||
'xs': 'text-xs',
|
||||
'sm': 'text-sm',
|
||||
'md': 'text-base',
|
||||
'lg': 'text-lg',
|
||||
'xl': 'text-xl',
|
||||
'2xl': 'text-2xl',
|
||||
'3xl': 'text-3xl',
|
||||
'4xl': 'text-4xl',
|
||||
'5xl': 'text-5xl',
|
||||
'6xl': 'text-6xl',
|
||||
},
|
||||
sub: {
|
||||
true: 'text-xs',
|
||||
},
|
||||
italic: {
|
||||
true: 'italic',
|
||||
},
|
||||
highlight: {
|
||||
true: 'bg-yellow-500',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type IFormControlLabelAstrickProps = React.ComponentPropsWithoutRef<
|
||||
typeof Text
|
||||
> &
|
||||
VariantProps<typeof formControlLabelAstrickStyle>;
|
||||
|
||||
const FormControlLabelAstrick = React.forwardRef<
|
||||
React.ElementRef<typeof Text>,
|
||||
IFormControlLabelAstrickProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { size: parentSize } = useStyleContext(SCOPE);
|
||||
|
||||
return (
|
||||
<Text
|
||||
ref={ref}
|
||||
className={formControlLabelAstrickStyle({
|
||||
parentVariants: { size: parentSize },
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const UIFormControl = createFormControl({
|
||||
Root: withStyleContext(View, SCOPE),
|
||||
Error: View,
|
||||
ErrorText: Text,
|
||||
ErrorIcon: UIIcon,
|
||||
Label: View,
|
||||
LabelText: Text,
|
||||
LabelAstrick: FormControlLabelAstrick,
|
||||
Helper: View,
|
||||
HelperText: Text,
|
||||
});
|
||||
|
||||
cssInterop(PrimitiveIcon, {
|
||||
className: {
|
||||
target: 'style',
|
||||
nativeStyleToProp: {
|
||||
height: true,
|
||||
width: true,
|
||||
fill: true,
|
||||
color: true,
|
||||
stroke: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type IFormControlProps = React.ComponentProps<typeof UIFormControl> &
|
||||
VariantProps<typeof formControlStyle>;
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof UIFormControl>,
|
||||
IFormControlProps
|
||||
>(({ className, size = 'md', ...props }, ref) => {
|
||||
return (
|
||||
<UIFormControl
|
||||
ref={ref}
|
||||
className={formControlStyle({ size, class: className })}
|
||||
{...props}
|
||||
context={{ size }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IFormControlErrorProps = React.ComponentProps<typeof UIFormControl.Error> &
|
||||
VariantProps<typeof formControlErrorStyle>;
|
||||
|
||||
const FormControlError = React.forwardRef<
|
||||
React.ElementRef<typeof UIFormControl.Error>,
|
||||
IFormControlErrorProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<UIFormControl.Error
|
||||
ref={ref}
|
||||
className={formControlErrorStyle({ class: className })}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IFormControlErrorTextProps = React.ComponentProps<
|
||||
typeof UIFormControl.Error.Text
|
||||
> &
|
||||
VariantProps<typeof formControlErrorTextStyle>;
|
||||
|
||||
const FormControlErrorText = React.forwardRef<
|
||||
React.ElementRef<typeof UIFormControl.Error.Text>,
|
||||
IFormControlErrorTextProps
|
||||
>(({ className, size, ...props }, ref) => {
|
||||
const { size: parentSize } = useStyleContext(SCOPE);
|
||||
return (
|
||||
<UIFormControl.Error.Text
|
||||
className={formControlErrorTextStyle({
|
||||
parentVariants: { size: parentSize },
|
||||
size,
|
||||
class: className,
|
||||
})}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IFormControlErrorIconProps = React.ComponentProps<
|
||||
typeof UIFormControl.Error.Icon
|
||||
> &
|
||||
VariantProps<typeof formControlErrorIconStyle> & {
|
||||
height?: number;
|
||||
width?: number;
|
||||
};
|
||||
|
||||
const FormControlErrorIcon = React.forwardRef<
|
||||
React.ElementRef<typeof UIFormControl.Error.Icon>,
|
||||
IFormControlErrorIconProps
|
||||
>(({ className, size, ...props }, ref) => {
|
||||
const { size: parentSize } = useStyleContext(SCOPE);
|
||||
|
||||
if (typeof size === 'number') {
|
||||
return (
|
||||
<UIFormControl.Error.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={formControlErrorIconStyle({ class: className })}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
(props.height !== undefined || props.width !== undefined) &&
|
||||
size === undefined
|
||||
) {
|
||||
return (
|
||||
<UIFormControl.Error.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={formControlErrorIconStyle({ class: className })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<UIFormControl.Error.Icon
|
||||
className={formControlErrorIconStyle({
|
||||
parentVariants: { size: parentSize },
|
||||
size,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IFormControlLabelProps = React.ComponentProps<typeof UIFormControl.Label> &
|
||||
VariantProps<typeof formControlLabelStyle>;
|
||||
|
||||
const FormControlLabel = React.forwardRef<
|
||||
React.ElementRef<typeof UIFormControl.Label>,
|
||||
IFormControlLabelProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<UIFormControl.Label
|
||||
ref={ref}
|
||||
className={formControlLabelStyle({ class: className })}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IFormControlLabelTextProps = React.ComponentProps<
|
||||
typeof UIFormControl.Label.Text
|
||||
> &
|
||||
VariantProps<typeof formControlLabelTextStyle>;
|
||||
|
||||
const FormControlLabelText = React.forwardRef<
|
||||
React.ElementRef<typeof UIFormControl.Label.Text>,
|
||||
IFormControlLabelTextProps
|
||||
>(({ className, size, ...props }, ref) => {
|
||||
const { size: parentSize } = useStyleContext(SCOPE);
|
||||
|
||||
return (
|
||||
<UIFormControl.Label.Text
|
||||
className={formControlLabelTextStyle({
|
||||
parentVariants: { size: parentSize },
|
||||
size,
|
||||
class: className,
|
||||
})}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IFormControlHelperProps = React.ComponentProps<
|
||||
typeof UIFormControl.Helper
|
||||
> &
|
||||
VariantProps<typeof formControlHelperStyle>;
|
||||
|
||||
const FormControlHelper = React.forwardRef<
|
||||
React.ElementRef<typeof UIFormControl.Helper>,
|
||||
IFormControlHelperProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<UIFormControl.Helper
|
||||
ref={ref}
|
||||
className={formControlHelperStyle({
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IFormControlHelperTextProps = React.ComponentProps<
|
||||
typeof UIFormControl.Helper.Text
|
||||
> &
|
||||
VariantProps<typeof formControlHelperTextStyle>;
|
||||
|
||||
const FormControlHelperText = React.forwardRef<
|
||||
React.ElementRef<typeof UIFormControl.Helper.Text>,
|
||||
IFormControlHelperTextProps
|
||||
>(({ className, size, ...props }, ref) => {
|
||||
const { size: parentSize } = useStyleContext(SCOPE);
|
||||
|
||||
return (
|
||||
<UIFormControl.Helper.Text
|
||||
className={formControlHelperTextStyle({
|
||||
parentVariants: { size: parentSize },
|
||||
size,
|
||||
class: className,
|
||||
})}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
FormControl.displayName = 'FormControl';
|
||||
FormControlError.displayName = 'FormControlError';
|
||||
FormControlErrorText.displayName = 'FormControlErrorText';
|
||||
FormControlErrorIcon.displayName = 'FormControlErrorIcon';
|
||||
FormControlLabel.displayName = 'FormControlLabel';
|
||||
FormControlLabelText.displayName = 'FormControlLabelText';
|
||||
FormControlLabelAstrick.displayName = 'FormControlLabelAstrick';
|
||||
FormControlHelper.displayName = 'FormControlHelper';
|
||||
FormControlHelperText.displayName = 'FormControlHelperText';
|
||||
|
||||
export {
|
||||
FormControl,
|
||||
FormControlError,
|
||||
FormControlErrorText,
|
||||
FormControlErrorIcon,
|
||||
FormControlLabel,
|
||||
FormControlLabelText,
|
||||
FormControlLabelAstrick,
|
||||
FormControlHelper,
|
||||
FormControlHelperText,
|
||||
};
|
@ -0,0 +1,219 @@
|
||||
import React, { forwardRef, memo } from 'react';
|
||||
import { H1, H2, H3, H4, H5, H6 } from '@expo/html-elements';
|
||||
import { headingStyle } from './styles';
|
||||
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
|
||||
import { cssInterop } from 'nativewind';
|
||||
|
||||
type IHeadingProps = VariantProps<typeof headingStyle> &
|
||||
React.ComponentPropsWithoutRef<typeof H1> & {
|
||||
as?: React.ElementType;
|
||||
};
|
||||
|
||||
cssInterop(H1, { className: 'style' });
|
||||
cssInterop(H2, { className: 'style' });
|
||||
cssInterop(H3, { className: 'style' });
|
||||
cssInterop(H4, { className: 'style' });
|
||||
cssInterop(H5, { className: 'style' });
|
||||
cssInterop(H6, { className: 'style' });
|
||||
|
||||
const MappedHeading = memo(
|
||||
forwardRef<React.ElementRef<typeof H1>, IHeadingProps>(
|
||||
(
|
||||
{
|
||||
size,
|
||||
className,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
switch (size) {
|
||||
case '5xl':
|
||||
case '4xl':
|
||||
case '3xl':
|
||||
return (
|
||||
<H1
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
// @ts-expect-error
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case '2xl':
|
||||
return (
|
||||
<H2
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
// @ts-expect-error
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case 'xl':
|
||||
return (
|
||||
<H3
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
// @ts-expect-error
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case 'lg':
|
||||
return (
|
||||
<H4
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
// @ts-expect-error
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case 'md':
|
||||
return (
|
||||
<H5
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
// @ts-expect-error
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case 'sm':
|
||||
case 'xs':
|
||||
return (
|
||||
<H6
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
// @ts-expect-error
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<H4
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
// @ts-expect-error
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const Heading = memo(
|
||||
forwardRef<React.ElementRef<typeof H1>, IHeadingProps>(
|
||||
({ className, size = 'lg', as: AsComp, ...props }, ref) => {
|
||||
const {
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
} = props;
|
||||
|
||||
if (AsComp) {
|
||||
return (
|
||||
<AsComp
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MappedHeading className={className} size={size} ref={ref} {...props} />
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Heading.displayName = 'Heading';
|
||||
|
||||
export { Heading };
|
@ -0,0 +1,203 @@
|
||||
import React, { forwardRef, memo } from 'react';
|
||||
import { headingStyle } from './styles';
|
||||
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
|
||||
type IHeadingProps = VariantProps<typeof headingStyle> &
|
||||
React.ComponentPropsWithoutRef<'h1'> & {
|
||||
as?: React.ElementType;
|
||||
};
|
||||
|
||||
const MappedHeading = memo(
|
||||
forwardRef<HTMLHeadingElement, IHeadingProps>(
|
||||
(
|
||||
{
|
||||
size,
|
||||
className,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
switch (size) {
|
||||
case '5xl':
|
||||
case '4xl':
|
||||
case '3xl':
|
||||
return (
|
||||
<h1
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case '2xl':
|
||||
return (
|
||||
<h2
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case 'xl':
|
||||
return (
|
||||
<h3
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case 'lg':
|
||||
return (
|
||||
<h4
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case 'md':
|
||||
return (
|
||||
<h5
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
case 'sm':
|
||||
case 'xs':
|
||||
return (
|
||||
<h6
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<h4
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const Heading = memo(
|
||||
forwardRef<HTMLHeadingElement, IHeadingProps>(
|
||||
({ className, size = 'lg', as: AsComp, ...props }, ref) => {
|
||||
const {
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
} = props;
|
||||
|
||||
if (AsComp) {
|
||||
return (
|
||||
<AsComp
|
||||
className={headingStyle({
|
||||
size,
|
||||
isTruncated,
|
||||
bold,
|
||||
underline,
|
||||
strikeThrough,
|
||||
sub,
|
||||
italic,
|
||||
highlight,
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MappedHeading className={className} size={size} ref={ref} {...props} />
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Heading.displayName = 'Heading';
|
||||
|
||||
export { Heading };
|
@ -0,0 +1,43 @@
|
||||
import { tva } from "@gluestack-ui/nativewind-utils/tva";
|
||||
import { isWeb } from "@gluestack-ui/nativewind-utils/IsWeb";
|
||||
const baseStyle = isWeb
|
||||
? "font-sans tracking-sm bg-transparent border-0 box-border display-inline list-none margin-0 padding-0 position-relative text-start no-underline whitespace-pre-wrap word-wrap-break-word"
|
||||
: "";
|
||||
|
||||
export const headingStyle = tva({
|
||||
base: `text-typography-900 font-bold font-heading tracking-sm my-0 ${baseStyle}`,
|
||||
variants: {
|
||||
isTruncated: {
|
||||
true: "truncate",
|
||||
},
|
||||
bold: {
|
||||
true: "font-bold",
|
||||
},
|
||||
underline: {
|
||||
true: "underline",
|
||||
},
|
||||
strikeThrough: {
|
||||
true: "line-through",
|
||||
},
|
||||
sub: {
|
||||
true: "text-xs",
|
||||
},
|
||||
italic: {
|
||||
true: "italic",
|
||||
},
|
||||
highlight: {
|
||||
true: "bg-yellow-500",
|
||||
},
|
||||
size: {
|
||||
"5xl": "text-6xl",
|
||||
"4xl": "text-5xl",
|
||||
"3xl": "text-4xl",
|
||||
"2xl": "text-3xl",
|
||||
xl: "text-2xl",
|
||||
lg: "text-xl",
|
||||
md: "text-lg",
|
||||
sm: "text-base",
|
||||
xs: "text-sm",
|
||||
},
|
||||
},
|
||||
});
|
@ -0,0 +1,214 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import { createInput } from "@gluestack-ui/input";
|
||||
import { View, Pressable, TextInput } from "react-native";
|
||||
import { tva } from "@gluestack-ui/nativewind-utils/tva";
|
||||
import {
|
||||
withStyleContext,
|
||||
useStyleContext,
|
||||
} from "@gluestack-ui/nativewind-utils/withStyleContext";
|
||||
import { cssInterop } from "nativewind";
|
||||
import type { VariantProps } from "@gluestack-ui/nativewind-utils";
|
||||
import { PrimitiveIcon, UIIcon } from "@gluestack-ui/icon";
|
||||
|
||||
const SCOPE = "INPUT";
|
||||
|
||||
const UIInput = createInput({
|
||||
Root: withStyleContext(View, SCOPE),
|
||||
Icon: UIIcon,
|
||||
Slot: Pressable,
|
||||
Input: TextInput,
|
||||
});
|
||||
|
||||
cssInterop(PrimitiveIcon, {
|
||||
className: {
|
||||
target: "style",
|
||||
nativeStyleToProp: {
|
||||
height: true,
|
||||
width: true,
|
||||
fill: true,
|
||||
color: "classNameColor",
|
||||
stroke: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const inputStyle = tva({
|
||||
base: "border-background-300 flex-row overflow-hidden content-center data-[hover=true]:border-outline-400 data-[focus=true]:border-orange-400 data-[focus=true]:hover:border-primary-700 data-[disabled=true]:opacity-40 data-[disabled=true]:hover:border-background-300 items-center",
|
||||
|
||||
variants: {
|
||||
size: {
|
||||
xl: "px-4 h-16",
|
||||
lg: "px-4 h-11",
|
||||
md: "px-4 h-10",
|
||||
sm: "px-4 h-9",
|
||||
},
|
||||
|
||||
variant: {
|
||||
underlined:
|
||||
"rounded-none border-b data-[invalid=true]:border-b-2 data-[invalid=true]:border-error-700 data-[invalid=true]:hover:border-error-700 data-[invalid=true]:data-[focus=true]:border-error-700 data-[invalid=true]:data-[focus=true]:hover:border-error-700 data-[invalid=true]:data-[disabled=true]:hover:border-error-700",
|
||||
|
||||
outline:
|
||||
"rounded-3xl border data-[invalid=true]:border-error-700 data-[invalid=true]:hover:border-error-700 data-[invalid=true]:data-[focus=true]:border-error-700 data-[invalid=true]:data-[focus=true]:hover:border-error-700 data-[invalid=true]:data-[disabled=true]:hover:border-error-700 data-[focus=true]:web:ring-1 data-[focus=true]:web:ring-inset data-[focus=true]:web:ring-indicator-primary data-[invalid=true]:web:ring-1 data-[invalid=true]:web:ring-inset data-[invalid=true]:web:ring-indicator-error data-[invalid=true]:data-[focus=true]:hover:web:ring-1 data-[invalid=true]:data-[focus=true]:hover:web:ring-inset data-[invalid=true]:data-[focus=true]:hover:web:ring-indicator-error data-[invalid=true]:data-[disabled=true]:hover:web:ring-1 data-[invalid=true]:data-[disabled=true]:hover:web:ring-inset data-[invalid=true]:data-[disabled=true]:hover:web:ring-indicator-error",
|
||||
|
||||
rounded:
|
||||
"rounded-full border data-[invalid=true]:border-error-700 data-[invalid=true]:hover:border-error-700 data-[invalid=true]:data-[focus=true]:border-error-700 data-[invalid=true]:data-[focus=true]:hover:border-error-700 data-[invalid=true]:data-[disabled=true]:hover:border-error-700 data-[focus=true]:web:ring-1 data-[focus=true]:web:ring-inset data-[focus=true]:web:ring-indicator-primary data-[invalid=true]:web:ring-1 data-[invalid=true]:web:ring-inset data-[invalid=true]:web:ring-indicator-error data-[invalid=true]:data-[focus=true]:hover:web:ring-1 data-[invalid=true]:data-[focus=true]:hover:web:ring-inset data-[invalid=true]:data-[focus=true]:hover:web:ring-indicator-error data-[invalid=true]:data-[disabled=true]:hover:web:ring-1 data-[invalid=true]:data-[disabled=true]:hover:web:ring-inset data-[invalid=true]:data-[disabled=true]:hover:web:ring-indicator-error",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const inputIconStyle = tva({
|
||||
base: "justify-center items-center text-typography-400 fill-none",
|
||||
parentVariants: {
|
||||
size: {
|
||||
"2xs": "h-3 w-3",
|
||||
xs: "h-3.5 w-3.5",
|
||||
sm: "h-4 w-4",
|
||||
md: "h-[18px] w-[18px]",
|
||||
lg: "h-5 w-5",
|
||||
xl: "h-6 w-6",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const inputSlotStyle = tva({
|
||||
base: "justify-center items-center web:disabled:cursor-not-allowed",
|
||||
});
|
||||
|
||||
const inputFieldStyle = tva({
|
||||
base: "flex-1 text-typography-900 py-0 px-3 placeholder:text-typography-500 h-full ios:leading-[0px] web:cursor-text web:data-[disabled=true]:cursor-not-allowed",
|
||||
|
||||
parentVariants: {
|
||||
variant: {
|
||||
underlined: "web:outline-0 web:outline-none px-0",
|
||||
outline: "web:outline-0 web:outline-none",
|
||||
rounded: "web:outline-0 web:outline-none px-4",
|
||||
},
|
||||
|
||||
size: {
|
||||
"2xs": "text-2xs",
|
||||
xs: "text-xs",
|
||||
sm: "text-sm",
|
||||
md: "text-base",
|
||||
lg: "text-lg",
|
||||
xl: "text-xl",
|
||||
"2xl": "text-2xl",
|
||||
"3xl": "text-3xl",
|
||||
"4xl": "text-4xl",
|
||||
"5xl": "text-5xl",
|
||||
"6xl": "text-6xl",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type IInputProps = React.ComponentProps<typeof UIInput> &
|
||||
VariantProps<typeof inputStyle> & { className?: string };
|
||||
const Input = React.forwardRef<React.ElementRef<typeof UIInput>, IInputProps>(
|
||||
({ className, variant = "outline", size = "md", ...props }, ref) => {
|
||||
return (
|
||||
<UIInput
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={inputStyle({ variant, size, class: className })}
|
||||
context={{ variant, size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
type IInputIconProps = React.ComponentProps<typeof UIInput.Icon> &
|
||||
VariantProps<typeof inputIconStyle> & {
|
||||
className?: string;
|
||||
height?: number;
|
||||
width?: number;
|
||||
};
|
||||
|
||||
const InputIcon = React.forwardRef<
|
||||
React.ElementRef<typeof UIInput.Icon>,
|
||||
IInputIconProps
|
||||
>(({ className, size, ...props }, ref) => {
|
||||
const { size: parentSize } = useStyleContext(SCOPE);
|
||||
|
||||
if (typeof size === "number") {
|
||||
return (
|
||||
<UIInput.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={inputIconStyle({ class: className })}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
(props.height !== undefined || props.width !== undefined) &&
|
||||
size === undefined
|
||||
) {
|
||||
return (
|
||||
<UIInput.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={inputIconStyle({ class: className })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<UIInput.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={inputIconStyle({
|
||||
parentVariants: {
|
||||
size: parentSize,
|
||||
},
|
||||
class: className,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IInputSlotProps = React.ComponentProps<typeof UIInput.Slot> &
|
||||
VariantProps<typeof inputSlotStyle> & { className?: string };
|
||||
|
||||
const InputSlot = React.forwardRef<
|
||||
React.ElementRef<typeof UIInput.Slot>,
|
||||
IInputSlotProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<UIInput.Slot
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={inputSlotStyle({
|
||||
class: className,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IInputFieldProps = React.ComponentProps<typeof UIInput.Input> &
|
||||
VariantProps<typeof inputFieldStyle> & { className?: string };
|
||||
|
||||
const InputField = React.forwardRef<
|
||||
React.ElementRef<typeof UIInput.Input>,
|
||||
IInputFieldProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { variant: parentVariant, size: parentSize } = useStyleContext(SCOPE);
|
||||
|
||||
return (
|
||||
<UIInput.Input
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={inputFieldStyle({
|
||||
parentVariants: {
|
||||
variant: parentVariant,
|
||||
size: parentSize,
|
||||
},
|
||||
class: className,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Input.displayName = "Input";
|
||||
InputIcon.displayName = "InputIcon";
|
||||
InputSlot.displayName = "InputSlot";
|
||||
InputField.displayName = "InputField";
|
||||
|
||||
export { Input, InputField, InputIcon, InputSlot };
|
@ -0,0 +1,102 @@
|
||||
"use client";
|
||||
import { createLink } from "@gluestack-ui/link";
|
||||
import { Pressable } from "react-native";
|
||||
import { Text } from "react-native";
|
||||
|
||||
import { tva } from "@gluestack-ui/nativewind-utils/tva";
|
||||
import { withStyleContext } from "@gluestack-ui/nativewind-utils/withStyleContext";
|
||||
import { cssInterop } from "nativewind";
|
||||
import type { VariantProps } from "@gluestack-ui/nativewind-utils";
|
||||
|
||||
import React from "react";
|
||||
export const UILink = createLink({
|
||||
Root: withStyleContext(Pressable),
|
||||
Text: Text,
|
||||
});
|
||||
|
||||
cssInterop(UILink, { className: "style" });
|
||||
cssInterop(UILink.Text, { className: "style" });
|
||||
|
||||
const linkStyle = tva({
|
||||
base: "group/link web:outline-0 data-[disabled=true]:web:cursor-not-allowed data-[focus-visible=true]:web:ring-2 data-[focus-visible=true]:web:ring-indicator-primary data-[focus-visible=true]:web:outline-0 data-[disabled=true]:opacity-4 ",
|
||||
});
|
||||
|
||||
const linkTextStyle = tva({
|
||||
base: "underline text-orange-400 data-[hover=true]:text-orange-400 data-[hover=true]:no-underline data-[active=true]:text-orange-400 font-normal font-body web:font-sans web:tracking-sm web:my-0 web:bg-transparent web:border-0 web:box-border web:display-inline web:list-none web:margin-0 web:padding-0 web:position-relative web:text-start web:whitespace-pre-wrap web:word-wrap-break-word",
|
||||
|
||||
variants: {
|
||||
isTruncated: {
|
||||
true: "web:truncate",
|
||||
},
|
||||
bold: {
|
||||
true: "font-bold",
|
||||
},
|
||||
underline: {
|
||||
true: "underline",
|
||||
},
|
||||
strikeThrough: {
|
||||
true: "line-through",
|
||||
},
|
||||
size: {
|
||||
"2xs": "text-2xs",
|
||||
xs: "text-xs",
|
||||
sm: "text-sm",
|
||||
md: "text-base",
|
||||
lg: "text-lg",
|
||||
xl: "text-xl",
|
||||
"2xl": "text-2xl",
|
||||
"3xl": "text-3xl",
|
||||
"4xl": "text-4xl",
|
||||
"5xl": "text-5xl",
|
||||
"6xl": "text-6xl",
|
||||
},
|
||||
sub: {
|
||||
true: "text-xs",
|
||||
},
|
||||
italic: {
|
||||
true: "italic",
|
||||
},
|
||||
highlight: {
|
||||
true: "bg-yellow-500",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type ILinkProps = React.ComponentProps<typeof UILink> &
|
||||
VariantProps<typeof linkStyle> & { className?: string };
|
||||
|
||||
const Link = React.forwardRef<React.ElementRef<typeof UILink>, ILinkProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<UILink
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={linkStyle({ class: className })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
type ILinkTextProps = React.ComponentProps<typeof UILink.Text> &
|
||||
VariantProps<typeof linkTextStyle> & { className?: string };
|
||||
|
||||
const LinkText = React.forwardRef<
|
||||
React.ElementRef<typeof UILink.Text>,
|
||||
ILinkTextProps
|
||||
>(({ className, size = "md", ...props }, ref) => {
|
||||
return (
|
||||
<UILink.Text
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={linkTextStyle({
|
||||
class: className,
|
||||
size,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Link.displayName = "Link";
|
||||
LinkText.displayName = "LinkText";
|
||||
|
||||
export { Link, LinkText };
|
@ -1,21 +1,12 @@
|
||||
import {SafeAreaView, Text, View} from "react-native";
|
||||
import Navigation from "@/src/navigation/navigation";
|
||||
import HomeScreen from "@/app/HomeScreen";
|
||||
import { SafeAreaView, View } from "react-native";
|
||||
import LoginPage from "./LoginPage";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<View>
|
||||
<App/>
|
||||
<SafeAreaView>
|
||||
<LoginPage />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<View>
|
||||
<SafeAreaView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 277 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB |
@ -0,0 +1,8 @@
|
||||
import * as React from "react";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
const Muscle = (props: SvgProps) => (
|
||||
<Svg width="800px" height="800px" viewBox="0 0 24 24" {...props}>
|
||||
<Path d="M9.14,16.77S8,13.17,10.09,11A14.12,14.12,0,0,1,13,9.13a4.78,4.78,0,1,1,5.61,4.7c-1.83,2.77-5.83,7.71-11.33,7.71C4.36,21.54,1.5,13,1.5,9.13V4.48A2.26,2.26,0,0,1,3.64,2.23c1.73-.09,4,0,4.54,1.17C9,5.11,7.23,8.18,5.32,8.18c0,1.5,1.83,4.76,3.49,6.56" />
|
||||
</Svg>
|
||||
);
|
||||
export default Muscle;
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue