diff --git a/expo.project/.npmrc b/expo.project/.npmrc old mode 100644 new mode 100755 index 0e5e9ca..521a9f7 --- a/expo.project/.npmrc +++ b/expo.project/.npmrc @@ -1 +1 @@ -legacy-peer-deps=true +legacy-peer-deps=true diff --git a/expo.project/components/ui/box/index.tsx b/expo.project/components/ui/box/index.tsx new file mode 100755 index 0000000..f6f241f --- /dev/null +++ b/expo.project/components/ui/box/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { View, ViewProps } from 'react-native'; + +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; +import { boxStyle } from './styles'; + +type IBoxProps = ViewProps & + VariantProps & { className?: string }; + +const Box = React.forwardRef, IBoxProps>( + function Box({ className, ...props }, ref) { + return ( + + ); + } +); + +Box.displayName = 'Box'; +export { Box }; diff --git a/expo.project/components/ui/box/index.web.tsx b/expo.project/components/ui/box/index.web.tsx new file mode 100755 index 0000000..eb39059 --- /dev/null +++ b/expo.project/components/ui/box/index.web.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { boxStyle } from './styles'; + +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; + +type IBoxProps = React.ComponentPropsWithoutRef<'div'> & + VariantProps & { className?: string }; + +const Box = React.forwardRef(function Box( + { className, ...props }, + ref +) { + return ( +
+ ); +}); + +Box.displayName = 'Box'; +export { Box }; diff --git a/expo.project/components/ui/box/styles.tsx b/expo.project/components/ui/box/styles.tsx new file mode 100755 index 0000000..33d9df3 --- /dev/null +++ b/expo.project/components/ui/box/styles.tsx @@ -0,0 +1,10 @@ +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import { isWeb } from '@gluestack-ui/utils/nativewind-utils'; + +const baseStyle = isWeb + ? 'flex flex-col relative z-0 box-border border-0 list-none min-w-0 min-h-0 bg-transparent items-stretch m-0 p-0 text-decoration-none' + : ''; + +export const boxStyle = tva({ + base: baseStyle, +}); diff --git a/expo.project/components/ui/button/index.tsx b/expo.project/components/ui/button/index.tsx new file mode 100755 index 0000000..0310e63 --- /dev/null +++ b/expo.project/components/ui/button/index.tsx @@ -0,0 +1,439 @@ +'use client'; +import React from 'react'; +import { createButton } from '@gluestack-ui/core/button/creator'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import { + withStyleContext, + useStyleContext, +} from '@gluestack-ui/utils/nativewind-utils'; +import { cssInterop } from 'nativewind'; +import { ActivityIndicator, Pressable, Text, View } from 'react-native'; +import type { VariantProps } from 'tailwind-variants'; +import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator'; + +const SCOPE = 'BUTTON'; + +const Root = withStyleContext(Pressable, SCOPE); + +const UIButton = createButton({ + Root: Root, + Text, + Group: View, + Spinner: ActivityIndicator, + Icon: UIIcon, +}); + +cssInterop(PrimitiveIcon, { + className: { + target: 'style', + nativeStyleToProp: { + height: true, + width: true, + fill: true, + color: 'classNameColor', + stroke: true, + }, + }, +}); + +const buttonStyle = tva({ + base: 'group/button rounded bg-primary-500 flex-row items-center justify-center data-[focus-visible=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[disabled=true]:opacity-40 gap-2', + variants: { + action: { + primary: + 'bg-primary-500 data-[hover=true]:bg-primary-600 data-[active=true]:bg-primary-700 border-primary-300 data-[hover=true]:border-primary-400 data-[active=true]:border-primary-500 data-[focus-visible=true]:web:ring-indicator-info', + secondary: + 'bg-secondary-500 border-secondary-300 data-[hover=true]:bg-secondary-600 data-[hover=true]:border-secondary-400 data-[active=true]:bg-secondary-700 data-[active=true]:border-secondary-700 data-[focus-visible=true]:web:ring-indicator-info', + positive: + 'bg-success-500 border-success-300 data-[hover=true]:bg-success-600 data-[hover=true]:border-success-400 data-[active=true]:bg-success-700 data-[active=true]:border-success-500 data-[focus-visible=true]:web:ring-indicator-info', + negative: + 'bg-error-500 border-error-300 data-[hover=true]:bg-error-600 data-[hover=true]:border-error-400 data-[active=true]:bg-error-700 data-[active=true]:border-error-500 data-[focus-visible=true]:web:ring-indicator-info', + default: + 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent', + }, + variant: { + link: 'px-0', + outline: + 'bg-transparent border data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent', + solid: '', + }, + + size: { + xs: 'px-3.5 h-8', + sm: 'px-4 h-9', + md: 'px-5 h-10', + lg: 'px-6 h-11', + xl: 'px-7 h-12', + }, + }, + compoundVariants: [ + { + action: 'primary', + variant: 'link', + class: + 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent', + }, + { + action: 'secondary', + variant: 'link', + class: + 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent', + }, + { + action: 'positive', + variant: 'link', + class: + 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent', + }, + { + action: 'negative', + variant: 'link', + class: + 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent', + }, + { + action: 'primary', + variant: 'outline', + class: + 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent', + }, + { + action: 'secondary', + variant: 'outline', + class: + 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent', + }, + { + action: 'positive', + variant: 'outline', + class: + 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent', + }, + { + action: 'negative', + variant: 'outline', + class: + 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent', + }, + ], +}); + +const buttonTextStyle = tva({ + base: 'text-typography-0 font-semibold web:select-none', + parentVariants: { + action: { + primary: + 'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700', + secondary: + 'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700', + positive: + 'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700', + negative: + 'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700', + }, + variant: { + link: 'data-[hover=true]:underline data-[active=true]:underline', + outline: '', + solid: + 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0', + }, + size: { + xs: 'text-xs', + sm: 'text-sm', + md: 'text-base', + lg: 'text-lg', + xl: 'text-xl', + }, + }, + parentCompoundVariants: [ + { + variant: 'solid', + action: 'primary', + class: + 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0', + }, + { + variant: 'solid', + action: 'secondary', + class: + 'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800', + }, + { + variant: 'solid', + action: 'positive', + class: + 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0', + }, + { + variant: 'solid', + action: 'negative', + class: + 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0', + }, + { + variant: 'outline', + action: 'primary', + class: + 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500', + }, + { + variant: 'outline', + action: 'secondary', + class: + 'text-typography-500 data-[hover=true]:text-primary-600 data-[active=true]:text-typography-700', + }, + { + variant: 'outline', + action: 'positive', + class: + 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500', + }, + { + variant: 'outline', + action: 'negative', + class: + 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500', + }, + ], +}); + +const buttonIconStyle = tva({ + base: 'fill-none', + parentVariants: { + variant: { + link: 'data-[hover=true]:underline data-[active=true]:underline', + outline: '', + solid: + 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0', + }, + size: { + xs: 'h-3.5 w-3.5', + sm: 'h-4 w-4', + md: 'h-[18px] w-[18px]', + lg: 'h-[18px] w-[18px]', + xl: 'h-5 w-5', + }, + action: { + primary: + 'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700', + secondary: + 'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700', + positive: + 'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700', + + negative: + 'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700', + }, + }, + parentCompoundVariants: [ + { + variant: 'solid', + action: 'primary', + class: + 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0', + }, + { + variant: 'solid', + action: 'secondary', + class: + 'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800', + }, + { + variant: 'solid', + action: 'positive', + class: + 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0', + }, + { + variant: 'solid', + action: 'negative', + class: + 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0', + }, + ], +}); + +const buttonGroupStyle = tva({ + base: '', + variants: { + space: { + 'xs': 'gap-1', + 'sm': 'gap-2', + 'md': 'gap-3', + 'lg': 'gap-4', + 'xl': 'gap-5', + '2xl': 'gap-6', + '3xl': 'gap-7', + '4xl': 'gap-8', + }, + isAttached: { + true: 'gap-0', + }, + flexDirection: { + 'row': 'flex-row', + 'column': 'flex-col', + 'row-reverse': 'flex-row-reverse', + 'column-reverse': 'flex-col-reverse', + }, + }, +}); + +type IButtonProps = Omit< + React.ComponentPropsWithoutRef, + 'context' +> & + VariantProps & { className?: string }; + +const Button = React.forwardRef< + React.ElementRef, + IButtonProps +>( + ( + { className, variant = 'solid', size = 'md', action = 'primary', ...props }, + ref + ) => { + return ( + + ); + } +); + +type IButtonTextProps = React.ComponentPropsWithoutRef & + VariantProps & { className?: string }; + +const ButtonText = React.forwardRef< + React.ElementRef, + IButtonTextProps +>(({ className, variant, size, action, ...props }, ref) => { + const { + variant: parentVariant, + size: parentSize, + action: parentAction, + } = useStyleContext(SCOPE); + + return ( + + ); +}); + +const ButtonSpinner = UIButton.Spinner; + +type IButtonIcon = React.ComponentPropsWithoutRef & + VariantProps & { + className?: string | undefined; + as?: React.ElementType; + height?: number; + width?: number; + }; + +const ButtonIcon = React.forwardRef< + React.ElementRef, + IButtonIcon +>(({ className, size, ...props }, ref) => { + const { + variant: parentVariant, + size: parentSize, + action: parentAction, + } = useStyleContext(SCOPE); + + if (typeof size === 'number') { + return ( + + ); + } else if ( + (props.height !== undefined || props.width !== undefined) && + size === undefined + ) { + return ( + + ); + } + return ( + + ); +}); + +type IButtonGroupProps = React.ComponentPropsWithoutRef & + VariantProps; + +const ButtonGroup = React.forwardRef< + React.ElementRef, + IButtonGroupProps +>( + ( + { + className, + space = 'md', + isAttached = false, + flexDirection = 'column', + ...props + }, + ref + ) => { + return ( + + ); + } +); + +Button.displayName = 'Button'; +ButtonText.displayName = 'ButtonText'; +ButtonSpinner.displayName = 'ButtonSpinner'; +ButtonIcon.displayName = 'ButtonIcon'; +ButtonGroup.displayName = 'ButtonGroup'; + +export { Button, ButtonText, ButtonSpinner, ButtonIcon, ButtonGroup }; diff --git a/expo.project/components/ui/hstack/index.tsx b/expo.project/components/ui/hstack/index.tsx new file mode 100755 index 0000000..725dabb --- /dev/null +++ b/expo.project/components/ui/hstack/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; +import { View } from 'react-native'; +import type { ViewProps } from 'react-native'; +import { hstackStyle } from './styles'; + +type IHStackProps = ViewProps & VariantProps; + +const HStack = React.forwardRef, IHStackProps>( + function HStack({ className, space, reversed, ...props }, ref) { + return ( + + ); + } +); + +HStack.displayName = 'HStack'; + +export { HStack }; diff --git a/expo.project/components/ui/hstack/index.web.tsx b/expo.project/components/ui/hstack/index.web.tsx new file mode 100755 index 0000000..39f1e40 --- /dev/null +++ b/expo.project/components/ui/hstack/index.web.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; +import { hstackStyle } from './styles'; + +type IHStackProps = React.ComponentPropsWithoutRef<'div'> & + VariantProps; + +const HStack = React.forwardRef, IHStackProps>( + function HStack({ className, space, reversed, ...props }, ref) { + return ( +
+ ); + } +); + +HStack.displayName = 'HStack'; + +export { HStack }; diff --git a/expo.project/components/ui/hstack/styles.tsx b/expo.project/components/ui/hstack/styles.tsx new file mode 100755 index 0000000..a070e45 --- /dev/null +++ b/expo.project/components/ui/hstack/styles.tsx @@ -0,0 +1,25 @@ +import { isWeb } from '@gluestack-ui/utils/nativewind-utils'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; + +const baseStyle = isWeb + ? 'flex relative z-0 box-border border-0 list-none min-w-0 min-h-0 bg-transparent items-stretch m-0 p-0 text-decoration-none' + : ''; + +export const hstackStyle = tva({ + base: `flex-row ${baseStyle}`, + variants: { + space: { + 'xs': 'gap-1', + 'sm': 'gap-2', + 'md': 'gap-3', + 'lg': 'gap-4', + 'xl': 'gap-5', + '2xl': 'gap-6', + '3xl': 'gap-7', + '4xl': 'gap-8', + }, + reversed: { + true: 'flex-row-reverse', + }, + }, +}); diff --git a/expo.project/components/ui/image/index.tsx b/expo.project/components/ui/image/index.tsx new file mode 100755 index 0000000..ef95f5d --- /dev/null +++ b/expo.project/components/ui/image/index.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { createImage } from '@gluestack-ui/core/image/creator'; +import { Platform, Image as RNImage } from 'react-native'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; + +const imageStyle = tva({ + base: 'max-w-full', + variants: { + size: { + '2xs': 'h-6 w-6', + 'xs': 'h-10 w-10', + 'sm': 'h-16 w-16', + 'md': 'h-20 w-20', + 'lg': 'h-24 w-24', + 'xl': 'h-32 w-32', + '2xl': 'h-64 w-64', + 'full': 'h-full w-full', + 'none': '', + }, + }, +}); + +const UIImage = createImage({ Root: RNImage }); + +type ImageProps = VariantProps & + React.ComponentProps; +const Image = React.forwardRef< + React.ComponentRef, + ImageProps & { className?: string } +>(function Image({ size = 'md', className, ...props }, ref) { + return ( + + ); +}); + +Image.displayName = 'Image'; +export { Image }; diff --git a/expo.project/components/ui/input/index.tsx b/expo.project/components/ui/input/index.tsx new file mode 100755 index 0000000..c1fbd94 --- /dev/null +++ b/expo.project/components/ui/input/index.tsx @@ -0,0 +1,217 @@ +'use client'; +import React from 'react'; +import { createInput } from '@gluestack-ui/core/input/creator'; +import { View, Pressable, TextInput } from 'react-native'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import { + withStyleContext, + useStyleContext, +} from '@gluestack-ui/utils/nativewind-utils'; +import { cssInterop } from 'nativewind'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; +import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator'; + +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-primary-700 data-[focus=true]:hover:border-primary-700 data-[disabled=true]:opacity-40 data-[disabled=true]:hover:border-background-300 items-center', + + variants: { + size: { + xl: 'h-12', + lg: 'h-11', + md: 'h-10', + sm: '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 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 & + VariantProps & { className?: string }; +const Input = React.forwardRef, IInputProps>( + function Input( + { className, variant = 'outline', size = 'md', ...props }, + ref + ) { + return ( + + ); + } +); + +type IInputIconProps = React.ComponentProps & + VariantProps & { + className?: string; + height?: number; + width?: number; + }; + +const InputIcon = React.forwardRef< + React.ComponentRef, + IInputIconProps +>(function InputIcon({ className, size, ...props }, ref) { + const { size: parentSize } = useStyleContext(SCOPE); + + if (typeof size === 'number') { + return ( + + ); + } else if ( + (props.height !== undefined || props.width !== undefined) && + size === undefined + ) { + return ( + + ); + } + return ( + + ); +}); + +type IInputSlotProps = React.ComponentProps & + VariantProps & { className?: string }; + +const InputSlot = React.forwardRef< + React.ComponentRef, + IInputSlotProps +>(function InputSlot({ className, ...props }, ref) { + return ( + + ); +}); + +type IInputFieldProps = React.ComponentProps & + VariantProps & { className?: string }; + +const InputField = React.forwardRef< + React.ComponentRef, + IInputFieldProps +>(function InputField({ className, ...props }, ref) { + const { variant: parentVariant, size: parentSize } = useStyleContext(SCOPE); + + return ( + + ); +}); + +Input.displayName = 'Input'; +InputIcon.displayName = 'InputIcon'; +InputSlot.displayName = 'InputSlot'; +InputField.displayName = 'InputField'; + +export { Input, InputField, InputIcon, InputSlot }; diff --git a/expo.project/components/ui/spinner/index.tsx b/expo.project/components/ui/spinner/index.tsx new file mode 100755 index 0000000..50d5e27 --- /dev/null +++ b/expo.project/components/ui/spinner/index.tsx @@ -0,0 +1,40 @@ +'use client'; +import { ActivityIndicator } from 'react-native'; +import React from 'react'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import { cssInterop } from 'nativewind'; + +cssInterop(ActivityIndicator, { + className: { target: 'style', nativeStyleToProp: { color: true } }, +}); + +const spinnerStyle = tva({}); + +const Spinner = React.forwardRef< + React.ComponentRef, + React.ComponentProps +>(function Spinner( + { + className, + color, + focusable = false, + 'aria-label': ariaLabel = 'loading', + ...props + }, + ref +) { + return ( + + ); +}); + +Spinner.displayName = 'Spinner'; + +export { Spinner }; diff --git a/expo.project/components/ui/text/index.tsx b/expo.project/components/ui/text/index.tsx new file mode 100755 index 0000000..c3c7cf2 --- /dev/null +++ b/expo.project/components/ui/text/index.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; +import { Text as RNText } from 'react-native'; +import { textStyle } from './styles'; + +type ITextProps = React.ComponentProps & + VariantProps; + +const Text = React.forwardRef, ITextProps>( + function Text( + { + className, + isTruncated, + bold, + underline, + strikeThrough, + size = 'md', + sub, + italic, + highlight, + ...props + }, + ref + ) { + return ( + + ); + } +); + +Text.displayName = 'Text'; + +export { Text }; diff --git a/expo.project/components/ui/text/index.web.tsx b/expo.project/components/ui/text/index.web.tsx new file mode 100755 index 0000000..0d008bc --- /dev/null +++ b/expo.project/components/ui/text/index.web.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; +import { textStyle } from './styles'; + +type ITextProps = React.ComponentProps<'span'> & VariantProps; + +const Text = React.forwardRef, ITextProps>( + function Text( + { + className, + isTruncated, + bold, + underline, + strikeThrough, + size = 'md', + sub, + italic, + highlight, + ...props + }: { className?: string } & ITextProps, + ref + ) { + return ( + + ); + } +); + +Text.displayName = 'Text'; + +export { Text }; diff --git a/expo.project/components/ui/text/styles.tsx b/expo.project/components/ui/text/styles.tsx new file mode 100755 index 0000000..4db83d1 --- /dev/null +++ b/expo.project/components/ui/text/styles.tsx @@ -0,0 +1,47 @@ +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import { isWeb } from '@gluestack-ui/utils/nativewind-utils'; + +const baseStyle = isWeb + ? 'font-sans tracking-sm my-0 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 textStyle = tva({ + base: `text-typography-700 font-body ${baseStyle}`, + + 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', + }, + }, +}); diff --git a/expo.project/components/ui/textarea/index.tsx b/expo.project/components/ui/textarea/index.tsx new file mode 100755 index 0000000..a87c313 --- /dev/null +++ b/expo.project/components/ui/textarea/index.tsx @@ -0,0 +1,94 @@ +'use client'; +import React from 'react'; +import { createTextarea } from '@gluestack-ui/core/textarea/creator'; +import { View, TextInput } from 'react-native'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import { + withStyleContext, + useStyleContext, +} from '@gluestack-ui/utils/nativewind-utils'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; + +const SCOPE = 'TEXTAREA'; +const UITextarea = createTextarea({ + Root: withStyleContext(View, SCOPE), + Input: TextInput, +}); + +const textareaStyle = tva({ + base: 'w-full h-[100px] border border-background-300 rounded data-[hover=true]:border-outline-400 data-[focus=true]:border-primary-700 data-[focus=true]:data-[hover=true]:border-primary-700 data-[disabled=true]:opacity-40 data-[disabled=true]:bg-background-50 data-[disabled=true]:data-[hover=true]:border-background-300', + + variants: { + variant: { + default: + 'data-[focus=true]:border-primary-700 data-[focus=true]:web:ring-1 data-[focus=true]:web:ring-inset data-[focus=true]:web:ring-indicator-primary data-[invalid=true]:border-error-700 data-[invalid=true]:web:ring-1 data-[invalid=true]:web:ring-inset data-[invalid=true]:web:ring-indicator-error data-[invalid=true]:data-[hover=true]:border-error-700 data-[invalid=true]:data-[focus=true]:data-[hover=true]:border-primary-700 data-[invalid=true]:data-[focus=true]:data-[hover=true]:web:ring-1 data-[invalid=true]:data-[focus=true]:data-[hover=true]:web:ring-inset data-[invalid=true]:data-[focus=true]:data-[hover=true]:web:ring-indicator-primary data-[invalid=true]:data-[disabled=true]:data-[hover=true]:border-error-700 data-[invalid=true]:data-[disabled=true]:data-[hover=true]:web:ring-1 data-[invalid=true]:data-[disabled=true]:data-[hover=true]:web:ring-inset data-[invalid=true]:data-[disabled=true]:data-[hover=true]:web:ring-indicator-error ', + }, + size: { + sm: '', + md: '', + lg: '', + xl: '', + }, + }, +}); + +const textareaInputStyle = tva({ + base: 'p-2 web:outline-0 web:outline-none flex-1 color-typography-900 placeholder:text-typography-500 web:cursor-text web:data-[disabled=true]:cursor-not-allowed', + parentVariants: { + size: { + sm: 'text-sm', + md: 'text-base', + lg: 'text-lg', + xl: 'text-xl', + }, + }, +}); + +type ITextareaProps = React.ComponentProps & + VariantProps; + +const Textarea = React.forwardRef< + React.ComponentRef, + ITextareaProps +>(function Textarea( + { className, variant = 'default', size = 'md', ...props }, + ref +) { + return ( + + ); +}); + +type ITextareaInputProps = React.ComponentProps & + VariantProps; + +const TextareaInput = React.forwardRef< + React.ComponentRef, + ITextareaInputProps +>(function TextareaInput({ className, ...props }, ref) { + const { size: parentSize } = useStyleContext(SCOPE); + + return ( + + ); +}); + +Textarea.displayName = 'Textarea'; +TextareaInput.displayName = 'TextareaInput'; + +export { Textarea, TextareaInput }; diff --git a/expo.project/components/ui/toast/index.tsx b/expo.project/components/ui/toast/index.tsx new file mode 100755 index 0000000..d1cc0e4 --- /dev/null +++ b/expo.project/components/ui/toast/index.tsx @@ -0,0 +1,240 @@ +'use client'; +import React from 'react'; +import { createToastHook } from '@gluestack-ui/core/toast/creator'; +import { AccessibilityInfo, Text, View, ViewStyle } from 'react-native'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import { cssInterop } from 'nativewind'; +import { + Motion, + AnimatePresence, + MotionComponentProps, +} from '@legendapp/motion'; +import { + withStyleContext, + useStyleContext, +} from '@gluestack-ui/utils/nativewind-utils'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; + +type IMotionViewProps = React.ComponentProps & + MotionComponentProps; + +const MotionView = Motion.View as React.ComponentType; + +const useToast = createToastHook(MotionView, AnimatePresence); +const SCOPE = 'TOAST'; + +cssInterop(MotionView, { className: 'style' }); + +const toastStyle = tva({ + base: 'p-4 m-1 rounded-md gap-1 web:pointer-events-auto shadow-hard-5 border-outline-100', + variants: { + action: { + error: 'bg-error-800', + warning: 'bg-warning-700', + success: 'bg-success-700', + info: 'bg-info-700', + muted: 'bg-background-800', + }, + + variant: { + solid: '', + outline: 'border bg-background-0', + }, + }, +}); + +const toastTitleStyle = tva({ + base: 'text-typography-0 font-medium font-body tracking-md text-left', + variants: { + isTruncated: { + true: '', + }, + 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', + }, + }, + parentVariants: { + variant: { + solid: '', + outline: '', + }, + action: { + error: '', + warning: '', + success: '', + info: '', + muted: '', + }, + }, + parentCompoundVariants: [ + { + variant: 'outline', + action: 'error', + class: 'text-error-800', + }, + { + variant: 'outline', + action: 'warning', + class: 'text-warning-800', + }, + { + variant: 'outline', + action: 'success', + class: 'text-success-800', + }, + { + variant: 'outline', + action: 'info', + class: 'text-info-800', + }, + { + variant: 'outline', + action: 'muted', + class: 'text-background-800', + }, + ], +}); + +const toastDescriptionStyle = tva({ + base: 'font-normal font-body tracking-md text-left', + variants: { + isTruncated: { + true: '', + }, + 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', + }, + }, + parentVariants: { + variant: { + solid: 'text-typography-50', + outline: 'text-typography-900', + }, + }, +}); + +const Root = withStyleContext(View, SCOPE); +type IToastProps = React.ComponentProps & { + className?: string; +} & VariantProps; + +const Toast = React.forwardRef, IToastProps>( + function Toast( + { className, variant = 'solid', action = 'muted', ...props }, + ref + ) { + return ( + + ); + } +); + +type IToastTitleProps = React.ComponentProps & { + className?: string; +} & VariantProps; + +const ToastTitle = React.forwardRef< + React.ComponentRef, + IToastTitleProps +>(function ToastTitle({ className, size = 'md', children, ...props }, ref) { + const { variant: parentVariant, action: parentAction } = + useStyleContext(SCOPE); + React.useEffect(() => { + // Issue from react-native side + // Hack for now, will fix this later + AccessibilityInfo.announceForAccessibility(children as string); + }, [children]); + + return ( + + {children} + + ); +}); + +type IToastDescriptionProps = React.ComponentProps & { + className?: string; +} & VariantProps; + +const ToastDescription = React.forwardRef< + React.ComponentRef, + IToastDescriptionProps +>(function ToastDescription({ className, size = 'md', ...props }, ref) { + const { variant: parentVariant } = useStyleContext(SCOPE); + return ( + + ); +}); + +Toast.displayName = 'Toast'; +ToastTitle.displayName = 'ToastTitle'; +ToastDescription.displayName = 'ToastDescription'; + +export { useToast, Toast, ToastTitle, ToastDescription }; diff --git a/expo.project/components/ui/vstack/index.tsx b/expo.project/components/ui/vstack/index.tsx new file mode 100755 index 0000000..c935fcf --- /dev/null +++ b/expo.project/components/ui/vstack/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; +import { View } from 'react-native'; + +import { vstackStyle } from './styles'; + +type IVStackProps = React.ComponentProps & + VariantProps; + +const VStack = React.forwardRef, IVStackProps>( + function VStack({ className, space, reversed, ...props }, ref) { + return ( + + ); + } +); + +VStack.displayName = 'VStack'; + +export { VStack }; diff --git a/expo.project/components/ui/vstack/index.web.tsx b/expo.project/components/ui/vstack/index.web.tsx new file mode 100755 index 0000000..54c8a67 --- /dev/null +++ b/expo.project/components/ui/vstack/index.web.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; + +import { vstackStyle } from './styles'; + +type IVStackProps = React.ComponentProps<'div'> & + VariantProps; + +const VStack = React.forwardRef, IVStackProps>( + function VStack({ className, space, reversed, ...props }, ref) { + return ( +
+ ); + } +); + +VStack.displayName = 'VStack'; + +export { VStack }; diff --git a/expo.project/components/ui/vstack/styles.tsx b/expo.project/components/ui/vstack/styles.tsx new file mode 100755 index 0000000..458534a --- /dev/null +++ b/expo.project/components/ui/vstack/styles.tsx @@ -0,0 +1,25 @@ +import { isWeb } from '@gluestack-ui/utils/nativewind-utils'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; + +const baseStyle = isWeb + ? 'flex flex-col relative z-0 box-border border-0 list-none min-w-0 min-h-0 bg-transparent items-stretch m-0 p-0 text-decoration-none' + : ''; + +export const vstackStyle = tva({ + base: `flex-col ${baseStyle}`, + variants: { + space: { + 'xs': 'gap-1', + 'sm': 'gap-2', + 'md': 'gap-3', + 'lg': 'gap-4', + 'xl': 'gap-5', + '2xl': 'gap-6', + '3xl': 'gap-7', + '4xl': 'gap-8', + }, + reversed: { + true: 'flex-col-reverse', + }, + }, +}); diff --git a/expo.project/package-lock.json b/expo.project/package-lock.json index 9fa3ffa..5ad538d 100755 --- a/expo.project/package-lock.json +++ b/expo.project/package-lock.json @@ -11,17 +11,18 @@ "@expo/html-elements": "^0.10.1", "@gluestack-ui/core": "^3.0.0", "@gluestack-ui/utils": "^3.0.0", - "@legendapp/motion": "^2.3.0", + "@legendapp/motion": "^2.4.0", "babel-plugin-module-resolver": "^5.0.2", "expo": "~53.0.22", "expo-status-bar": "~2.2.3", + "lucide-react-native": "^0.543.0", "nativewind": "^4.1.23", "react": "19.0.0", "react-aria": "^3.33.0", "react-dom": "^19.1.1", "react-native": "0.79.6", "react-native-reanimated": "^4.1.0", - "react-native-safe-area-context": "^4.11.0", + "react-native-safe-area-context": "^5.6.1", "react-native-svg": "^15.2.0", "react-native-worklets": "^0.5.0", "react-stately": "^3.39.0", @@ -2521,15 +2522,15 @@ } }, "node_modules/@legendapp/motion": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@legendapp/motion/-/motion-2.3.0.tgz", - "integrity": "sha512-LtTD06eyz/Ge23FAR6BY+i9Gsgr/ZgxE12FneML8LrZGcZOSPN2Ojz3N2eJaTiA50kqoeqrGCaYJja8KgKpL6Q==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@legendapp/motion/-/motion-2.4.0.tgz", + "integrity": "sha512-AAYpRLGvxGD5hIGl9sVHyoUufr66zoH82PuxYcKiPSMdCBI3jwZFWh6CuHjV1leRKVIRk2py1rSvIVabG8eqcw==", "license": "MIT", "dependencies": { "@legendapp/tools": "2.0.1" }, "peerDependencies": { - "nativewind": "^2.0.0", + "nativewind": "*", "react": ">=16", "react-native": "*" } @@ -7695,6 +7696,17 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react-native": { + "version": "0.543.0", + "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-0.543.0.tgz", + "integrity": "sha512-citYLzBOmDy14leIr20cFAZ8hNy23Y3LkE4gXM4/AAF8Cyo4gfOzRZXRJPmpY5yiayrwOOcV1jwID+VA3FI92Q==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-native": "*", + "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0" + } + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -9356,9 +9368,9 @@ } }, "node_modules/react-native-safe-area-context": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.11.0.tgz", - "integrity": "sha512-Bg7bozxEB+ZS+H3tVYs5yY1cvxNXgR6nRQwpSMkYR9IN5CbxohLnSprrOPG/ostTCd4F6iCk0c51pExEhifSKQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz", + "integrity": "sha512-/wJE58HLEAkATzhhX1xSr+fostLsK8Q97EfpfMDKo8jlOc1QKESSX/FQrhk7HhQH/2uSaox4Y86sNaI02kteiA==", "license": "MIT", "peerDependencies": { "react": "*", diff --git a/expo.project/package.json b/expo.project/package.json old mode 100644 new mode 100755 index 035e92e..8dc15cc --- a/expo.project/package.json +++ b/expo.project/package.json @@ -12,17 +12,18 @@ "@expo/html-elements": "^0.10.1", "@gluestack-ui/core": "^3.0.0", "@gluestack-ui/utils": "^3.0.0", - "@legendapp/motion": "^2.3.0", + "@legendapp/motion": "^2.4.0", "babel-plugin-module-resolver": "^5.0.2", "expo": "~53.0.22", "expo-status-bar": "~2.2.3", + "lucide-react-native": "^0.543.0", "nativewind": "^4.1.23", "react": "19.0.0", "react-aria": "^3.33.0", "react-dom": "^19.1.1", "react-native": "0.79.6", "react-native-reanimated": "^4.1.0", - "react-native-safe-area-context": "^4.11.0", + "react-native-safe-area-context": "^5.6.1", "react-native-svg": "^15.2.0", "react-native-worklets": "^0.5.0", "react-stately": "^3.39.0",