Input
单行文本输入框,内置清除与密码可见性切换
输入框(Input)基于 React Native 的 TextInput 封装,在外层套一个 View 作为可见边框容器,把 leading / 清除按钮 / 密码切换按钮 / trailing 排在同一行里。值由组件托管(useControllableState),受控与非受控共用一条路径,因此清除按钮在两种用法下都能真正清空。
import { Input } from '@skyroc/native-ui';基础用法
不传任何变体属性时为 outline + md。placeholder、defaultValue 等原生属性直接传即可。
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 | 无边框无背景、无内边距、高度自适应 | 嵌在自定义容器里当纯输入区 |
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 同时决定容器高度、圆角、水平内边距、元素间距与输入字号,功能图标(清除、密码切换)的像素尺寸也随之变化:
| 尺寸 | 高度 | 圆角 | 水平内边距 | 间距 | 字号 | 图标 |
|---|---|---|---|---|---|---|
sm | 40 | rounded-md | 8 | 6 | --text-sm | 18 |
md | 48 | rounded-lg | 12 | 8 | --text-base | 20 |
lg | 64 | rounded-xl | 16 | 10 | --text-lg | 22 |
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 按控件高度自然居中。
错误态
error 为 true 时边框变为 border-destructive。它的优先级高于聚焦色 —— 出错的输入框即使处于聚焦状态也保持红框。
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 会把底层 TextInput 的 editable 置为 false 并整体降到 50% 不透明度,同时清除按钮不再出现、密码切换按钮不响应点击。
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 只是额外通知。
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。
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 };secureTextEntry 从 InputProps 里被移除了,请通过 type 控制。清除按钮与密码按钮可以共存,顺序为「清除 → 眼睛 → trailing」。
前后插槽
leading 与 trailing 分别放在输入区两侧,用于图标、单位、快捷按钮等。
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 里用 withUniwind 把 accent-* 映射到 color)。
受控用法
传 value + onChangeText 即为受控。组件内部仍用同一套状态托管,所以 clearable 在受控下同样生效。
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"。
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 |
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 };className 与 classNames.root 同时存在时,className 排在更后面参与合并,优先级更高。功能图标的颜色由内部的 accent-muted-foreground 决定,不在可覆盖的 slot 内。
原生属性透传
除下表列出的属性外,其余属性原样透传给底层输入组件,keyboardType、maxLength、multiline、returnKeyType、testID 等都可直接使用。
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 };几个例外:
editable与secureTextEntry已从InputProps中移除,分别由disabled和type控制。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 的全部属性(placeholder、keyboardType、maxLength、testID 等),其中 editable 与 secureTextEntry 已被移除。
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| variant | 容器外观,none 无边框且尺寸由父容器决定 | 'outline' | 'filled' | 'underline' | 'none' | 'outline' |
| size | 尺寸,同时决定高度、圆角、内边距、字号与功能图标大小 | 'sm' | 'md' | 'lg' | 'md' |
| type | 输入框类型,password 时开启 secureTextEntry 并显示可见性切换按钮 | 'text' | 'password' | 'text' |
| error | 错误态,边框变为 destructive,优先级高于聚焦色 | boolean | - |
| disabled | 禁用状态,输入不可编辑且功能按钮不响应点击 | boolean | false |
| clearable | 是否可清除,有值且未禁用时在尾部显示清除按钮 | boolean | false |
| onClear | 清除回调,清空动作由组件完成,此处只做额外通知 | () => void | - |
| passwordVisible | 密码是否可见(受控) | boolean | - |
| defaultPasswordVisible | 密码是否可见的非受控默认值 | boolean | false |
| onPasswordVisibleChange | 密码可见性变化回调 | (visible: boolean) => void | - |
| value | 输入值(受控) | string | - |
| defaultValue | 输入值的非受控默认值 | string | - |
| onChangeText | 输入值变化回调 | (value: string) => void | - |
| leading | 左侧内容,排在输入区之前 | ReactNode | - |
| trailing | 右侧内容,排在清除、密码按钮之后 | ReactNode | - |
| component | 自定义底层输入组件,在 Sheet 内使用时传 BottomSheetTextInput | ComponentType<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 并显示可见性切换按钮。
InputSlots
可通过 classNames 覆盖的 slot 名称,action 为清除与密码切换按钮共用的槽。
SlotClassNames
classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 InputSlots。