diff --git a/src/_utils/index.ts b/src/_utils/index.ts index a56ef3672..38aeb6e49 100644 --- a/src/_utils/index.ts +++ b/src/_utils/index.ts @@ -9,7 +9,7 @@ export { render } from './vue' export type { MaybeArray } from './vue' -export { warn, warnOnce, throwError } from './naive' +export { warn, warnOnce, throwError, smallerSize, largerSize } from './naive' export { formatLength } from './css' export { createKey } from './cssr' export * from './composable' diff --git a/src/_utils/naive/index.ts b/src/_utils/naive/index.ts index 59dad4e9d..9c7f17bf4 100644 --- a/src/_utils/naive/index.ts +++ b/src/_utils/naive/index.ts @@ -1 +1,2 @@ export { warn, warnOnce, throwError } from './warn' +export { smallerSize, largerSize } from './prop' diff --git a/src/_utils/naive/prop.ts b/src/_utils/naive/prop.ts new file mode 100644 index 000000000..953c0f355 --- /dev/null +++ b/src/_utils/naive/prop.ts @@ -0,0 +1,29 @@ +export function largerSize ( + size: 'tiny' | 'small' | 'medium' | 'large' +): 'small' | 'medium' | 'large' | 'huge' { + switch (size) { + case 'tiny': + return 'small' + case 'small': + return 'medium' + case 'medium': + return 'large' + case 'large': + return 'huge' + } +} + +export function smallerSize ( + size: 'small' | 'medium' | 'large' | 'huge' +): 'tiny' | 'small' | 'medium' | 'large' { + switch (size) { + case 'small': + return 'tiny' + case 'medium': + return 'small' + case 'large': + return 'medium' + case 'huge': + return 'large' + } +} diff --git a/src/dynamic-tags/index.js b/src/dynamic-tags/index.js deleted file mode 100644 index b3cef11e3..000000000 --- a/src/dynamic-tags/index.js +++ /dev/null @@ -1,2 +0,0 @@ -/* istanbul ignore file */ -export { default as NDynamicTags } from './src/DynamicTags.vue' diff --git a/src/dynamic-tags/index.ts b/src/dynamic-tags/index.ts new file mode 100644 index 000000000..34a3b34cd --- /dev/null +++ b/src/dynamic-tags/index.ts @@ -0,0 +1,2 @@ +/* istanbul ignore file */ +export { default as NDynamicTags } from './src/DynamicTags' diff --git a/src/dynamic-tags/src/DynamicTags.tsx b/src/dynamic-tags/src/DynamicTags.tsx new file mode 100644 index 000000000..a56902ae1 --- /dev/null +++ b/src/dynamic-tags/src/DynamicTags.tsx @@ -0,0 +1,202 @@ +import { + h, + defineComponent, + ref, + PropType, + CSSProperties, + computed, + nextTick +} from 'vue' +import commonProps from '../../tag/src/common-props' +import { AddIcon } from '../../_base/icons' +import { NButton } from '../../button' +import { InputRef, NInput } from '../../input' +import { NTag } from '../../tag' +import { NBaseIcon } from '../../_base' +import { useTheme, useFormItem, useLocale } from '../../_mixins' +import type { ThemeProps } from '../../_mixins' +import { warn, call, MaybeArray, smallerSize } from '../../_utils' +import { dynamicTagsLight } from '../styles' +import type { DynamicTagsTheme } from '../styles' + +import type { OnUpdateValue } from './interface' +import style from './styles/index.cssr' + +export default defineComponent({ + name: 'DynamicTags', + props: { + ...(useTheme.props as ThemeProps), + ...commonProps, + closable: { + type: Boolean, + default: true + }, + value: { + type: Array as PropType, + default: () => { + return [] + } + }, + tagStyle: { + type: Object as PropType, + default: () => { + return { + marginRight: '6px' + } + } + }, + inputStyle: { + type: Object as PropType, + default: () => { + return { + width: '64px' + } + } + }, + // eslint-disable-next-line vue/prop-name-casing + 'onUpdate:value': [Function, Array] as PropType>, + // deprecated + onChange: { + type: [Function, Array] as PropType< + MaybeArray | undefined + >, + validator: () => { + if (__DEV__) { + warn( + 'dynamic-tags', + '`on-change` is deprecated, please use `on-update:value` instead.' + ) + } + return true + }, + default: undefined + } + }, + setup (props) { + const { locale } = useLocale('DynamicTags') + const formItem = useFormItem(props) + const inputValueRef = ref('') + const showInputRef = ref(false) + const inputForceFocusedRef = ref(true) + const inputInstRef = ref(null) + const themeRef = useTheme( + 'DynamicTags', + 'DynamicTags', + style, + dynamicTagsLight, + props + ) + const localizedAddRef = computed(() => { + return locale.value.add + }) + const inputSizeRef = computed(() => { + return smallerSize(props.size) + }) + function doChange (value: string[]): void { + const { onChange, 'onUpdate:value': onUpdateValue } = props + const { nTriggerFormInput, nTriggerFormChange } = formItem + if (onChange) call(onChange, value) + if (onUpdateValue) call(onUpdateValue, value) + nTriggerFormInput() + nTriggerFormChange() + } + function handleCloseClick (index: number): void { + const tags = props.value.slice(0) + tags.splice(index, 1) + doChange(tags) + } + function handleInputKeyUp (e: KeyboardEvent): void { + switch (e.code) { + case 'Enter': + handleInputConfirm() + } + } + function handleInputConfirm (): void { + if (inputValueRef.value) { + const tags = props.value.slice(0) + tags.push(inputValueRef.value) + doChange(tags) + } + showInputRef.value = false + inputForceFocusedRef.value = true + inputValueRef.value = '' + } + function handleInputBlur (): void { + handleInputConfirm() + } + function handleAddClick (): void { + showInputRef.value = true + void nextTick(() => { + inputInstRef.value?.focus() + inputForceFocusedRef.value = false + }) + } + return { + inputInstRef, + localizedAdd: localizedAddRef, + inputSize: inputSizeRef, + inputValue: inputValueRef, + showInput: showInputRef, + inputForceFocused: inputForceFocusedRef, + handleInputKeyUp, + handleAddClick, + handleInputBlur, + handleCloseClick, + mergedTheme: themeRef + } + }, + render () { + const { mergedTheme } = this + return ( +
+ {this.value.map((tag, index) => ( + this.handleCloseClick(index)} + > + {{ defualt: () => tag }} + + ))} + {this.showInput ? ( + { + this.inputValue = v + }} + forceFocus={this.inputForceFocused} + unstableTheme={mergedTheme.peers.Input} + unstableThemeOverrides={mergedTheme.overrides.Input} + style={this.inputStyle} + size={this.inputSize} + placeholder="" + onKeyup={this.handleInputKeyUp} + onBlur={this.handleInputBlur} + /> + ) : ( + + {{ + icon: () => ( + {{ default: () => }} + ) + }} + + )} +
+ ) + } +}) diff --git a/src/dynamic-tags/src/DynamicTags.vue b/src/dynamic-tags/src/DynamicTags.vue deleted file mode 100644 index d6f70846d..000000000 --- a/src/dynamic-tags/src/DynamicTags.vue +++ /dev/null @@ -1,184 +0,0 @@ - - - diff --git a/src/dynamic-tags/src/interface.ts b/src/dynamic-tags/src/interface.ts new file mode 100644 index 000000000..71e0cba61 --- /dev/null +++ b/src/dynamic-tags/src/interface.ts @@ -0,0 +1 @@ +export type OnUpdateValue = (value: string[]) => void diff --git a/src/dynamic-tags/src/styles/index.cssr.js b/src/dynamic-tags/src/styles/index.cssr.ts similarity index 100% rename from src/dynamic-tags/src/styles/index.cssr.js rename to src/dynamic-tags/src/styles/index.cssr.ts diff --git a/src/dynamic-tags/styles/dark.js b/src/dynamic-tags/styles/dark.ts similarity index 72% rename from src/dynamic-tags/styles/dark.js rename to src/dynamic-tags/styles/dark.ts index 62741eeba..a0aa442c2 100644 --- a/src/dynamic-tags/styles/dark.js +++ b/src/dynamic-tags/styles/dark.ts @@ -2,8 +2,9 @@ import { tagDark } from '../../tag/styles' import { inputDark } from '../../input/styles' import { buttonDark } from '../../button/styles' import { commonDark } from '../../_styles/new-common' +import type { DynamicTagsTheme } from './light' -export default { +const dynamicTagsDark: DynamicTagsTheme = { name: 'DynamicTags', common: commonDark, peers: { @@ -12,3 +13,5 @@ export default { Tag: tagDark } } + +export default dynamicTagsDark diff --git a/src/dynamic-tags/styles/index.js b/src/dynamic-tags/styles/index.js deleted file mode 100644 index f7c25a81c..000000000 --- a/src/dynamic-tags/styles/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as dynamicTagsDark } from './dark.js' -export { default as dynamicTagsLight } from './light.js' diff --git a/src/dynamic-tags/styles/index.ts b/src/dynamic-tags/styles/index.ts new file mode 100644 index 000000000..ec7464b19 --- /dev/null +++ b/src/dynamic-tags/styles/index.ts @@ -0,0 +1,3 @@ +export { default as dynamicTagsDark } from './dark' +export { default as dynamicTagsLight } from './light' +export type { DynamicTagsTheme, DynamicTagsThemeVars } from './light' diff --git a/src/dynamic-tags/styles/light.js b/src/dynamic-tags/styles/light.ts similarity index 53% rename from src/dynamic-tags/styles/light.js rename to src/dynamic-tags/styles/light.ts index 9cbf5a281..1e88bda52 100644 --- a/src/dynamic-tags/styles/light.js +++ b/src/dynamic-tags/styles/light.ts @@ -2,8 +2,9 @@ import { tagLight } from '../../tag/styles' import { inputLight } from '../../input/styles' import { buttonLight } from '../../button/styles' import { commonLight } from '../../_styles/new-common' +import { createTheme } from '../../_mixins' -export default { +const dynamicTagsLight = createTheme({ name: 'DynamicTags', common: commonLight, peers: { @@ -11,4 +12,9 @@ export default { Button: buttonLight, Tag: tagLight } -} +}) + +export default dynamicTagsLight +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DynamicTagsThemeVars {} +export type DynamicTagsTheme = typeof dynamicTagsLight diff --git a/src/input/src/interface.ts b/src/input/src/interface.ts index aeab4ab50..f58b996bd 100644 --- a/src/input/src/interface.ts +++ b/src/input/src/interface.ts @@ -1,4 +1,4 @@ -export type Size = 'small' | 'medium' | 'large' +export type Size = 'tiny' | 'small' | 'medium' | 'large' // null is for clearable export type OnUpdateValue = <