diff --git a/packages/varlet-vue2-ui/src/countdown/Countdown.vue b/packages/varlet-vue2-ui/src/countdown/Countdown.vue
index 975d9e0..7935d3a 100644
--- a/packages/varlet-vue2-ui/src/countdown/Countdown.vue
+++ b/packages/varlet-vue2-ui/src/countdown/Countdown.vue
@@ -6,12 +6,11 @@
-
diff --git a/packages/varlet-vue2-ui/src/input.zip b/packages/varlet-vue2-ui/src/input.zip
new file mode 100644
index 0000000..0332ee0
Binary files /dev/null and b/packages/varlet-vue2-ui/src/input.zip differ
diff --git a/packages/varlet-vue2-ui/src/input/Input.vue b/packages/varlet-vue2-ui/src/input/Input.vue
new file mode 100644
index 0000000..3f34421
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/Input.vue
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
diff --git a/packages/varlet-vue2-ui/src/input/__tests__/__snapshots__/index.spec.js.snap b/packages/varlet-vue2-ui/src/input/__tests__/__snapshots__/index.spec.js.snap
new file mode 100644
index 0000000..eb36a7a
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/__tests__/__snapshots__/index.spec.js.snap
@@ -0,0 +1,346 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`test input clear 1`] = `
+"
"
+`;
+
+exports[`test input example 1`] = `
+""
+`;
+
+exports[`test input focus & blur 1`] = `
+""
+`;
+
+exports[`test input focus & blur 2`] = `
+""
+`;
+
+exports[`test input hint to be false 1`] = `
+""
+`;
+
+exports[`test input maxlength 1`] = `
+""
+`;
+
+exports[`test input validation 1`] = `
+""
+`;
+
+exports[`test input validation 2`] = `
+""
+`;
+
+exports[`test input validation 3`] = `
+""
+`;
diff --git a/packages/varlet-vue2-ui/src/input/__tests__/index.spec.js b/packages/varlet-vue2-ui/src/input/__tests__/index.spec.js
new file mode 100644
index 0000000..4c2af6f
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/__tests__/index.spec.js
@@ -0,0 +1,219 @@
+import example from '../example'
+import Input from '..'
+import VarInput from '../Input'
+import { mount } from '@vue/test-utils'
+import Vue from 'vue'
+import { delay } from '../../utils/jest'
+
+test('test input example', () => {
+ const wrapper = mount(example)
+ expect(wrapper.html()).toMatchSnapshot()
+ wrapper.destroy()
+})
+
+test('test input plugin', () => {
+ Vue.use(Input)
+ expect(Vue.component(Input.name)).toBeTruthy()
+})
+
+test('test input focus & blur', async () => {
+ const onFocus = jest.fn()
+ const onBlur = jest.fn()
+
+ const wrapper = mount(VarInput, {
+ listeners: {
+ focus: onFocus,
+ blur: onBlur,
+ },
+ })
+
+ wrapper.vm.focus()
+ await wrapper.find('.var-input__input').trigger('focus')
+ expect(wrapper.html()).toMatchSnapshot()
+
+ wrapper.vm.blur()
+ await wrapper.find('.var-input__input').trigger('blur')
+ expect(wrapper.html()).toMatchSnapshot()
+
+ wrapper.destroy()
+})
+
+test('test input onInput & onChange & onClick', async () => {
+ const onInput = jest.fn((value) => wrapper.setProps({ value }))
+ const onChange = jest.fn()
+ const onClick = jest.fn()
+
+ const wrapper = mount(VarInput, {
+ propsData: {
+ value: '',
+ },
+ listeners: {
+ input: onInput,
+ change: onChange,
+ click: onClick,
+ },
+ })
+
+ await wrapper.trigger('click')
+ expect(onClick).toHaveBeenCalledTimes(1)
+
+ wrapper.find('.var-input__input').setValue('t')
+
+ await wrapper.find('.var-input__input').trigger('input')
+ expect(onInput).lastCalledWith('t', new Event('input'))
+ expect(wrapper.props('value')).toBe('t')
+
+ await wrapper.find('.var-input__input').trigger('change')
+ expect(onChange).lastCalledWith('t', new Event('input'))
+
+ wrapper.destroy()
+})
+
+test('test input maxlength', () => {
+ const wrapper = mount(VarInput, {
+ propsData: {
+ value: 'text',
+ maxlength: 100,
+ },
+ })
+
+ expect(wrapper.find('.var-form-details__length').text()).toBe('4/100')
+ expect(wrapper.html()).toMatchSnapshot()
+})
+
+test('test input hint to be false', () => {
+ const wrapper = mount(VarInput, {
+ propsData: {
+ value: 'text',
+ hint: false,
+ },
+ })
+
+ expect(wrapper.html()).toMatchSnapshot()
+
+ wrapper.destroy()
+})
+
+test('test input clear', async () => {
+ const onInput = jest.fn((value) => wrapper.setProps({ value }))
+ const onClear = jest.fn()
+
+ const wrapper = mount(VarInput, {
+ propsData: {
+ value: 'text',
+ clearable: true,
+ },
+ listeners: {
+ clear: onClear,
+ input: onInput,
+ },
+ })
+
+ expect(wrapper.html()).toMatchSnapshot()
+
+ await wrapper.find('.var-input__clear-icon').trigger('click')
+ expect(onClear).lastCalledWith('')
+ expect(wrapper.props('value')).toBe('')
+
+ wrapper.destroy()
+})
+
+const triggerEvents = async (wrapper) => {
+ await wrapper.find('.var-input__input').trigger('input')
+ await wrapper.find('.var-input__input').trigger('change')
+ await wrapper.find('.var-input__clear-icon').trigger('click')
+ await wrapper.trigger('click')
+}
+
+test('test input disabled', async () => {
+ const onClear = jest.fn()
+ const onClick = jest.fn()
+ const onInput = jest.fn()
+ const onChange = jest.fn()
+
+ const wrapper = mount(VarInput, {
+ propsData: {
+ value: 'hello',
+ clearable: true,
+ disabled: true,
+ },
+ listeners: {
+ input: onInput,
+ clear: onClear,
+ click: onClick,
+ change: onChange,
+ },
+ })
+
+ await triggerEvents(wrapper)
+
+ expect(onInput).toHaveBeenCalledTimes(0)
+ expect(onClear).toHaveBeenCalledTimes(0)
+ expect(onClick).toHaveBeenCalledTimes(0)
+ expect(onChange).toHaveBeenCalledTimes(0)
+
+ wrapper.destroy()
+})
+
+test('test input readonly', async () => {
+ const onClear = jest.fn()
+ const onClick = jest.fn()
+ const onInput = jest.fn()
+ const onChange = jest.fn()
+
+ const wrapper = mount(VarInput, {
+ propsData: {
+ value: 'hello',
+ clearable: true,
+ readonly: true,
+ },
+ listeners: {
+ input: onInput,
+ clear: onClear,
+ click: onClick,
+ chnage: onChange,
+ },
+ })
+
+ await triggerEvents(wrapper)
+
+ expect(onInput).toHaveBeenCalledTimes(0)
+ expect(onClear).toHaveBeenCalledTimes(0)
+ expect(onClick).toHaveBeenCalledTimes(2)
+ expect(onChange).toHaveBeenCalledTimes(0)
+
+ wrapper.destroy()
+})
+
+test('test input validation', async () => {
+ const onInput = jest.fn((value) => wrapper.setProps({ value }))
+
+ const wrapper = mount(VarInput, {
+ propsData: {
+ value: '',
+ rules: [(v) => v.length > 3 || '长度必须大于3'],
+ },
+ listeners: {
+ input: onInput,
+ },
+ })
+
+ wrapper.find('.var-input__input').setValue('1')
+ await wrapper.find('.var-input__input').trigger('input')
+ await delay(16)
+ expect(wrapper.find('.var-form-details__message').text()).toBe('长度必须大于3')
+ expect(wrapper.html()).toMatchSnapshot()
+
+ wrapper.vm.reset()
+ await delay(16)
+ expect(wrapper.props('value')).toBe('')
+ expect(wrapper.html()).toMatchSnapshot()
+
+ wrapper.find('.var-input__input').setValue('1234')
+ await wrapper.find('.var-input__input').trigger('input')
+ await delay(16)
+ expect(wrapper.find('.var-form-details__message').exists()).toBeFalsy()
+ expect(wrapper.html()).toMatchSnapshot()
+
+ wrapper.destroy()
+})
diff --git a/packages/varlet-vue2-ui/src/input/docs/en-US.md b/packages/varlet-vue2-ui/src/input/docs/en-US.md
new file mode 100644
index 0000000..b2c2a65
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/docs/en-US.md
@@ -0,0 +1,172 @@
+# Input
+
+### Install
+
+```js
+import Vue from 'vue'
+import { Input } from '@varlet/ui'
+
+Vue.use(Input)
+```
+
+### Basic Usage
+
+The behavior of the input box is consistent with the basic content, and the user can always get a string that conforms to the `type rule when inputting
+
+```js
+export default {
+ data: () =>({
+ value: ''
+ }),
+}
+```
+
+```html
+
+```
+
+### Plain Mode
+
+If you only need the basic functionality of the component, you can remove some styles through attributes.
+
+```html
+
+```
+
+### Textarea
+
+```html
+
+```
+
+### Maxlength
+
+```html
+
+```
+
+### Disabled
+
+```html
+
+```
+
+### Readonly
+
+```html
+
+```
+
+### Clearable
+
+```html
+
+```
+
+### Display Icon
+```js
+import Vue from 'vue'
+import { Icon } from '@varlet/ui'
+
+Vue.use(Icon)
+```
+
+```html
+
+
+
+
+
+
+
+
+```
+
+### Validate
+
+The values are validated by passing in an array of validators,If the validator returns `true`, the validation passes.
+Other values are converted to text as a user prompt.
+
+```html
+
+```
+
+## API
+
+### Props
+
+| Prop | Description | Type | Default |
+| --- | --- | --- | --- |
+| `v-model` | The value of the binding | _string_ | `-` |
+| `placeholder` | placeholder | _string_ | `-` |
+| `type` | Input type, The optional value is `text` `password` `number` | _string_ | `text` |
+| `maxlength` | Maxlength | _string \| number_ | `-` |
+| `textarea` | Is it a textarea | _boolean_ | `false` |
+| `rows` | Number of lines to display in the textarea | _string \| number_ | `8` |
+| `line` | Whether to display a dividing line | _boolean_ | `true` |
+| `hint` | Whether to use placeholder as hint | _boolean_ | `true` |
+| `text-color` | Text color | _string_ | `-` |
+| `focus-color` | The primary color in focus | _string_ | `-` |
+| `blur-color` | The primary color in blur | _string_ | `-` |
+| `readonly` | Whether the readonly | _boolean_ | `false` |
+| `disabled` | Whether the disabled | _boolean_ | `false` |
+| `clearable` | Whether the clearable | _boolean_ | `false` |
+| `resize` | Whether textarea can be dragged to resize | _boolean_ | `false` |
+| `validate-trigger` | Timing to trigger validation, The optional value is `onFocus` `onBlur` `onChange` `onClick` `onClear` `onInput` | _ValidateTriggers[]_ | `['onInput', 'onClear']` |
+| `rules` | The validation rules, Returns `true` to indicate that the validation passed,The remaining values are converted to text as user prompts | _Array<(v: string) => any>_ | `-` |
+
+### Methods
+
+| Method | Description | Arguments | Return |
+| --- | --- | --- | --- |
+| `focus` | Focus | `-` | `-` |
+| `blur` | Blur | `-` | `-` |
+| `validate` | Trigger validate | `-` | `valid: Promise` |
+| `resetValidation` | Clearing validate messages | `-` | `-` |
+| `reset` | Clear the value of the binding and validate messages | `-` | `-` |
+
+### Events
+
+| Event | Description | Arguments |
+| --- | --- | --- |
+| `focus` | Trigger while focusing | `event: Event` |
+| `blur` | Triggered when out of focus | `event: Event` |
+| `click` | Triggered on Click | `event: Event` |
+| `clear` | Triggered on Clearance | `value: string` |
+| `input` | Trigger on input | `value: string`, `event: Event` |
+| `change` | Trigger on change | `value: string`, `event: Event` |
+
+### Slots
+
+| Slot | Description | Arguments |
+| --- | --- | --- |
+| `prepend-icon` | Prepend Icon | `-` |
+| `append-icon` | Append Icon | `-` |
+
+### Style Variables
+Here are the CSS variables used by the component, Styles can be customized using [StyleProvider](#/en-US/style-provider)
+
+| Variable | Default |
+| --- | --- |
+| `--input-input-text-color` | `#555` |
+| `--input-error-color` | `var(--color-danger)` |
+| `--input-blur-color` | `#888` |
+| `--input-focus-color` | `var(--color-primary)` |
+| `--input-placeholder-size` | `16px` |
+| `--input-textarea-height` | `auto` |
+| `--input-textarea-padding-top` | `8px` |
+| `--input-icon-padding` | `16px 0 0` |
+| `--input-icon-size` | `20px` |
+| `--input-input-text-color` | `#555` |
+| `--input-line-size` | `1px` |
+| `--input-line-spread-size` | `2px` |
+| `--input-disabled-color` | `var(--color-text-disabled)` |
\ No newline at end of file
diff --git a/packages/varlet-vue2-ui/src/input/docs/zh-CN.md b/packages/varlet-vue2-ui/src/input/docs/zh-CN.md
new file mode 100644
index 0000000..3bb2185
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/docs/zh-CN.md
@@ -0,0 +1,172 @@
+# 输入框
+
+### 引入
+
+```js
+import Vue from 'vue'
+import { Input } from '@varlet/ui'
+
+Vue.use(Input)
+```
+
+### 基本使用
+
+输入框的行为和基本原生一致,用户输入时始终获得一个符合 `type` 规则的字符串
+
+```js
+export default {
+ data: () =>({
+ value: ''
+ }),
+}
+```
+
+```html
+
+```
+
+### 朴素模式
+
+如果只需要组件的基本功能,可以通过属性去除部分样式。
+
+```html
+
+```
+
+### 文本域
+
+```html
+
+```
+
+### 最大长度
+
+```html
+
+```
+
+### 禁用
+
+```html
+
+```
+
+### 只读
+
+```html
+
+```
+
+### 可清除
+
+```html
+
+```
+
+### 显示图标
+```js
+import Vue from 'vue'
+import { Icon } from '@varlet/ui'
+
+Vue.use(Icon)
+```
+
+```html
+
+
+
+
+
+
+
+
+```
+
+### 字段校验
+
+通过传入一个校验器数组可以对值进行校验,校验器返回 `true` 则为校验通过。
+以外的值将转换为文本作为用户提示。
+
+```html
+
+```
+
+## API
+
+### 属性
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| `v-model` | 绑定的值 | _string_ | `-` |
+| `placeholder` | 占位符 | _string_ | `-` |
+| `type` | 输入框类型, 可选值为 `text` `password` `number` | _string_ | `text` |
+| `maxlength` | 最大长度 | _string \| number_ | `-` |
+| `textarea` | 是否是文本域 | _boolean_ | `false` |
+| `rows` | 文本域的显示行数 | _string \| number_ | `8` |
+| `line` | 是否显示分割线 | _boolean_ | `true` |
+| `hint` | 是否使用占位符作为提示 | _boolean_ | `true` |
+| `text-color` | 文字颜色 | _string_ | `-` |
+| `focus-color` | 聚焦时的主要颜色 | _string_ | `-` |
+| `blur-color` | 失焦时的主要颜色 | _string_ | `-` |
+| `readonly` | 是否只读 | _boolean_ | `false` |
+| `disabled` | 是否禁用 | _boolean_ | `false` |
+| `clearable` | 是否可清除 | _boolean_ | `false` |
+| `resize` | 文本域是否可以拖动调整尺寸 | _boolean_ | `false` |
+| `validate-trigger` | 触发验证的时机,可选值为 `onFocus` `onBlur` `onChange` `onClick` `onClear` `onInput` | _ValidateTriggers[]_ | `['onInput', 'onClear']` |
+| `rules` | 验证规则,返回 `true` 表示验证通过,其余的值则转换为文本作为用户提示 | _Array<(v: string) => any>_ | `-` |
+
+### 方法
+
+| 方法名 | 说明 | 参数 | 返回值 |
+| --- | --- | --- | --- |
+| `focus` | 聚焦 | `-` | `-` |
+| `blur` | 失焦 | `-` | `-` |
+| `validate` | 触发校验 | `-` | `valid: Promise` |
+| `resetValidation` | 清空校验信息 | `-` | `-` |
+| `reset` | 清空绑定的值和校验信息 | `-` | `-` |
+
+### 事件
+
+| 事件名 | 说明 | 参数 |
+| --- | --- | --- |
+| `focus` | 聚焦时触发 | `event: Event` |
+| `blur` | 失焦时触发 | `event: Event` |
+| `click` | 点击时触发 | `event: Event` |
+| `clear` | 清除时触发 | `value: string` |
+| `input` | 输入时触发 | `value: string`, `event: Event` |
+| `change` | 更新时触发 | `value: string`, `event: Event` |
+
+### 插槽
+
+| 插槽名 | 说明 | 参数 |
+| --- | --- | --- |
+| `prepend-icon` | 前置图标 | `-` |
+| `append-icon` | 后置图标 | `-` |
+
+### 样式变量
+以下为组件使用的 css 变量,可以使用 [StyleProvider 组件](#/zh-CN/style-provider)进行样式定制
+
+| 变量名 | 默认值 |
+| --- | --- |
+| `--input-input-text-color` | `#555` |
+| `--input-error-color` | `var(--color-danger)` |
+| `--input-blur-color` | `#888` |
+| `--input-focus-color` | `var(--color-primary)` |
+| `--input-placeholder-size` | `16px` |
+| `--input-textarea-height` | `auto` |
+| `--input-textarea-padding-top` | `8px` |
+| `--input-icon-padding` | `16px 0 0` |
+| `--input-icon-size` | `20px` |
+| `--input-input-text-color` | `#555` |
+| `--input-line-size` | `1px` |
+| `--input-line-spread-size` | `2px` |
+| `--input-disabled-color` | `var(--color-text-disabled)` |
diff --git a/packages/varlet-vue2-ui/src/input/example/index.vue b/packages/varlet-vue2-ui/src/input/example/index.vue
new file mode 100644
index 0000000..fcd5978
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/example/index.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
diff --git a/packages/varlet-vue2-ui/src/input/example/locale/en-US.ts b/packages/varlet-vue2-ui/src/input/example/locale/en-US.ts
new file mode 100644
index 0000000..9a08b12
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/example/locale/en-US.ts
@@ -0,0 +1,14 @@
+export default {
+ basicUsage: 'Basic Usage',
+ plainMode: 'Plain Mode',
+ textarea: 'Textarea',
+ maxlength: 'Maxlength',
+ disabled: 'Disabled',
+ readonly: 'Readonly',
+ clearable: 'Clearable',
+ displayIcon: 'Display Icon',
+ validate: 'Validate',
+ placeholder: 'Please enter text',
+ maxMessage: 'Text length must be greater than 6',
+ clearableText: 'Clearable Text',
+}
diff --git a/packages/varlet-vue2-ui/src/input/example/locale/index.ts b/packages/varlet-vue2-ui/src/input/example/locale/index.ts
new file mode 100644
index 0000000..d2e375e
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/example/locale/index.ts
@@ -0,0 +1,23 @@
+// lib
+import _zhCN from '../../../locale/zh-CN'
+import _enCN from '../../../locale/en-US'
+// mobile example doc
+import zhCN from './zh-CN'
+import enUS from './en-US'
+import { useLocale, add as _add, use as _use } from '../../../locale'
+
+const { add, use: exampleUse, pack, packs, merge } = useLocale()
+
+const use = (lang: string) => {
+ _use(lang)
+ exampleUse(lang)
+}
+
+export { add, pack, packs, merge, use }
+
+// lib
+_add('zh-CN', _zhCN)
+_add('en-US', _enCN)
+// mobile example doc
+add('zh-CN', zhCN as any)
+add('en-US', enUS as any)
diff --git a/packages/varlet-vue2-ui/src/input/example/locale/zh-CN.ts b/packages/varlet-vue2-ui/src/input/example/locale/zh-CN.ts
new file mode 100644
index 0000000..4975e83
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/example/locale/zh-CN.ts
@@ -0,0 +1,14 @@
+export default {
+ basicUsage: '基本使用',
+ plainMode: '朴素模式',
+ textarea: '文本域',
+ maxlength: '最大长度',
+ disabled: '禁用',
+ readonly: '只读',
+ clearable: '可清除',
+ displayIcon: '显示图标',
+ validate: '字段校验',
+ placeholder: '请输入文本',
+ maxMessage: '文本长度必须大于6',
+ clearableText: '可清除文本',
+}
diff --git a/packages/varlet-vue2-ui/src/input/index.ts b/packages/varlet-vue2-ui/src/input/index.ts
new file mode 100644
index 0000000..e815931
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/index.ts
@@ -0,0 +1,10 @@
+import type { VueConstructor } from 'vue'
+import Input from './Input.vue'
+
+Input.install = function (app: VueConstructor) {
+ app.component(Input.name, Input)
+}
+
+export const _InputComponent = Input
+
+export default Input
diff --git a/packages/varlet-vue2-ui/src/input/input.less b/packages/varlet-vue2-ui/src/input/input.less
new file mode 100644
index 0000000..15d1408
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/input.less
@@ -0,0 +1,187 @@
+@input-input-text-color: #555;
+@input-error-color: var(--color-danger);
+@input-blur-color: #888;
+@input-focus-color: var(--color-primary);
+@input-placeholder-size: 16px;
+@input-textarea-height: auto;
+@input-textarea-padding-top: 8px;
+@input-icon-padding: 16px 0 0;
+@input-icon-size: 20px;
+@input-line-size: 1px;
+@input-line-spread-size: 2px;
+@input-disabled-color: var(--color-text-disabled);
+
+:root {
+ --input-input-text-color: @input-input-text-color;
+ --input-error-color: @input-error-color;
+ --input-blur-color: @input-blur-color;
+ --input-focus-color: @input-focus-color;
+ --input-placeholder-size: @input-placeholder-size;
+ --input-textarea-height: @input-textarea-height;
+ --input-textarea-padding-top: @input-textarea-padding-top;
+ --input-icon-padding: @input-icon-padding;
+ --input-icon-size: @input-icon-size;
+ --input-line-size: @input-line-size;
+ --input-line-spread-size: @input-line-spread-size;
+ --input-disabled-color: @input-disabled-color;
+}
+
+.var {
+ &-input-footer-margin-enter-from,
+ &-input-footer-margin-leave-to {
+ opacity: 0;
+ margin-top: 2px !important;
+ }
+
+ &-input-footer-margin-enter-active,
+ &-input-footer-margin-leave-active {
+ transition: 0.2s all var(--cubic-bezier);
+ }
+}
+
+.var-input {
+ width: 100%;
+ color: var(--input-input-text-color);
+
+ &__controller {
+ width: 100%;
+ display: flex;
+ position: relative;
+ }
+
+ &__wrap {
+ position: relative;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ padding-top: var(--input-placeholder-size);
+ }
+
+ &__icon {
+ display: flex;
+ align-items: center;
+ padding: var(--input-icon-padding);
+ font-size: 20px;
+ }
+
+ &__placeholder {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ width: 100%;
+ transform-origin: left;
+ transition-property: top, transform, width;
+ transition-duration: 0.3s;
+ transform: translate(0, calc(-50% + var(--input-placeholder-size) / 2)) scale(1);
+ font-size: var(--input-placeholder-size);
+ color: var(--input-blur-color);
+ }
+
+ &__textarea-placeholder {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ transform-origin: left;
+ transition-property: transform, width;
+ transition-duration: 0.3s;
+ transform: translate(0, calc(var(--input-textarea-padding-top) + var(--input-placeholder-size))) scale(1);
+ font-size: var(--input-placeholder-size);
+ color: var(--input-blur-color);
+ }
+
+ &__autocomplete {
+ width: 0;
+ height: 0;
+ padding: 0;
+ border: none;
+ outline: none;
+ font-size: 0;
+ }
+
+ &__input {
+ width: 100%;
+ height: 32px;
+ padding: 0;
+ outline: none;
+ border: none;
+ background: transparent;
+ color: var(--input-input-text-color);
+ caret-color: var(--input-focus-color);
+ font: inherit;
+ }
+
+ &__line {
+ width: 100%;
+ height: var(--input-line-size);
+ background: var(--input-blur-color);
+ }
+
+ &__dot {
+ width: 100%;
+ height: var(--input-line-spread-size);
+ background: var(--input-focus-color);
+ transform: scaleX(0);
+ transform-origin: center;
+ transition: transform 0.3s var(--cubic-bezier);
+ }
+
+ &__clear-icon[var-input-cover] {
+ display: flex;
+ margin-left: 4px;
+ }
+
+ &--textarea {
+ padding-top: var(--input-textarea-padding-top);
+ height: var(--input-textarea-height);
+ }
+
+ &--placeholder-hint {
+ top: 0;
+ width: 133.33%;
+ transform: translate(0, 0) scale(0.75);
+ }
+
+ &--placeholder-non-hint {
+ top: 50%;
+ transform: translate(0, -50%) scale(1);
+ }
+
+ &--non-hint {
+ padding-top: 0;
+ }
+
+ &--placeholder-hidden {
+ visibility: hidden;
+ }
+
+ &--focus {
+ color: var(--input-focus-color);
+ }
+
+ &--spread {
+ transform: scaleX(1);
+ }
+
+ &--disabled {
+ -webkit-text-fill-color: var(--input-disabled-color);
+ opacity: 1;
+ color: var(--input-disabled-color);
+ }
+
+ &--error {
+ color: var(--input-error-color);
+ }
+
+ &--line-disabled {
+ background: var(--input-disabled-color);
+ }
+
+ &--line-error {
+ background: var(--input-error-color);
+ }
+
+ &--caret-error {
+ caret-color: var(--input-error-color);
+ }
+}
diff --git a/packages/varlet-vue2-ui/src/input/props.ts b/packages/varlet-vue2-ui/src/input/props.ts
new file mode 100644
index 0000000..9c78645
--- /dev/null
+++ b/packages/varlet-vue2-ui/src/input/props.ts
@@ -0,0 +1,72 @@
+import type { PropType } from 'vue'
+
+export function typeValidator(type: string) {
+ return ['text', 'password', 'number'].includes(type)
+}
+
+export type ValidateTriggers = 'onFocus' | 'onBlur' | 'onChange' | 'onClick' | 'onClear' | 'onInput'
+
+export const props = {
+ value: {
+ type: String,
+ },
+ type: {
+ type: String as PropType<'text' | 'password' | 'number'>,
+ default: 'text',
+ validator: typeValidator,
+ },
+ textarea: {
+ type: Boolean,
+ default: false,
+ },
+ rows: {
+ type: [String, Number],
+ default: 8,
+ },
+ placeholder: {
+ type: String,
+ },
+ line: {
+ type: Boolean,
+ default: true,
+ },
+ hint: {
+ type: Boolean,
+ default: true,
+ },
+ textColor: {
+ type: String,
+ },
+ focusColor: {
+ type: String,
+ },
+ blurColor: {
+ type: String,
+ },
+ maxlength: {
+ type: [String, Number],
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
+ readonly: {
+ type: Boolean,
+ default: false,
+ },
+ clearable: {
+ type: Boolean,
+ default: false,
+ },
+ resize: {
+ type: Boolean,
+ default: false,
+ },
+ validateTrigger: {
+ type: Array as PropType,
+ default: () => ['onInput', 'onClear'],
+ },
+ rules: {
+ type: Array as PropType any>>,
+ },
+}
diff --git a/packages/varlet-vue2-ui/types/index.d.ts b/packages/varlet-vue2-ui/types/index.d.ts
index ad4daf7..2d7fcc7 100644
--- a/packages/varlet-vue2-ui/types/index.d.ts
+++ b/packages/varlet-vue2-ui/types/index.d.ts
@@ -24,5 +24,6 @@ export * from './snackbar'
export * from './sticky'
export * from './loading'
export * from './menu'
+export * from './input'
export * from './varComponent'
export * from './varDirective'
diff --git a/packages/varlet-vue2-ui/types/input.d.ts b/packages/varlet-vue2-ui/types/input.d.ts
new file mode 100644
index 0000000..d164be4
--- /dev/null
+++ b/packages/varlet-vue2-ui/types/input.d.ts
@@ -0,0 +1,44 @@
+import { VarComponent } from './varComponent'
+
+export type InputValidateTriggers = 'onFocus' | 'onBlur' | 'onChange' | 'onClick' | 'onClear' | 'onInput'
+
+export interface InputProps {
+ value?: string
+ type?: 'text' | 'password' | 'number'
+ textarea?: boolean
+ rows?: string | number
+ placeholder?: string
+ hint?: boolean
+ textColor?: string
+ focusColor?: string
+ blurColor?: string
+ maxlength?: string | number
+ disabled?: boolean
+ readonly?: boolean
+ clearable?: boolean
+ resize?: boolean
+ validateTrigger?: InputValidateTriggers[]
+ rules?: Array<(v: string) => any>
+ onFocus?: (e: Event) => void
+ onBlur?: (e: Event) => void
+ onClick?: (e: Event) => void
+ onClear?: (value: string) => void
+ onInput?: (value: string, e: Event) => void
+ onChange?: (value: string, e: Event) => void
+}
+
+export class Input extends VarComponent {
+ $props: InputProps
+
+ focus(): void
+
+ blur(): void
+
+ validate(): Promise
+
+ resetValidation(): void
+
+ reset(): void
+}
+
+export class _InputComponent extends Input {}