Morphin
Introduction
Button
Chip
Input
Searchbar
Sidebar

Input

A sizeable, themeable input.
Tamagui is great
This is not correct
Tamagui is great
This is not correct
Tamagui is great
This is not correct
Tamagui is great
This is not correct
Tamagui is great
This is not correct
Tamagui is great
This is not correct

Code

import { YStack } from 'tamagui'
import { MnInput } from 'yourpath/ui'
import { Eye, User2 } from '@tamagui/lucide-icons'

const DemoInput = () => {
  return (
    <>
      <YStack minHeight={250} overflow="hidden" gap="$4" margin="$3" padding="$2">
        <MnInput />
        <MnInput iconLeft={User2} />
        <MnInput iconRight={Eye} roundedBorder />
        <MnInput helperText="Tamagui is great" placeholder="Placeholder" />
        <MnInput errorText="This is not correct" placeholder="Placeholder" error />
        <MnInput roundedBorder />
      </YStack>
    </>
  )
}

Props

MnInput
Prop NameTypeDescription
iconLeftIconPropRender a left icon inside the button
iconRightIconPropRender a right icon inside the button
variantoutlined (default)Inputs are outlined by default
roundedBorderbooleanAdd a small rounded border on corners
helperTextstringAdd helper text under the input
errorTextstringAdd an error message under the input
errorbooleanIf true then input are red in color

MnInput Code

import { getSpace } from '@tamagui/get-token'
import { SizeTokens, styled, ThemeableStack } from 'tamagui'

export const IconContainer = styled(ThemeableStack, {
  name: 'IconContainer',
  justifyContent: 'center',
  backgroundColor: '$backgroundColor',
  variants: {
    unstyled: {
      false: {
        size: '$true',
        backgroundColor: '$background',
        borderRadius: 0,
      },
    },

    size: {
      '...size': (val: SizeTokens, { tokens }) => {
        return {
          paddingHorizontal: getSpace(val, {
            shift: -4,
          }),
        }
      },
    },

    disabled: {
      true: {
        opacity: 0.5,
        // TODO breaking types
        pointerEvents: 'none' as any,
      },
    },
  } as const,

  defaultVariants: {
    unstyled: process.env.TAMAGUI_HEADLESS === '1',
  },
})

import { styled, Input } from 'tamagui'

export const StyledInput = styled(Input, {
  borderColor: 'transparent',
  flex: 1,

  focusVisibleStyle: {
    outlineColor: '$outlineColor',
    outlineWidth: 0,
    outlineStyle: 'solid',
  },
  focusStyle: {
    borderColor: 'transparent',
  },
  hoverStyle: {
    borderColor: 'transparent',
  },
})

import React, { FunctionComponent, useState } from 'react'

import {
  composeEventHandlers,
  getFontSize,
  SizableText,
  styled,
  InputProps as TamaInputProps,
  useComposedRefs,
  useGetThemedIcon,
  useProps,
  XGroup,
  YStack,
} from 'tamagui'
import { IconContainer } from './IconContainer'
import { StyledInput } from './StyledInput'

type MnInputVariant = 'outlined'
type InputComponentIconProps = { color?: any; size?: any }
type IconProp = JSX.Element | FunctionComponent<InputComponentIconProps> | null

interface MnInputProps extends TamaInputProps {
  iconLeft?: IconProp
  iconRight?: IconProp
  scaleIcon?: number
  scaleSpace?: number
  variant?: MnInputVariant
  roundedBorder?: boolean
  error?: boolean
  helperText?: string
  errorText?: string
}

const XGroupStyled = styled(XGroup, {
  name: 'Input',
  borderStyle: 'solid',
  borderWidth: '$0.5',
  borderColor: '$borderColor',
  overflow: 'hidden',

  variants: {
    unstyled: {
      false: {
        height: 'auto',
        borderRadius: 0,
        hoverStyle: {
          borderColor: '$color7',
        },
      },
    },
    variant: {
      outlined: {
        borderColor: '$borderColor',
      },
    },
    roundedBorder: {
      true: {
        borderRadius: '$2',
      },
    },
    isFocused: {
      true: {
        // This should have been the outlineColor, need to understand the theme better
        outlineColor: '$color7',
        outlineWidth: 2,
        outlineStyle: 'solid',
      },
    },
    isError: {
      true: {
        borderColor: 'red',
        outlineColor: 'red',
        hoverStyle: {
          borderColor: 'red',
        },
      },
    },
  } as const,

  defaultVariants: {
    unstyled: process.env.TAMAGUI_HEADLESS === '1',
  },
})

export const MnInput = React.forwardRef((inProps: MnInputProps, forwardedRef) => {
  const {
    onFocus,
    onBlur,
    variant = 'outlined',
    iconLeft,
    iconRight,
    scaleIcon = 1,
    scaleSpace = 1,
    helperText,
    errorText,
    roundedBorder = false,
    error = false,
    color,
    space,
    ...rest
  } = useProps(inProps)
  const [isFocused, setIsFocused] = useState(false)
  const ref = React.useRef<HTMLInputElement>(null)
  const composedRefs = useComposedRefs<any>(forwardedRef, ref)

  const size = inProps.size || '$true'
  const iconSize = getFontSize(size as any) * scaleIcon
  const getThemedIcon = useGetThemedIcon({ size: iconSize, color: color as any })
  const [themedIconLeft, themedIconRight] = [iconLeft, iconRight].map(getThemedIcon)

  return (
    <YStack>
      <XGroupStyled
        testID="mn-input"
        variant={variant}
        isFocused={isFocused}
        isError={error}
        roundedBorder={roundedBorder}
        size={'$0'}
      >
        <XGroup.Item>
          {themedIconLeft && <IconContainer>{themedIconLeft}</IconContainer>}
        </XGroup.Item>
        <XGroup.Item>
          <StyledInput
            ref={composedRefs}
            onFocus={composeEventHandlers(onFocus, () => {
              setIsFocused(true)
            })}
            onBlur={composeEventHandlers(onBlur, () => {
              setIsFocused(false)
            })}
            {...(iconLeft && { paddingLeft: 0 })}
            {...(iconRight && { paddingRight: 0 })}
            height={'auto'}
            {...(error && { placeholderTextColor: '0px' })}
            {...rest}
          />
        </XGroup.Item>

        <XGroup.Item>
          {themedIconRight && <IconContainer>{themedIconRight}</IconContainer>}
        </XGroup.Item>
      </XGroupStyled>
      {helperText && (
        <SizableText theme="alt1" size="$1">
          {helperText}
        </SizableText>
      )}
      {error && (
        <SizableText theme="red_alt1" size="$1">
          {errorText}
        </SizableText>
      )}
    </YStack>
  )
})