mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2024-12-21 04:50:14 +08:00
feat(input): add show-password-toggle prop to support show & hide password (#179)
* feat(n-input): add show-password attribute to support show & hide password * feat(n-input): update show-password-toggle attribute * docs: fix typos * feat(n-input): remove unnecessary code * feat(n-input): trigger click to show & hide password * feat(n-input): add icon component prop onMouseup * style(icon): add style to the eye icon * style(n-input): modify css
This commit is contained in:
parent
faee18c1d4
commit
96ae3a38dd
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Pending
|
## Pending
|
||||||
|
|
||||||
|
- `n-input` add show-password-toggle prop.
|
||||||
|
|
||||||
### Feats
|
### Feats
|
||||||
|
|
||||||
- `n-form`, `n-form-item` enhance show-require-mark prop,closes [#171](https://github.com/TuSimple/naive-ui/issues/171)
|
- `n-form`, `n-form-item` enhance show-require-mark prop,closes [#171](https://github.com/TuSimple/naive-ui/issues/171)
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Pending
|
## Pending
|
||||||
|
|
||||||
|
- 为 `n-input` 组件中 password 属性增加查看隐藏功能
|
||||||
|
|
||||||
### Feats
|
### Feats
|
||||||
|
|
||||||
- `n-form`, `n-form-item` 增强 show-require-mark 属性,关闭 [#171](https://github.com/TuSimple/naive-ui/issues/171)
|
- `n-form`, `n-form-item` 增强 show-require-mark 属性,关闭 [#171](https://github.com/TuSimple/naive-ui/issues/171)
|
||||||
|
@ -20,7 +20,8 @@ export default defineComponent({
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
onClick: Function as PropType<(e: MouseEvent) => void>,
|
onClick: Function as PropType<(e: MouseEvent) => void>,
|
||||||
onMousedown: Function as PropType<(e: MouseEvent) => void>
|
onMousedown: Function as PropType<(e: MouseEvent) => void>,
|
||||||
|
onMouseup: Function as PropType<(e: MouseEvent) => void>
|
||||||
},
|
},
|
||||||
setup (props) {
|
setup (props) {
|
||||||
useStyle('BaseIcon', style, toRef(props, 'clsPrefix'))
|
useStyle('BaseIcon', style, toRef(props, 'clsPrefix'))
|
||||||
@ -31,6 +32,7 @@ export default defineComponent({
|
|||||||
class={`${this.clsPrefix}-base-icon`}
|
class={`${this.clsPrefix}-base-icon`}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onMousedown={this.onMousedown}
|
onMousedown={this.onMousedown}
|
||||||
|
onMouseup={this.onMouseup}
|
||||||
role={this.role}
|
role={this.role}
|
||||||
aria-label={this.ariaLabel}
|
aria-label={this.ariaLabel}
|
||||||
aria-hidden={this.ariaHidden}
|
aria-hidden={this.ariaHidden}
|
||||||
|
28
src/_internal/icons/Eye.tsx
Normal file
28
src/_internal/icons/Eye.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { h, defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Eye',
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<path
|
||||||
|
d="M255.66 112c-77.94 0-157.89 45.11-220.83 135.33a16 16 0 0 0-.27 17.77C82.92 340.8 161.8 400 255.66 400c92.84 0 173.34-59.38 221.79-135.25a16.14 16.14 0 0 0 0-17.47C428.89 172.28 347.8 112 255.66 112z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="32"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="256"
|
||||||
|
cy="256"
|
||||||
|
r="80"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="32"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
31
src/_internal/icons/EyeOff.tsx
Normal file
31
src/_internal/icons/EyeOff.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { h, defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'EyeOff',
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<path
|
||||||
|
d="M432 448a15.92 15.92 0 0 1-11.31-4.69l-352-352a16 16 0 0 1 22.62-22.62l352 352A16 16 0 0 1 432 448z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M255.66 384c-41.49 0-81.5-12.28-118.92-36.5c-34.07-22-64.74-53.51-88.7-91v-.08c19.94-28.57 41.78-52.73 65.24-72.21a2 2 0 0 0 .14-2.94L93.5 161.38a2 2 0 0 0-2.71-.12c-24.92 21-48.05 46.76-69.08 76.92a31.92 31.92 0 0 0-.64 35.54c26.41 41.33 60.4 76.14 98.28 100.65C162 402 207.9 416 255.66 416a239.13 239.13 0 0 0 75.8-12.58a2 2 0 0 0 .77-3.31l-21.58-21.58a4 4 0 0 0-3.83-1a204.8 204.8 0 0 1-51.16 6.47z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M490.84 238.6c-26.46-40.92-60.79-75.68-99.27-100.53C349 110.55 302 96 255.66 96a227.34 227.34 0 0 0-74.89 12.83a2 2 0 0 0-.75 3.31l21.55 21.55a4 4 0 0 0 3.88 1a192.82 192.82 0 0 1 50.21-6.69c40.69 0 80.58 12.43 118.55 37c34.71 22.4 65.74 53.88 89.76 91a.13.13 0 0 1 0 .16a310.72 310.72 0 0 1-64.12 72.73a2 2 0 0 0-.15 2.95l19.9 19.89a2 2 0 0 0 2.7.13a343.49 343.49 0 0 0 68.64-78.48a32.2 32.2 0 0 0-.1-34.78z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M256 160a95.88 95.88 0 0 0-21.37 2.4a2 2 0 0 0-1 3.38l112.59 112.56a2 2 0 0 0 3.38-1A96 96 0 0 0 256 160z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M165.78 233.66a2 2 0 0 0-3.38 1a96 96 0 0 0 115 115a2 2 0 0 0 1-3.38z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
@ -7,6 +7,8 @@ export { default as CheckmarkIcon } from './Checkmark'
|
|||||||
export { default as ChevronLeftIcon } from './ChevronLeft'
|
export { default as ChevronLeftIcon } from './ChevronLeft'
|
||||||
export { default as ChevronRightIcon } from './ChevronRight'
|
export { default as ChevronRightIcon } from './ChevronRight'
|
||||||
export { default as CloseIcon } from './Close'
|
export { default as CloseIcon } from './Close'
|
||||||
|
export { default as EyeIcon } from './Eye'
|
||||||
|
export { default as EyeOffIcon } from './EyeOff'
|
||||||
export { default as TrashIcon } from './Trash'
|
export { default as TrashIcon } from './Trash'
|
||||||
export { default as DownloadIcon } from './Download'
|
export { default as DownloadIcon } from './Download'
|
||||||
export { default as EmptyIcon } from './Empty'
|
export { default as EmptyIcon } from './Empty'
|
||||||
|
@ -30,6 +30,7 @@ count
|
|||||||
| clearable | `boolean` | `false` | |
|
| clearable | `boolean` | `false` | |
|
||||||
| default-value | `string \| [string, string] \| null` | `null` | |
|
| default-value | `string \| [string, string] \| null` | `null` | |
|
||||||
| disabled | `boolean` | `false` | |
|
| disabled | `boolean` | `false` | |
|
||||||
|
| show-password-toggle | `boolean` | `false` | Controls the display and hiding of passwords |
|
||||||
| maxlength | `number` | `undefined` | |
|
| maxlength | `number` | `undefined` | |
|
||||||
| minlength | `number` | `undefined` | |
|
| minlength | `number` | `undefined` | |
|
||||||
| pair | `boolean` | `false` | Whether to input pairwise value. |
|
| pair | `boolean` | `false` | Whether to input pairwise value. |
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
# Password
|
# Password
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<n-input type="password" placeholder="Password" :maxlength="8" />
|
<n-input
|
||||||
|
type="password"
|
||||||
|
show-password-toggle
|
||||||
|
placeholder="Password"
|
||||||
|
:maxlength="8"
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
@ -30,6 +30,7 @@ count
|
|||||||
| clearable | `boolean` | `false` | |
|
| clearable | `boolean` | `false` | |
|
||||||
| default-value | `string \| [string, string] \| null` | `null` | |
|
| default-value | `string \| [string, string] \| null` | `null` | |
|
||||||
| disabled | `boolean` | `false` | |
|
| disabled | `boolean` | `false` | |
|
||||||
|
| show-password-toggle | `boolean` | `false` | 控制密码的显示隐藏 |
|
||||||
| maxlength | `number` | `undefined` | |
|
| maxlength | `number` | `undefined` | |
|
||||||
| minlength | `number` | `undefined` | |
|
| minlength | `number` | `undefined` | |
|
||||||
| pair | `boolean` | `false` | 是否输入成对的值 |
|
| pair | `boolean` | `false` | 是否输入成对的值 |
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
# 密码
|
# 密码
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<n-input type="password" placeholder="密码" :maxlength="8" />
|
<n-input
|
||||||
|
type="password"
|
||||||
|
show-password-toggle
|
||||||
|
placeholder="密码"
|
||||||
|
:maxlength="8"
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
@ -18,7 +18,8 @@ import {
|
|||||||
import { useMergedState } from 'vooks'
|
import { useMergedState } from 'vooks'
|
||||||
import { toRgbString, getAlphaString, getPadding } from 'seemly'
|
import { toRgbString, getAlphaString, getPadding } from 'seemly'
|
||||||
import { VResizeObserver } from 'vueuc'
|
import { VResizeObserver } from 'vueuc'
|
||||||
import { NBaseClear } from '../../_internal'
|
import { NBaseClear, NBaseIcon } from '../../_internal'
|
||||||
|
import { EyeIcon, EyeOffIcon } from '../../_internal/icons'
|
||||||
import { useTheme, useLocale, useFormItem, useConfig } from '../../_mixins'
|
import { useTheme, useLocale, useFormItem, useConfig } from '../../_mixins'
|
||||||
import type { ThemeProps } from '../../_mixins'
|
import type { ThemeProps } from '../../_mixins'
|
||||||
import { call, createKey, ExtractPublicPropTypes } from '../../_utils'
|
import { call, createKey, ExtractPublicPropTypes } from '../../_utils'
|
||||||
@ -75,6 +76,7 @@ const inputProps = {
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
passivelyActivated: Boolean,
|
passivelyActivated: Boolean,
|
||||||
|
showPasswordToggle: Boolean,
|
||||||
stateful: {
|
stateful: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
@ -220,6 +222,8 @@ export default defineComponent({
|
|||||||
return !!mergedValue && (hoverRef.value || mergedFocus)
|
return !!mergedValue && (hoverRef.value || mergedFocus)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// passwordVisible
|
||||||
|
const passwordVisibleRef = ref<boolean>(false)
|
||||||
// focus
|
// focus
|
||||||
const mergedFocusRef = computed(() => {
|
const mergedFocusRef = computed(() => {
|
||||||
return props.internalForceFocus || focusedRef.value
|
return props.internalForceFocus || focusedRef.value
|
||||||
@ -495,6 +499,15 @@ export default defineComponent({
|
|||||||
function handleMouseLeave (): void {
|
function handleMouseLeave (): void {
|
||||||
hoverRef.value = false
|
hoverRef.value = false
|
||||||
}
|
}
|
||||||
|
function handlePasswordToggleClick (): void {
|
||||||
|
passwordVisibleRef.value = !passwordVisibleRef.value
|
||||||
|
}
|
||||||
|
function handlePasswordToggleMousedown (e: MouseEvent): void {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
function handlePasswordToggleMouseup (e: MouseEvent): void {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
function handleWrapperKeyDown (e: KeyboardEvent): void {
|
function handleWrapperKeyDown (e: KeyboardEvent): void {
|
||||||
props.onKeydown?.(e)
|
props.onKeydown?.(e)
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
@ -630,6 +643,7 @@ export default defineComponent({
|
|||||||
// value
|
// value
|
||||||
uncontrolledValue: uncontrolledValueRef,
|
uncontrolledValue: uncontrolledValueRef,
|
||||||
mergedValue: mergedValueRef,
|
mergedValue: mergedValueRef,
|
||||||
|
passwordVisible: passwordVisibleRef,
|
||||||
mergedPlaceholder: mergedPlaceholderRef,
|
mergedPlaceholder: mergedPlaceholderRef,
|
||||||
showPlaceholder1: showPlaceholder1Ref,
|
showPlaceholder1: showPlaceholder1Ref,
|
||||||
showPlaceholder2: showPlaceholder2Ref,
|
showPlaceholder2: showPlaceholder2Ref,
|
||||||
@ -641,6 +655,9 @@ export default defineComponent({
|
|||||||
textDecorationStyle: textDecorationStyleRef,
|
textDecorationStyle: textDecorationStyleRef,
|
||||||
mergedClsPrefix: mergedClsPrefixRef,
|
mergedClsPrefix: mergedClsPrefixRef,
|
||||||
mergedBordered: mergedBorderedRef,
|
mergedBordered: mergedBorderedRef,
|
||||||
|
showPasswordToggle: computed(() => {
|
||||||
|
return props.showPasswordToggle && !props.disabled
|
||||||
|
}),
|
||||||
// methods
|
// methods
|
||||||
handleCompositionStart,
|
handleCompositionStart,
|
||||||
handleCompositionEnd,
|
handleCompositionEnd,
|
||||||
@ -655,6 +672,9 @@ export default defineComponent({
|
|||||||
handleChange,
|
handleChange,
|
||||||
handleClick,
|
handleClick,
|
||||||
handleClear,
|
handleClear,
|
||||||
|
handlePasswordToggleClick,
|
||||||
|
handlePasswordToggleMousedown,
|
||||||
|
handlePasswordToggleMouseup,
|
||||||
handleWrapperKeyDown,
|
handleWrapperKeyDown,
|
||||||
handleTextAreaMirrorResize,
|
handleTextAreaMirrorResize,
|
||||||
mergedTheme: themeRef,
|
mergedTheme: themeRef,
|
||||||
@ -852,7 +872,13 @@ export default defineComponent({
|
|||||||
<div class={`${mergedClsPrefix}-input__input`}>
|
<div class={`${mergedClsPrefix}-input__input`}>
|
||||||
<input
|
<input
|
||||||
ref="inputElRef"
|
ref="inputElRef"
|
||||||
type={this.type}
|
type={
|
||||||
|
this.type === 'password' &&
|
||||||
|
this.showPasswordToggle &&
|
||||||
|
this.passwordVisible
|
||||||
|
? 'text'
|
||||||
|
: this.type
|
||||||
|
}
|
||||||
class={`${mergedClsPrefix}-input__input-el`}
|
class={`${mergedClsPrefix}-input__input-el`}
|
||||||
tabindex={
|
tabindex={
|
||||||
this.passivelyActivated && !this.activated ? -1 : undefined
|
this.passivelyActivated && !this.activated ? -1 : undefined
|
||||||
@ -892,7 +918,10 @@ export default defineComponent({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!this.pair &&
|
{!this.pair &&
|
||||||
(this.$slots.suffix || this.clearable || this.showCount) ? (
|
(this.$slots.suffix ||
|
||||||
|
this.clearable ||
|
||||||
|
this.showCount ||
|
||||||
|
this.showPasswordToggle) ? (
|
||||||
<div class={`${mergedClsPrefix}-input__suffix`}>
|
<div class={`${mergedClsPrefix}-input__suffix`}>
|
||||||
{[
|
{[
|
||||||
renderSlot(this.$slots, 'suffix'),
|
renderSlot(this.$slots, 'suffix'),
|
||||||
@ -907,6 +936,20 @@ export default defineComponent({
|
|||||||
) : null,
|
) : null,
|
||||||
this.showCount && this.type !== 'textarea' ? (
|
this.showCount && this.type !== 'textarea' ? (
|
||||||
<WordCount />
|
<WordCount />
|
||||||
|
) : null,
|
||||||
|
this.showPasswordToggle && this.type === 'password' ? (
|
||||||
|
<NBaseIcon
|
||||||
|
clsPrefix={mergedClsPrefix}
|
||||||
|
class={`${mergedClsPrefix}-input__eye`}
|
||||||
|
onMousedown={this.handlePasswordToggleMousedown}
|
||||||
|
onMouseup={this.handlePasswordToggleMouseup}
|
||||||
|
onClick={this.handlePasswordToggleClick}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: () =>
|
||||||
|
this.passwordVisible ? <EyeIcon /> : <EyeOffIcon />
|
||||||
|
}}
|
||||||
|
</NBaseIcon>
|
||||||
) : null
|
) : null
|
||||||
]}
|
]}
|
||||||
</div>
|
</div>
|
||||||
|
@ -283,9 +283,13 @@ export default c([
|
|||||||
cE('prefix', {
|
cE('prefix', {
|
||||||
marginRight: '4px'
|
marginRight: '4px'
|
||||||
}),
|
}),
|
||||||
cE('suffix', {
|
cE('suffix', `
|
||||||
marginLeft: '4px'
|
margin-left: 4px;
|
||||||
}),
|
`, [
|
||||||
|
cE('eye', `
|
||||||
|
cursor: pointer;
|
||||||
|
`)
|
||||||
|
]),
|
||||||
cE('suffix, prefix', `
|
cE('suffix, prefix', `
|
||||||
transition: color .3s var(--bezier);
|
transition: color .3s var(--bezier);
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
Loading…
Reference in New Issue
Block a user