From 093fa555ba207154391ac0e3626de4c469164247 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 18 May 2022 22:51:45 +0800 Subject: [PATCH] feat: input add clearIcon & status --- components/_util/createContext.ts | 13 +- components/calendar/Header.tsx | 10 +- components/form/FormItemContext.ts | 15 +- components/input/ClearableLabeledInput.tsx | 152 +------- components/input/Group.tsx | 5 + components/input/Input.tsx | 424 ++++++--------------- components/input/Search.tsx | 31 +- components/input/TextArea.tsx | 34 +- components/input/demo/basic.vue | 15 +- components/input/demo/group.vue | 34 +- components/input/demo/index.vue | 3 + components/input/demo/status.vue | 43 +++ components/input/index.en-US.md | 4 +- components/input/index.zh-CN.md | 4 +- components/input/inputProps.ts | 95 +---- components/input/style/affix.less | 4 + components/input/style/allow-clear.less | 3 +- components/input/style/index.less | 20 +- components/input/style/index.tsx | 1 + components/input/style/mixin.less | 68 +++- components/input/style/status.less | 42 ++ components/input/util.ts | 18 - components/vc-input/BaseInput.tsx | 151 ++++++++ components/vc-input/Input.tsx | 261 +++++++++++++ components/vc-input/inputProps.ts | 126 ++++++ components/vc-input/utils/commonUtils.ts | 104 +++++ components/vc-input/utils/types.ts | 3 + 27 files changed, 1088 insertions(+), 595 deletions(-) create mode 100644 components/input/demo/status.vue create mode 100644 components/input/style/status.less create mode 100644 components/vc-input/BaseInput.tsx create mode 100644 components/vc-input/Input.tsx create mode 100644 components/vc-input/inputProps.ts create mode 100644 components/vc-input/utils/commonUtils.ts create mode 100644 components/vc-input/utils/types.ts diff --git a/components/_util/createContext.ts b/components/_util/createContext.ts index 59c322f1a..2a666d3f6 100644 --- a/components/_util/createContext.ts +++ b/components/_util/createContext.ts @@ -1,9 +1,14 @@ -import { inject, provide } from 'vue'; +import { inject, provide, reactive, watchEffect } from 'vue'; -function createContext(defaultValue?: T) { +function createContext>(defaultValue?: T) { const contextKey = Symbol('contextKey'); - const useProvide = (props: T) => { - provide(contextKey, props); + const useProvide = (props: T, newProps?: T) => { + const mergedProps = reactive({} as T); + provide(contextKey, mergedProps); + watchEffect(() => { + Object.assign(mergedProps, props, newProps || {}); + }); + return mergedProps; }; const useInject = () => { return inject(contextKey, defaultValue as T) || ({} as T); diff --git a/components/calendar/Header.tsx b/components/calendar/Header.tsx index ebb749bd6..2a6d226ad 100644 --- a/components/calendar/Header.tsx +++ b/components/calendar/Header.tsx @@ -2,7 +2,7 @@ import Select from '../select'; import { Group, Button } from '../radio'; import type { CalendarMode } from './generateCalendar'; import type { Ref } from 'vue'; -import { reactive, watchEffect, defineComponent, ref } from 'vue'; +import { defineComponent, ref } from 'vue'; import type { Locale } from '../vc-picker/interface'; import type { GenerateConfig } from '../vc-picker/generate'; import { FormItemInputContext } from '../form/FormItemContext'; @@ -170,13 +170,7 @@ export default defineComponent>({ setup(_props, { attrs }) { const divRef = ref(null); const formItemInputContext = FormItemInputContext.useInject(); - const newFormItemInputContext = reactive({}); - FormItemInputContext.useProvide(newFormItemInputContext); - watchEffect(() => { - Object.assign(newFormItemInputContext, formItemInputContext, { - isFormItemInput: false, - }); - }); + FormItemInputContext.useProvide(formItemInputContext, { isFormItemInput: false }); return () => { const props = { ..._props, ...attrs }; diff --git a/components/form/FormItemContext.ts b/components/form/FormItemContext.ts index 7ebb25508..0e1cdb7ab 100644 --- a/components/form/FormItemContext.ts +++ b/components/form/FormItemContext.ts @@ -1,4 +1,4 @@ -import type { ComputedRef, InjectionKey, ConcreteComponent, FunctionalComponent } from 'vue'; +import type { ComputedRef, InjectionKey, ConcreteComponent } from 'vue'; import { watch, computed, @@ -115,7 +115,12 @@ export interface FormItemStatusContextProps { export const FormItemInputContext = createContext({}); -export const NoFormStatus: FunctionalComponent = (_, { slots }) => { - FormItemInputContext.useProvide({}); - return slots.default?.(); -}; +export const NoFormStatus = defineComponent({ + name: 'NoFormStatus', + setup(_, { slots }) { + FormItemInputContext.useProvide({}); + return () => { + return slots.default?.(); + }; + }, +}); diff --git a/components/input/ClearableLabeledInput.tsx b/components/input/ClearableLabeledInput.tsx index 74cb8e536..e43127df6 100644 --- a/components/input/ClearableLabeledInput.tsx +++ b/components/input/ClearableLabeledInput.tsx @@ -3,11 +3,14 @@ import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; import PropTypes from '../_util/vue-types'; import { cloneElement } from '../_util/vnode'; import type { PropType, VNode } from 'vue'; -import { ref, defineComponent } from 'vue'; +import { defineComponent } from 'vue'; import { tuple } from '../_util/type'; import type { Direction, SizeType } from '../config-provider'; import type { MouseEventHandler } from '../_util/EventInterface'; -import { getInputClassName, hasAddon, hasPrefixSuffix } from './util'; +import { hasAddon } from './util'; +import { FormItemInputContext } from '../form/FormItemContext'; +import type { InputStatus } from '../_util/statusUtils'; +import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; const ClearableInputType = ['text', 'input']; @@ -34,20 +37,13 @@ export default defineComponent({ bordered: { type: Boolean, default: true }, triggerFocus: { type: Function as PropType<() => void> }, hidden: Boolean, + status: String as PropType, }, setup(props, { slots, attrs }) { - const containerRef = ref(); - const onInputMouseUp: MouseEventHandler = e => { - if (containerRef.value?.contains(e.target as Element)) { - const { triggerFocus } = props; - triggerFocus?.(); - } - }; + const statusContext = FormItemInputContext.useInject(); + const renderClearIcon = (prefixCls: string) => { - const { allowClear, value, disabled, readonly, handleReset, suffix = slots.suffix } = props; - if (!allowClear) { - return null; - } + const { value, disabled, readonly, handleReset, suffix = slots.suffix } = props; const needClear = !disabled && !readonly && value; const className = `${prefixCls}-clear-icon`; return ( @@ -66,123 +62,6 @@ export default defineComponent({ /> ); }; - - const renderSuffix = (prefixCls: string) => { - const { suffix = slots.suffix?.(), allowClear } = props; - if (suffix || allowClear) { - return ( - - {renderClearIcon(prefixCls)} - {suffix} - - ); - } - return null; - }; - - const renderLabeledIcon = (prefixCls: string, element: VNode) => { - const { - focused, - value, - prefix = slots.prefix?.(), - size, - suffix = slots.suffix?.(), - disabled, - allowClear, - direction, - readonly, - bordered, - hidden, - addonAfter = slots.addonAfter, - addonBefore = slots.addonBefore, - } = props; - const suffixNode = renderSuffix(prefixCls); - if (!hasPrefixSuffix({ prefix, suffix, allowClear })) { - return cloneElement(element, { - value, - }); - } - - const prefixNode = prefix ? {prefix} : null; - - const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, { - [`${prefixCls}-affix-wrapper-focused`]: focused, - [`${prefixCls}-affix-wrapper-disabled`]: disabled, - [`${prefixCls}-affix-wrapper-sm`]: size === 'small', - [`${prefixCls}-affix-wrapper-lg`]: size === 'large', - [`${prefixCls}-affix-wrapper-input-with-clear-btn`]: suffix && allowClear && value, - [`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl', - [`${prefixCls}-affix-wrapper-readonly`]: readonly, - [`${prefixCls}-affix-wrapper-borderless`]: !bordered, - // className will go to addon wrapper - [`${attrs.class}`]: !hasAddon({ addonAfter, addonBefore }) && attrs.class, - }); - return ( - - ); - }; - - const renderInputWithLabel = (prefixCls: string, labeledElement: VNode) => { - const { - addonBefore = slots.addonBefore?.(), - addonAfter = slots.addonAfter?.(), - size, - direction, - hidden, - } = props; - // Not wrap when there is not addons - if (!hasAddon({ addonBefore, addonAfter })) { - return labeledElement; - } - - const wrapperClassName = `${prefixCls}-group`; - const addonClassName = `${wrapperClassName}-addon`; - const addonBeforeNode = addonBefore ? ( - {addonBefore} - ) : null; - const addonAfterNode = addonAfter ? {addonAfter} : null; - - const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, { - [`${wrapperClassName}-rtl`]: direction === 'rtl', - }); - - const mergedGroupClassName = classNames( - `${prefixCls}-group-wrapper`, - { - [`${prefixCls}-group-wrapper-sm`]: size === 'small', - [`${prefixCls}-group-wrapper-lg`]: size === 'large', - [`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl', - }, - attrs.class, - ); - - // Need another wrapper for changing display:table to display:inline-block - // and put style prop in wrapper - return ( - - ); - }; - const renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => { const { value, @@ -190,9 +69,13 @@ export default defineComponent({ direction, bordered, hidden, + status: customStatus, addonAfter = slots.addonAfter, addonBefore = slots.addonBefore, } = props; + + const { status: contextStatus, hasFeedback } = statusContext; + if (!allowClear) { return cloneElement(element, { value, @@ -201,6 +84,11 @@ export default defineComponent({ const affixWrapperCls = classNames( `${prefixCls}-affix-wrapper`, `${prefixCls}-affix-wrapper-textarea-with-clear-btn`, + getStatusClassNames( + `${prefixCls}-affix-wrapper`, + getMergedStatus(contextStatus, customStatus), + hasFeedback, + ), { [`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl', [`${prefixCls}-affix-wrapper-borderless`]: !bordered, @@ -209,7 +97,7 @@ export default defineComponent({ }, ); return ( -