Peace 1 week ago
parent 2499fba82c
commit c7f1a03688
  1. 2
      expo.project/.npmrc
  2. 19
      expo.project/components/ui/box/index.tsx
  3. 19
      expo.project/components/ui/box/index.web.tsx
  4. 10
      expo.project/components/ui/box/styles.tsx
  5. 439
      expo.project/components/ui/button/index.tsx
  6. 27
      expo.project/components/ui/hstack/index.tsx
  7. 26
      expo.project/components/ui/hstack/index.web.tsx
  8. 25
      expo.project/components/ui/hstack/styles.tsx
  9. 48
      expo.project/components/ui/image/index.tsx
  10. 217
      expo.project/components/ui/input/index.tsx
  11. 40
      expo.project/components/ui/spinner/index.tsx
  12. 48
      expo.project/components/ui/text/index.tsx
  13. 45
      expo.project/components/ui/text/index.web.tsx
  14. 47
      expo.project/components/ui/text/styles.tsx
  15. 94
      expo.project/components/ui/textarea/index.tsx
  16. 240
      expo.project/components/ui/toast/index.tsx
  17. 28
      expo.project/components/ui/vstack/index.tsx
  18. 27
      expo.project/components/ui/vstack/index.web.tsx
  19. 25
      expo.project/components/ui/vstack/styles.tsx
  20. 30
      expo.project/package-lock.json
  21. 5
      expo.project/package.json

@ -1 +1 @@
legacy-peer-deps=true
legacy-peer-deps=true

@ -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<typeof boxStyle> & { className?: string };
const Box = React.forwardRef<React.ComponentRef<typeof View>, IBoxProps>(
function Box({ className, ...props }, ref) {
return (
<View ref={ref} {...props} className={boxStyle({ class: className })} />
);
}
);
Box.displayName = 'Box';
export { Box };

@ -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<typeof boxStyle> & { className?: string };
const Box = React.forwardRef<HTMLDivElement, IBoxProps>(function Box(
{ className, ...props },
ref
) {
return (
<div ref={ref} className={boxStyle({ class: className })} {...props} />
);
});
Box.displayName = 'Box';
export { Box };

@ -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,
});

@ -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<typeof UIButton>,
'context'
> &
VariantProps<typeof buttonStyle> & { className?: string };
const Button = React.forwardRef<
React.ElementRef<typeof UIButton>,
IButtonProps
>(
(
{ className, variant = 'solid', size = 'md', action = 'primary', ...props },
ref
) => {
return (
<UIButton
ref={ref}
{...props}
className={buttonStyle({ variant, size, action, class: className })}
context={{ variant, size, action }}
/>
);
}
);
type IButtonTextProps = React.ComponentPropsWithoutRef<typeof UIButton.Text> &
VariantProps<typeof buttonTextStyle> & { className?: string };
const ButtonText = React.forwardRef<
React.ElementRef<typeof UIButton.Text>,
IButtonTextProps
>(({ className, variant, size, action, ...props }, ref) => {
const {
variant: parentVariant,
size: parentSize,
action: parentAction,
} = useStyleContext(SCOPE);
return (
<UIButton.Text
ref={ref}
{...props}
className={buttonTextStyle({
parentVariants: {
variant: parentVariant,
size: parentSize,
action: parentAction,
},
variant: variant as 'link' | 'outline' | 'solid' | undefined,
size,
action: action as
| 'primary'
| 'secondary'
| 'positive'
| 'negative'
| undefined,
class: className,
})}
/>
);
});
const ButtonSpinner = UIButton.Spinner;
type IButtonIcon = React.ComponentPropsWithoutRef<typeof UIButton.Icon> &
VariantProps<typeof buttonIconStyle> & {
className?: string | undefined;
as?: React.ElementType;
height?: number;
width?: number;
};
const ButtonIcon = React.forwardRef<
React.ElementRef<typeof UIButton.Icon>,
IButtonIcon
>(({ className, size, ...props }, ref) => {
const {
variant: parentVariant,
size: parentSize,
action: parentAction,
} = useStyleContext(SCOPE);
if (typeof size === 'number') {
return (
<UIButton.Icon
ref={ref}
{...props}
className={buttonIconStyle({ class: className })}
size={size}
/>
);
} else if (
(props.height !== undefined || props.width !== undefined) &&
size === undefined
) {
return (
<UIButton.Icon
ref={ref}
{...props}
className={buttonIconStyle({ class: className })}
/>
);
}
return (
<UIButton.Icon
{...props}
className={buttonIconStyle({
parentVariants: {
size: parentSize,
variant: parentVariant,
action: parentAction,
},
size,
class: className,
})}
ref={ref}
/>
);
});
type IButtonGroupProps = React.ComponentPropsWithoutRef<typeof UIButton.Group> &
VariantProps<typeof buttonGroupStyle>;
const ButtonGroup = React.forwardRef<
React.ElementRef<typeof UIButton.Group>,
IButtonGroupProps
>(
(
{
className,
space = 'md',
isAttached = false,
flexDirection = 'column',
...props
},
ref
) => {
return (
<UIButton.Group
className={buttonGroupStyle({
class: className,
space,
isAttached: isAttached as boolean,
flexDirection: flexDirection as any,
})}
{...props}
ref={ref}
/>
);
}
);
Button.displayName = 'Button';
ButtonText.displayName = 'ButtonText';
ButtonSpinner.displayName = 'ButtonSpinner';
ButtonIcon.displayName = 'ButtonIcon';
ButtonGroup.displayName = 'ButtonGroup';
export { Button, ButtonText, ButtonSpinner, ButtonIcon, ButtonGroup };

@ -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<typeof hstackStyle>;
const HStack = React.forwardRef<React.ComponentRef<typeof View>, IHStackProps>(
function HStack({ className, space, reversed, ...props }, ref) {
return (
<View
className={hstackStyle({
space,
reversed: reversed as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
HStack.displayName = 'HStack';
export { HStack };

@ -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<typeof hstackStyle>;
const HStack = React.forwardRef<React.ComponentRef<'div'>, IHStackProps>(
function HStack({ className, space, reversed, ...props }, ref) {
return (
<div
className={hstackStyle({
space,
reversed: reversed as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
HStack.displayName = 'HStack';
export { HStack };

@ -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',
},
},
});

@ -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<typeof imageStyle> &
React.ComponentProps<typeof UIImage>;
const Image = React.forwardRef<
React.ComponentRef<typeof UIImage>,
ImageProps & { className?: string }
>(function Image({ size = 'md', className, ...props }, ref) {
return (
<UIImage
className={imageStyle({ size, class: className })}
{...props}
ref={ref}
// @ts-expect-error : web only
style={
Platform.OS === 'web'
? { height: 'revert-layer', width: 'revert-layer' }
: undefined
}
/>
);
});
Image.displayName = 'Image';
export { Image };

@ -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<typeof UIInput> &
VariantProps<typeof inputStyle> & { className?: string };
const Input = React.forwardRef<React.ComponentRef<typeof UIInput>, IInputProps>(
function Input(
{ 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.ComponentRef<typeof UIInput.Icon>,
IInputIconProps
>(function InputIcon({ 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.ComponentRef<typeof UIInput.Slot>,
IInputSlotProps
>(function InputSlot({ 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.ComponentRef<typeof UIInput.Input>,
IInputFieldProps
>(function InputField({ 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,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<typeof ActivityIndicator>,
React.ComponentProps<typeof ActivityIndicator>
>(function Spinner(
{
className,
color,
focusable = false,
'aria-label': ariaLabel = 'loading',
...props
},
ref
) {
return (
<ActivityIndicator
ref={ref}
focusable={focusable}
aria-label={ariaLabel}
{...props}
color={color}
className={spinnerStyle({ class: className })}
/>
);
});
Spinner.displayName = 'Spinner';
export { Spinner };

@ -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<typeof RNText> &
VariantProps<typeof textStyle>;
const Text = React.forwardRef<React.ComponentRef<typeof RNText>, ITextProps>(
function Text(
{
className,
isTruncated,
bold,
underline,
strikeThrough,
size = 'md',
sub,
italic,
highlight,
...props
},
ref
) {
return (
<RNText
className={textStyle({
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
size,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
Text.displayName = 'Text';
export { Text };

@ -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<typeof textStyle>;
const Text = React.forwardRef<React.ComponentRef<'span'>, ITextProps>(
function Text(
{
className,
isTruncated,
bold,
underline,
strikeThrough,
size = 'md',
sub,
italic,
highlight,
...props
}: { className?: string } & ITextProps,
ref
) {
return (
<span
className={textStyle({
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
size,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
Text.displayName = 'Text';
export { Text };

@ -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',
},
},
});

@ -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<typeof UITextarea> &
VariantProps<typeof textareaStyle>;
const Textarea = React.forwardRef<
React.ComponentRef<typeof UITextarea>,
ITextareaProps
>(function Textarea(
{ className, variant = 'default', size = 'md', ...props },
ref
) {
return (
<UITextarea
ref={ref}
{...props}
className={textareaStyle({ variant, class: className })}
context={{ size }}
/>
);
});
type ITextareaInputProps = React.ComponentProps<typeof UITextarea.Input> &
VariantProps<typeof textareaInputStyle>;
const TextareaInput = React.forwardRef<
React.ComponentRef<typeof UITextarea.Input>,
ITextareaInputProps
>(function TextareaInput({ className, ...props }, ref) {
const { size: parentSize } = useStyleContext(SCOPE);
return (
<UITextarea.Input
ref={ref}
{...props}
textAlignVertical="top"
className={textareaInputStyle({
parentVariants: {
size: parentSize,
},
class: className,
})}
/>
);
});
Textarea.displayName = 'Textarea';
TextareaInput.displayName = 'TextareaInput';
export { Textarea, TextareaInput };

@ -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<typeof View> &
MotionComponentProps<typeof View, ViewStyle, unknown, unknown, unknown>;
const MotionView = Motion.View as React.ComponentType<IMotionViewProps>;
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<typeof Root> & {
className?: string;
} & VariantProps<typeof toastStyle>;
const Toast = React.forwardRef<React.ComponentRef<typeof Root>, IToastProps>(
function Toast(
{ className, variant = 'solid', action = 'muted', ...props },
ref
) {
return (
<Root
ref={ref}
className={toastStyle({ variant, action, class: className })}
context={{ variant, action }}
{...props}
/>
);
}
);
type IToastTitleProps = React.ComponentProps<typeof Text> & {
className?: string;
} & VariantProps<typeof toastTitleStyle>;
const ToastTitle = React.forwardRef<
React.ComponentRef<typeof Text>,
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 (
<Text
{...props}
ref={ref}
aria-live="assertive"
aria-atomic="true"
role="alert"
className={toastTitleStyle({
size,
class: className,
parentVariants: {
variant: parentVariant,
action: parentAction,
},
})}
>
{children}
</Text>
);
});
type IToastDescriptionProps = React.ComponentProps<typeof Text> & {
className?: string;
} & VariantProps<typeof toastDescriptionStyle>;
const ToastDescription = React.forwardRef<
React.ComponentRef<typeof Text>,
IToastDescriptionProps
>(function ToastDescription({ className, size = 'md', ...props }, ref) {
const { variant: parentVariant } = useStyleContext(SCOPE);
return (
<Text
ref={ref}
{...props}
className={toastDescriptionStyle({
size,
class: className,
parentVariants: {
variant: parentVariant,
},
})}
/>
);
});
Toast.displayName = 'Toast';
ToastTitle.displayName = 'ToastTitle';
ToastDescription.displayName = 'ToastDescription';
export { useToast, Toast, ToastTitle, ToastDescription };

@ -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<typeof View> &
VariantProps<typeof vstackStyle>;
const VStack = React.forwardRef<React.ComponentRef<typeof View>, IVStackProps>(
function VStack({ className, space, reversed, ...props }, ref) {
return (
<View
className={vstackStyle({
space,
reversed: reversed as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
VStack.displayName = 'VStack';
export { VStack };

@ -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<typeof vstackStyle>;
const VStack = React.forwardRef<React.ComponentRef<'div'>, IVStackProps>(
function VStack({ className, space, reversed, ...props }, ref) {
return (
<div
className={vstackStyle({
space,
reversed: reversed as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
VStack.displayName = 'VStack';
export { VStack };

@ -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',
},
},
});

@ -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": "*",

@ -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",

Loading…
Cancel
Save