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
|
||||
|
||||
- `n-input` add show-password-toggle prop.
|
||||
|
||||
### Feats
|
||||
|
||||
- `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
|
||||
|
||||
- 为 `n-input` 组件中 password 属性增加查看隐藏功能
|
||||
|
||||
### Feats
|
||||
|
||||
- `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
|
||||
},
|
||||
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) {
|
||||
useStyle('BaseIcon', style, toRef(props, 'clsPrefix'))
|
||||
@ -31,6 +32,7 @@ export default defineComponent({
|
||||
class={`${this.clsPrefix}-base-icon`}
|
||||
onClick={this.onClick}
|
||||
onMousedown={this.onMousedown}
|
||||
onMouseup={this.onMouseup}
|
||||
role={this.role}
|
||||
aria-label={this.ariaLabel}
|
||||
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 ChevronRightIcon } from './ChevronRight'
|
||||
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 DownloadIcon } from './Download'
|
||||
export { default as EmptyIcon } from './Empty'
|
||||
|
@ -30,6 +30,7 @@ count
|
||||
| clearable | `boolean` | `false` | |
|
||||
| default-value | `string \| [string, string] \| null` | `null` | |
|
||||
| disabled | `boolean` | `false` | |
|
||||
| show-password-toggle | `boolean` | `false` | Controls the display and hiding of passwords |
|
||||
| maxlength | `number` | `undefined` | |
|
||||
| minlength | `number` | `undefined` | |
|
||||
| pair | `boolean` | `false` | Whether to input pairwise value. |
|
||||
|
@ -1,5 +1,10 @@
|
||||
# Password
|
||||
|
||||
```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` | |
|
||||
| default-value | `string \| [string, string] \| null` | `null` | |
|
||||
| disabled | `boolean` | `false` | |
|
||||
| show-password-toggle | `boolean` | `false` | 控制密码的显示隐藏 |
|
||||
| maxlength | `number` | `undefined` | |
|
||||
| minlength | `number` | `undefined` | |
|
||||
| pair | `boolean` | `false` | 是否输入成对的值 |
|
||||
|
@ -1,5 +1,10 @@
|
||||
# 密码
|
||||
|
||||
```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 { toRgbString, getAlphaString, getPadding } from 'seemly'
|
||||
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 type { ThemeProps } from '../../_mixins'
|
||||
import { call, createKey, ExtractPublicPropTypes } from '../../_utils'
|
||||
@ -75,6 +76,7 @@ const inputProps = {
|
||||
default: false
|
||||
},
|
||||
passivelyActivated: Boolean,
|
||||
showPasswordToggle: Boolean,
|
||||
stateful: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@ -220,6 +222,8 @@ export default defineComponent({
|
||||
return !!mergedValue && (hoverRef.value || mergedFocus)
|
||||
}
|
||||
})
|
||||
// passwordVisible
|
||||
const passwordVisibleRef = ref<boolean>(false)
|
||||
// focus
|
||||
const mergedFocusRef = computed(() => {
|
||||
return props.internalForceFocus || focusedRef.value
|
||||
@ -495,6 +499,15 @@ export default defineComponent({
|
||||
function handleMouseLeave (): void {
|
||||
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 {
|
||||
props.onKeydown?.(e)
|
||||
switch (e.code) {
|
||||
@ -630,6 +643,7 @@ export default defineComponent({
|
||||
// value
|
||||
uncontrolledValue: uncontrolledValueRef,
|
||||
mergedValue: mergedValueRef,
|
||||
passwordVisible: passwordVisibleRef,
|
||||
mergedPlaceholder: mergedPlaceholderRef,
|
||||
showPlaceholder1: showPlaceholder1Ref,
|
||||
showPlaceholder2: showPlaceholder2Ref,
|
||||
@ -641,6 +655,9 @@ export default defineComponent({
|
||||
textDecorationStyle: textDecorationStyleRef,
|
||||
mergedClsPrefix: mergedClsPrefixRef,
|
||||
mergedBordered: mergedBorderedRef,
|
||||
showPasswordToggle: computed(() => {
|
||||
return props.showPasswordToggle && !props.disabled
|
||||
}),
|
||||
// methods
|
||||
handleCompositionStart,
|
||||
handleCompositionEnd,
|
||||
@ -655,6 +672,9 @@ export default defineComponent({
|
||||
handleChange,
|
||||
handleClick,
|
||||
handleClear,
|
||||
handlePasswordToggleClick,
|
||||
handlePasswordToggleMousedown,
|
||||
handlePasswordToggleMouseup,
|
||||
handleWrapperKeyDown,
|
||||
handleTextAreaMirrorResize,
|
||||
mergedTheme: themeRef,
|
||||
@ -852,7 +872,13 @@ export default defineComponent({
|
||||
<div class={`${mergedClsPrefix}-input__input`}>
|
||||
<input
|
||||
ref="inputElRef"
|
||||
type={this.type}
|
||||
type={
|
||||
this.type === 'password' &&
|
||||
this.showPasswordToggle &&
|
||||
this.passwordVisible
|
||||
? 'text'
|
||||
: this.type
|
||||
}
|
||||
class={`${mergedClsPrefix}-input__input-el`}
|
||||
tabindex={
|
||||
this.passivelyActivated && !this.activated ? -1 : undefined
|
||||
@ -892,7 +918,10 @@ export default defineComponent({
|
||||
</div>
|
||||
)}
|
||||
{!this.pair &&
|
||||
(this.$slots.suffix || this.clearable || this.showCount) ? (
|
||||
(this.$slots.suffix ||
|
||||
this.clearable ||
|
||||
this.showCount ||
|
||||
this.showPasswordToggle) ? (
|
||||
<div class={`${mergedClsPrefix}-input__suffix`}>
|
||||
{[
|
||||
renderSlot(this.$slots, 'suffix'),
|
||||
@ -907,6 +936,20 @@ export default defineComponent({
|
||||
) : null,
|
||||
this.showCount && this.type !== 'textarea' ? (
|
||||
<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
|
||||
]}
|
||||
</div>
|
||||
|
@ -283,9 +283,13 @@ export default c([
|
||||
cE('prefix', {
|
||||
marginRight: '4px'
|
||||
}),
|
||||
cE('suffix', {
|
||||
marginLeft: '4px'
|
||||
}),
|
||||
cE('suffix', `
|
||||
margin-left: 4px;
|
||||
`, [
|
||||
cE('eye', `
|
||||
cursor: pointer;
|
||||
`)
|
||||
]),
|
||||
cE('suffix, prefix', `
|
||||
transition: color .3s var(--bezier);
|
||||
flex-wrap: nowrap;
|
||||
|
Loading…
Reference in New Issue
Block a user