tanjinzhou
4 years ago
11 changed files with 419 additions and 7 deletions
@ -0,0 +1,53 @@
|
||||
import { useInjectFormItemPrefix } from './context'; |
||||
import { VueNode } from '../_util/type'; |
||||
import { computed, defineComponent, ref, watch } from '@vue/runtime-core'; |
||||
import classNames from '../_util/classNames'; |
||||
import Transition, { getTransitionProps } from '../_util/transition'; |
||||
|
||||
export interface ErrorListProps { |
||||
errors?: VueNode[]; |
||||
/** @private Internal Usage. Do not use in your production */ |
||||
help?: VueNode; |
||||
/** @private Internal Usage. Do not use in your production */ |
||||
onDomErrorVisibleChange?: (visible: boolean) => void; |
||||
} |
||||
|
||||
export default defineComponent<ErrorListProps>({ |
||||
name: 'ErrorList', |
||||
setup(props) { |
||||
const { prefixCls, status } = useInjectFormItemPrefix(); |
||||
const visible = computed(() => props.errors && props.errors.length); |
||||
const innerStatus = ref(status.value); |
||||
// Memo status in same visible |
||||
watch([() => visible, () => status], () => { |
||||
if (visible.value && status.value) { |
||||
innerStatus.value = status.value; |
||||
} |
||||
}); |
||||
return () => { |
||||
const baseClassName = `${prefixCls.value}-item-explain`; |
||||
const transitionProps = getTransitionProps('show-help', { |
||||
onAfterLeave: () => props.onDomErrorVisibleChange?.(false), |
||||
}); |
||||
return ( |
||||
<Transition {...transitionProps}> |
||||
{visible ? ( |
||||
<div |
||||
class={classNames(baseClassName, { |
||||
[`${baseClassName}-${innerStatus}`]: innerStatus, |
||||
})} |
||||
key="help" |
||||
> |
||||
{props.errors?.map((error: any, index: number) => ( |
||||
// eslint-disable-next-line react/no-array-index-key |
||||
<div key={index} role="alert"> |
||||
{error} |
||||
</div> |
||||
))} |
||||
</div> |
||||
) : null} |
||||
</Transition> |
||||
); |
||||
}; |
||||
}, |
||||
}); |
@ -0,0 +1,108 @@
|
||||
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; |
||||
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; |
||||
import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled'; |
||||
import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled'; |
||||
|
||||
import Col, { ColProps } from '../grid/col'; |
||||
import { useProvideForm, useInjectForm, useProvideFormItemPrefix } from './context'; |
||||
import ErrorList from './ErrorList'; |
||||
import classNames from '../_util/classNames'; |
||||
import { ValidateStatus } from './FormItem'; |
||||
import { VueNode } from '../_util/type'; |
||||
import { computed, defineComponent, HTMLAttributes, onUnmounted } from 'vue'; |
||||
|
||||
interface FormItemInputMiscProps { |
||||
prefixCls: string; |
||||
errors: VueNode[]; |
||||
hasFeedback?: boolean; |
||||
validateStatus?: ValidateStatus; |
||||
onDomErrorVisibleChange: (visible: boolean) => void; |
||||
} |
||||
|
||||
export interface FormItemInputProps { |
||||
wrapperCol?: ColProps; |
||||
help?: VueNode; |
||||
extra?: VueNode; |
||||
status?: ValidateStatus; |
||||
} |
||||
|
||||
const iconMap: { [key: string]: any } = { |
||||
success: CheckCircleFilled, |
||||
warning: ExclamationCircleFilled, |
||||
error: CloseCircleFilled, |
||||
validating: LoadingOutlined, |
||||
}; |
||||
const FormItemInput = defineComponent<FormItemInputProps & FormItemInputMiscProps>({ |
||||
slots: ['help', 'extra', 'errors'], |
||||
setup(props, { slots }) { |
||||
const formContext = useInjectForm(); |
||||
const { wrapperCol: contextWrapperCol } = formContext; |
||||
|
||||
// Pass to sub FormItem should not with col info |
||||
const subFormContext = { ...formContext }; |
||||
delete subFormContext.labelCol; |
||||
delete subFormContext.wrapperCol; |
||||
useProvideForm(subFormContext); |
||||
|
||||
useProvideFormItemPrefix({ |
||||
prefixCls: computed(() => props.prefixCls), |
||||
status: computed(() => props.status), |
||||
}); |
||||
|
||||
return () => { |
||||
const { |
||||
prefixCls, |
||||
wrapperCol, |
||||
help = slots.help?.(), |
||||
errors = slots.errors?.(), |
||||
onDomErrorVisibleChange, |
||||
hasFeedback, |
||||
validateStatus, |
||||
extra = slots.extra?.(), |
||||
} = props; |
||||
const baseClassName = `${prefixCls}-item`; |
||||
|
||||
const mergedWrapperCol: ColProps & HTMLAttributes = |
||||
wrapperCol || contextWrapperCol?.value || {}; |
||||
|
||||
const className = classNames(`${baseClassName}-control`, mergedWrapperCol.class); |
||||
|
||||
onUnmounted(() => { |
||||
onDomErrorVisibleChange(false); |
||||
}); |
||||
|
||||
// Should provides additional icon if `hasFeedback` |
||||
const IconNode = validateStatus && iconMap[validateStatus]; |
||||
const icon = |
||||
hasFeedback && IconNode ? ( |
||||
<span class={`${baseClassName}-children-icon`}> |
||||
<IconNode /> |
||||
</span> |
||||
) : null; |
||||
|
||||
const inputDom = ( |
||||
<div class={`${baseClassName}-control-input`}> |
||||
<div class={`${baseClassName}-control-input-content`}>{slots.default?.()}</div> |
||||
{icon} |
||||
</div> |
||||
); |
||||
const errorListDom = ( |
||||
<ErrorList errors={errors} help={help} onDomErrorVisibleChange={onDomErrorVisibleChange} /> |
||||
); |
||||
|
||||
// If extra = 0, && will goes wrong |
||||
// 0&&error -> 0 |
||||
const extraDom = extra ? <div class={`${baseClassName}-extra`}>{extra}</div> : null; |
||||
|
||||
return ( |
||||
<Col {...mergedWrapperCol} class={className}> |
||||
{inputDom} |
||||
{errorListDom} |
||||
{extraDom} |
||||
</Col> |
||||
); |
||||
}; |
||||
}, |
||||
}); |
||||
|
||||
export default FormItemInput; |
@ -0,0 +1,92 @@
|
||||
import Col, { ColProps } from '../grid/col'; |
||||
import { FormLabelAlign } from './interface'; |
||||
import { useInjectForm } from './context'; |
||||
import { RequiredMark } from './Form'; |
||||
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver'; |
||||
import defaultLocale from '../locale/default'; |
||||
import classNames from '../_util/classNames'; |
||||
import { VueNode } from '../_util/type'; |
||||
import { FunctionalComponent, HTMLAttributes } from 'vue'; |
||||
|
||||
export interface FormItemLabelProps { |
||||
colon?: boolean; |
||||
htmlFor?: string; |
||||
label?: VueNode; |
||||
labelAlign?: FormLabelAlign; |
||||
labelCol?: ColProps & HTMLAttributes; |
||||
requiredMark?: RequiredMark; |
||||
required?: boolean; |
||||
prefixCls: string; |
||||
} |
||||
|
||||
const FormItemLabel: FunctionalComponent<FormItemLabelProps> = (props, { slots }) => { |
||||
const { prefixCls, htmlFor, labelCol, labelAlign, colon, required, requiredMark } = props; |
||||
const [formLocale] = useLocaleReceiver('Form'); |
||||
const label = props.label ?? slots.label?.(); |
||||
if (!label) return null; |
||||
const { |
||||
vertical, |
||||
labelAlign: contextLabelAlign, |
||||
labelCol: contextLabelCol, |
||||
colon: contextColon, |
||||
} = useInjectForm(); |
||||
const mergedLabelCol: FormItemLabelProps['labelCol'] = labelCol || contextLabelCol?.value || {}; |
||||
|
||||
const mergedLabelAlign: FormLabelAlign | undefined = labelAlign || contextLabelAlign?.value; |
||||
|
||||
const labelClsBasic = `${prefixCls}-item-label`; |
||||
const labelColClassName = classNames( |
||||
labelClsBasic, |
||||
mergedLabelAlign === 'left' && `${labelClsBasic}-left`, |
||||
mergedLabelCol.class, |
||||
); |
||||
|
||||
let labelChildren = label; |
||||
// Keep label is original where there should have no colon |
||||
const computedColon = colon === true || (contextColon?.value !== false && colon !== false); |
||||
const haveColon = computedColon && !vertical.value; |
||||
// Remove duplicated user input colon |
||||
if (haveColon && typeof label === 'string' && (label as string).trim() !== '') { |
||||
labelChildren = (label as string).replace(/[:|:]\s*$/, ''); |
||||
} |
||||
|
||||
labelChildren = ( |
||||
<> |
||||
{labelChildren} |
||||
{slots.tooltip?.({ class: `${prefixCls}-item-tooltip` })} |
||||
</> |
||||
); |
||||
|
||||
// Add required mark if optional |
||||
if (requiredMark === 'optional' && !required) { |
||||
labelChildren = ( |
||||
<> |
||||
{labelChildren} |
||||
<span class={`${prefixCls}-item-optional`}> |
||||
{formLocale.value?.optional || defaultLocale.Form?.optional} |
||||
</span> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
const labelClassName = classNames({ |
||||
[`${prefixCls}-item-required`]: required, |
||||
[`${prefixCls}-item-required-mark-optional`]: requiredMark === 'optional', |
||||
[`${prefixCls}-item-no-colon`]: !computedColon, |
||||
}); |
||||
return ( |
||||
<Col {...mergedLabelCol} class={labelColClassName}> |
||||
<label |
||||
html-for={htmlFor} |
||||
class={labelClassName} |
||||
title={typeof label === 'string' ? label : ''} |
||||
> |
||||
{labelChildren} |
||||
</label> |
||||
</Col> |
||||
); |
||||
}; |
||||
|
||||
FormItemLabel.displayName = 'FormItemLabel'; |
||||
|
||||
export default FormItemLabel; |
@ -0,0 +1,49 @@
|
||||
import { inject, InjectionKey, provide, ComputedRef, computed } from 'vue'; |
||||
import { ColProps } from '../grid'; |
||||
import { RequiredMark } from './Form'; |
||||
import { ValidateStatus } from './FormItem'; |
||||
import { FormLabelAlign } from './interface'; |
||||
|
||||
export interface FormContextProps { |
||||
vertical: ComputedRef<boolean>; |
||||
name?: ComputedRef<string>; |
||||
colon?: ComputedRef<boolean>; |
||||
labelAlign?: ComputedRef<FormLabelAlign>; |
||||
labelCol?: ComputedRef<ColProps>; |
||||
wrapperCol?: ComputedRef<ColProps>; |
||||
requiredMark?: ComputedRef<RequiredMark>; |
||||
//itemRef: (name: (string | number)[]) => (node: React.ReactElement) => void;
|
||||
} |
||||
|
||||
export const FormContextKey: InjectionKey<FormContextProps> = Symbol('formContextKey'); |
||||
|
||||
export const useProvideForm = (state: FormContextProps) => { |
||||
provide(FormContextKey, state); |
||||
}; |
||||
|
||||
export const useInjectForm = () => { |
||||
return inject(FormContextKey, { |
||||
labelAlign: computed(() => 'right' as FormLabelAlign), |
||||
vertical: computed(() => false), |
||||
}); |
||||
}; |
||||
|
||||
/** Used for ErrorList only */ |
||||
export interface FormItemPrefixContextProps { |
||||
prefixCls: ComputedRef<string>; |
||||
status?: ComputedRef<ValidateStatus>; |
||||
} |
||||
|
||||
export const FormItemPrefixContextKey: InjectionKey<FormItemPrefixContextProps> = Symbol( |
||||
'formItemPrefixContextKey', |
||||
); |
||||
|
||||
export const useProvideFormItemPrefix = (state: FormItemPrefixContextProps) => { |
||||
provide(FormItemPrefixContextKey, state); |
||||
}; |
||||
|
||||
export const useInjectFormItemPrefix = () => { |
||||
return inject(FormItemPrefixContextKey, { |
||||
prefixCls: computed(() => ''), |
||||
}); |
||||
}; |
Loading…
Reference in new issue