Skyroc Native UI

Input

单行文本输入框,内置清除与密码可见性切换

输入框(Input)基于 React Native 的 TextInput 封装,在外层套一个 View 作为可见边框容器,把 leading / 清除按钮 / 密码切换按钮 / trailing 排在同一行里。值由组件托管(useControllableState),受控与非受控共用一条路径,因此清除按钮在两种用法下都能真正清空。

import { Input } from '@skyroc/native-ui';

基础用法

不传任何变体属性时为 outline + mdplaceholderdefaultValue 等原生属性直接传即可。

InputBasic.tsx
Loading…
import { Input } from '@skyroc/native-ui';
import { View } from 'react-native';

const InputBasic = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <Input placeholder="请输入" />
      <Input defaultValue="非受控默认值" />
    </View>
  );
};

export { InputBasic };

何时使用

  • 需要单行文本录入时使用;多行文本传 multiline 并自行给一个高度(见「原生属性透传」)。
  • 表单场景推荐配合 FormItem / FieldItem 使用,由它们负责标签、错误文案与校验,Input 只负责输入区。
  • 表单行内的「标签 + 输入」布局用 variant="none",把边框交给外层容器,避免出现双层边框。

外观变体

variant 控制容器外观:

变体表现适用场景
outline四边描边(border-input独立输入框(默认)
filled灰底(bg-muted)+ 透明描边弱化边框的表单、搜索条
underline仅底部描边、无圆角紧凑列表、内联编辑
none无边框无背景、无内边距、高度自适应嵌在自定义容器里当纯输入区
InputVariant.tsx
Loading…
import { Input } from '@skyroc/native-ui';
import { View } from 'react-native';

const VARIANTS = ['outline', 'filled', 'underline', 'none'] as const;

const InputVariant = () => {
  return (
    <View className="gap-3 bg-background p-4">
      {VARIANTS.map(v => (
        <Input
          key={v}
          placeholder={v}
          variant={v}
        />
      ))}
    </View>
  );
};

export { InputVariant };

none 同时清掉了 height / padding / gap / border-radius,所以尺寸完全由父容器决定;它也不会随 focused / error 变色。

尺寸

size 同时决定容器高度、圆角、水平内边距、元素间距与输入字号,功能图标(清除、密码切换)的像素尺寸也随之变化:

尺寸高度圆角水平内边距间距字号图标
sm40rounded-md86--text-sm18
md48rounded-lg128--text-base20
lg64rounded-xl1610--text-lg22
InputSize.tsx
Loading…
import { Input } from '@skyroc/native-ui';
import { View } from 'react-native';

const SIZES = ['sm', 'md', 'lg'] as const;

const InputSize = () => {
  return (
    <View className="gap-3 bg-background p-4">
      {SIZES.map(s => (
        <Input
          key={s}
          placeholder={s}
          size={s}
        />
      ))}
    </View>
  );
};

export { InputSize };

字号一律用 text-(length:--text-*) 而不是 text-sm / text-base 简写:简写会连带输出 line-height,Uniwind 会把它换算成绝对行高传给 RN,iOS 在强制行高时会把多出的空间全部加在文字上方,表现为「输入框里的文字偏下、不居中」。单行输入不需要行高,只给 fontSize,让 iOS 按控件高度自然居中。

错误态

errortrue 时边框变为 border-destructive。它的优先级高于聚焦色 —— 出错的输入框即使处于聚焦状态也保持红框。

InputError.tsx
Loading…
import { Input, Text } from '@skyroc/native-ui';
import { View } from 'react-native';

const BORDER_VARIANTS = ['outline', 'filled', 'underline'] as const;

/** Error 只覆盖有边框的变体;none 没有自身边框,需要由外层布局呈现错误反馈 */
const InputError = () => {
  return (
    <View className="gap-3 bg-background p-4">
      {BORDER_VARIANTS.map(variant => (
        <Input
          error
          key={variant}
          placeholder={`${variant} + error`}
          variant={variant}
        />
      ))}
      <View className="rounded-lg border border-destructive px-3 py-2">
        <Input
          error
          placeholder="none 由父容器呈现错误边界"
          variant="none"
        />
      </View>
      <Text className="text-sm text-muted-foreground">
        outline、filled、underline 聚焦后仍保持红框;none 始终无自身边框
      </Text>
    </View>
  );
};

export { InputError };

error 只影响有边框的三种变体(outline / filled / underline)。none 没有自己的边框,错误反馈需要由父容器呈现。

禁用

disabled 会把底层 TextInputeditable 置为 false 并整体降到 50% 不透明度,同时清除按钮不再出现、密码切换按钮不响应点击。

InputDisabled.tsx
Loading…
import { Input } from '@skyroc/native-ui';
import { View } from 'react-native';

/** 功能按钮在 disabled 下也不响应点击 */
const InputDisabled = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <Input
        disabled
        placeholder="disabled"
      />
      <Input
        clearable
        disabled
        defaultValue="disabled 时不显示清除按钮"
      />
      <Input
        disabled
        defaultValue="密码按钮点不动"
        type="password"
      />
    </View>
  );
};

export { InputDisabled };

清除按钮

clearable 在有值且未禁用时,于尾部渲染清除按钮。清空动作由组件自己完成(受控模式下走 onChangeText),onClear 只是额外通知。

InputClearable.tsx
Loading…
import { Input, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

/** 受控与非受控都能真正清空 */
const InputClearable = () => {
  const [clearControlled, setClearControlled] = useState('填了值,点右侧清除');
  const [lastCleared, setLastCleared] = useState('-');

  function handleClear() {
    setLastCleared('clearable 触发了 onClear');
  }

  return (
    <View className="gap-3 bg-background p-4">
      <Input
        clearable
        defaultValue="非受控,组件自己清空"
        onClear={handleClear}
      />
      <Input
        clearable
        placeholder="受控"
        value={clearControlled}
        onChangeText={setClearControlled}
      />
      <Text className="text-sm text-muted-foreground">受控值:{clearControlled || '(空)'}</Text>
      <Text className="text-sm text-muted-foreground">{lastCleared}</Text>
    </View>
  );
};

export { InputClearable };

点击清除后组件会把焦点还给输入框 —— 否则按钮会抢走焦点导致键盘收起,用户还得再点一次才能继续输入。

密码框

type="password" 时自动开启 secureTextEntry 并在尾部渲染眼睛图标。可见性支持两种模式:非受控用 defaultPasswordVisible,受控用 passwordVisible + onPasswordVisibleChange

InputPassword.tsx
Loading…
import { Button, Input } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const InputPassword = () => {
  const [passwordVisible, setPasswordVisible] = useState(false);

  return (
    <View className="gap-3 bg-background p-4">
      <Input
        defaultPasswordVisible
        defaultValue="默认显示内容"
        type="password"
      />
      <Input
        clearable
        defaultValue="clearable + password"
        type="password"
      />
      <Input
        passwordVisible={passwordVisible}
        placeholder="受控可见性"
        type="password"
        onPasswordVisibleChange={setPasswordVisible}
      />
      <Button
        size="sm"
        onPress={() => setPasswordVisible(v => !v)}
      >
        {passwordVisible ? '隐藏密码' : '显示密码'}
      </Button>
    </View>
  );
};

export { InputPassword };

secureTextEntryInputProps 里被移除了,请通过 type 控制。清除按钮与密码按钮可以共存,顺序为「清除 → 眼睛 → trailing」。

前后插槽

leadingtrailing 分别放在输入区两侧,用于图标、单位、快捷按钮等。

InputSlot.tsx
Loading…
import Feather from '@expo/vector-icons/Feather';
import { Input, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
import { withUniwind } from 'uniwind';

/** Feather 不认 className,用 withUniwind 将语义色映射到 color */
const Icon = withUniwind(Feather);

const SearchIcon = (
  <Icon
    colorClassName="accent-primary"
    name="search"
    size={18}
  />
);

/** Trailing 与密码按钮共存且排在其后 */
const InputSlot = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <Input
        leading={SearchIcon}
        placeholder="搜索"
      />
      <Input
        placeholder="带单位"
        trailing={<Text className="text-muted-foreground">元</Text>}
      />
      <Input
        leading={SearchIcon}
        placeholder="password + trailing 同时存在"
        trailing={<Text className="text-muted-foreground">GO</Text>}
        type="password"
      />
    </View>
  );
};

export { InputSlot };

插槽内容不会自动继承输入区的文字颜色:@expo/vector-icons 之类的图标组件需要自己取色(demo 里用 withUniwindaccent-* 映射到 color)。

受控用法

value + onChangeText 即为受控。组件内部仍用同一套状态托管,所以 clearable 在受控下同样生效。

InputControlled.tsx
Loading…
import { Input, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const InputControlled = () => {
  const [controlled, setControlled] = useState('');

  const emailError = controlled.length > 0 && !controlled.includes('@');

  return (
    <View className="gap-3 bg-background p-4">
      <Input
        clearable
        error={emailError}
        placeholder="输入邮箱"
        value={controlled}
        onChangeText={setControlled}
      />
      <Text className="text-sm text-muted-foreground">当前值:{controlled || '(空)'}</Text>
      {emailError ? <Text className="text-sm text-destructive">邮箱必须包含 @</Text> : null}
    </View>
  );
};

export { InputControlled };

无边框内联

variant="none" 常用于「标签 + 输入」的行内布局:边框、圆角、内边距都交给外层容器,Input 自己只占一个 flex-1 的输入区。组件不会替你右对齐,需要靠右时自行传 textAlign="right"

InputInline.tsx
Loading…
import { Input, Text } from '@skyroc/native-ui';
import { View } from 'react-native';

/** none 变体不再强制右对齐,需要靠右时由调用方传 textAlign */
const InputInline = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <View className="flex-row items-center rounded-lg border border-input px-3">
        <Text className="w-20">昵称</Text>
        <Input
          className="flex-1"
          placeholder="请输入昵称"
          variant="none"
        />
      </View>
      <View className="flex-row items-center rounded-lg border border-input px-3">
        <Text className="w-20">手机号</Text>
        <Input
          className="flex-1"
          keyboardType="number-pad"
          placeholder="请输入手机号"
          textAlign="right"
          variant="none"
        />
      </View>
    </View>
  );
};

export { InputInline };

样式覆盖

className 追加到根容器上,classNames 按 slot 细粒度覆盖:

slot作用位置
root根容器 View(边框、背景、高度、内边距)
control底层 TextInput(字号、文字色、placeholder)
action清除 / 密码切换按钮的容器 Pressable
InputStyles.tsx
Loading…
import { Input } from '@skyroc/native-ui';
import { View } from 'react-native';

/** className 落在 root,classNames 逐槽覆盖 */
const InputStyles = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <Input
        className="border-success bg-success/10"
        placeholder="className 覆盖 root"
      />
      <Input
        classNames={{ control: 'text-primary font-semibold' }}
        defaultValue="classNames.control 覆盖输入区"
      />
      <Input
        clearable
        classNames={{ action: 'opacity-40' }}
        defaultValue="classNames.action 覆盖功能按钮"
      />
    </View>
  );
};

export { InputStyles };

classNameclassNames.root 同时存在时,className 排在更后面参与合并,优先级更高。功能图标的颜色由内部的 accent-muted-foreground 决定,不在可覆盖的 slot 内。

原生属性透传

除下表列出的属性外,其余属性原样透传给底层输入组件,keyboardTypemaxLengthmultilinereturnKeyTypetestID 等都可直接使用。

InputNativeProps.tsx
Loading…
import { Input } from '@skyroc/native-ui';
import { View } from 'react-native';

/** 透传给底层 TextInput 的原生属性 */
const InputNativeProps = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <Input
        keyboardType="email-address"
        placeholder="email-address 键盘"
      />
      <Input
        maxLength={6}
        placeholder="maxLength=6"
      />
      <Input
        multiline
        className="h-24 items-start py-2"
        placeholder="multiline"
      />
    </View>
  );
};

export { InputNativeProps };

几个例外:

  • editablesecureTextEntry 已从 InputProps 中移除,分别由 disabledtype 控制。
  • value / onChangeText / onFocus / onBlur / className 由组件接管后再回调,透传属性覆盖不到它们。
  • 组件固定了 allowFontScaling={false}maxFontSizeMultiplier={1},输入文字不随系统字体缩放变化,以免撑破固定高度的容器。
  • multiline 只是透传,容器高度仍由 size 决定,需要自行用 className 调整高度与对齐(如 h-24 items-start py-2)。

Sheet / BottomSheet 内使用时,把 component 换成 BottomSheetTextInput,键盘与手势才能正确协作:

import { BottomSheetTextInput } from '@gorhom/bottom-sheet';

<Input component={BottomSheetTextInput} placeholder="在 Sheet 里输入" />;

无障碍

  • 清除与密码切换按钮带 hitSlop={8},小尺寸下也有足够的点击热区。
  • disabled 会同时禁用两个功能按钮,避免禁用态仍能清空内容或切换密码可见性。
  • 传入 testID 时,两个功能按钮会自动派生出 ${testID}-clear${testID}-eye,便于端到端测试定位。

API

Input

除下表外,Input 透传 TextInput 的全部属性(placeholderkeyboardTypemaxLengthtestID 等),其中 editablesecureTextEntry 已被移除。

属性说明类型默认值
variant容器外观,none 无边框且尺寸由父容器决定'outline' | 'filled' | 'underline' | 'none''outline'
size尺寸,同时决定高度、圆角、内边距、字号与功能图标大小'sm' | 'md' | 'lg''md'
type输入框类型,password 时开启 secureTextEntry 并显示可见性切换按钮'text' | 'password''text'
error错误态,边框变为 destructive,优先级高于聚焦色boolean-
disabled禁用状态,输入不可编辑且功能按钮不响应点击booleanfalse
clearable是否可清除,有值且未禁用时在尾部显示清除按钮booleanfalse
onClear清除回调,清空动作由组件完成,此处只做额外通知() => void-
passwordVisible密码是否可见(受控)boolean-
defaultPasswordVisible密码是否可见的非受控默认值booleanfalse
onPasswordVisibleChange密码可见性变化回调(visible: boolean) => void-
value输入值(受控)string-
defaultValue输入值的非受控默认值string-
onChangeText输入值变化回调(value: string) => void-
leading左侧内容,排在输入区之前ReactNode-
trailing右侧内容,排在清除、密码按钮之后ReactNode-
component自定义底层输入组件,在 Sheet 内使用时传 BottomSheetTextInputComponentType<TextInputProps & RefAttributes<TextInput>>TextInput
className根容器类名,合并到变体样式之后string-
classNames各 slot 的类名覆盖,见「样式覆盖」一节SlotClassNames<'action' | 'control' | 'root'>-
ref底层 TextInput 的 ref,可调用 focus / blur / clear / measure 等原生方法Ref<TextInput>-

类型

import type { InputProps, InputSlots, InputType } from '@skyroc/native-ui';

InputType

输入框类型,password 时自动开启 secureTextEntry 并显示可见性切换按钮。

'password' | 'text'

InputSlots

可通过 classNames 覆盖的 slot 名称,action 为清除与密码切换按钮共用的槽。

'action' | 'control' | 'root'

SlotClassNames

classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 InputSlots。

Partial<Record<Slots, string>>