From 3e5a392f7302b9f3612272116aa98ca5710de916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=A2=E6=9E=9C=E6=B1=81?= Date: Wed, 12 Jul 2023 10:15:04 +0800 Subject: [PATCH] docs: Form add ColorPicker demo (#42880) * docs: form add color-picker demo * test: update snapshot * fix: color-picker support form * perf: optimize code logic * refactor: optimize code * test: update snapshot * test: update snap * test: update snap * chore: fix prettier * chore: fix prettier --- components/color-picker/ColorPicker.tsx | 23 +- .../color-picker/__tests__/index.test.tsx | 48 ++- components/color-picker/color.ts | 3 + .../color-picker/hooks/useColorState.ts | 9 +- components/color-picker/interface.ts | 2 + components/color-picker/style/index.ts | 40 ++ .../__snapshots__/demo-extend.test.ts.snap | 408 ++++++++++++++++++ .../__snapshots__/demo.test.tsx.snap | 43 ++ components/form/demo/validate-other.tsx | 16 +- .../__snapshots__/demo-extend.test.ts.snap | 4 +- 10 files changed, 583 insertions(+), 13 deletions(-) diff --git a/components/color-picker/ColorPicker.tsx b/components/color-picker/ColorPicker.tsx index 11a3071429..5b0e524c3a 100644 --- a/components/color-picker/ColorPicker.tsx +++ b/components/color-picker/ColorPicker.tsx @@ -7,10 +7,12 @@ import useMergedState from 'rc-util/lib/hooks/useMergedState'; import type { CSSProperties, FC } from 'react'; import React, { useContext, useRef, useState } from 'react'; import genPurePanel from '../_util/PurePanel'; +import { getStatusClassNames } from '../_util/statusUtils'; import type { SizeType } from '../config-provider/SizeContext'; import type { ConfigConsumerProps } from '../config-provider/context'; import { ConfigContext } from '../config-provider/context'; import useSize from '../config-provider/hooks/useSize'; +import { FormItemInputContext, NoFormStyle } from '../form/context'; import type { PopoverProps } from '../popover'; import Popover from '../popover'; import theme from '../theme'; @@ -21,6 +23,7 @@ import useColorState from './hooks/useColorState'; import type { ColorFormat, ColorPickerBaseProps, + ColorValueType, PresetsItem, TriggerPlacement, TriggerType, @@ -32,8 +35,8 @@ export type ColorPickerProps = Omit< RcColorPickerProps, 'onChange' | 'value' | 'defaultValue' | 'panelRender' | 'onChangeComplete' > & { - value?: Color | string; - defaultValue?: Color | string; + value?: ColorValueType; + defaultValue?: ColorValueType; children?: React.ReactNode; open?: boolean; disabled?: boolean; @@ -114,12 +117,16 @@ const ColorPicker: CompoundedComponent = (props) => { const prefixCls = getPrefixCls('color-picker', customizePrefixCls); + // ===================== Form Status ===================== + const { status: contextStatus } = React.useContext(FormItemInputContext); + // ===================== Style ===================== const mergedSize = useSize(customizeSize); const [wrapSSR, hashId] = useStyle(prefixCls); const rtlCls = { [`${prefixCls}-rtl`]: direction }; const mergeRootCls = classNames(rootClassName, rtlCls); const mergeCls = classNames( + getStatusClassNames(prefixCls, contextStatus), { [`${prefixCls}-sm`]: mergedSize === 'small', [`${prefixCls}-lg`]: mergedSize === 'large', @@ -135,7 +142,8 @@ const ColorPicker: CompoundedComponent = (props) => { const handleChange = (data: Color, type?: HsbaColorType, pickColor?: boolean) => { let color: Color = generateColor(data); - if (colorCleared) { + const isNull = value === null || (!value && defaultValue === null); + if (colorCleared || isNull) { setColorCleared(false); const hsba = color.toHsb(); // ignore alpha slider @@ -199,7 +207,14 @@ const ColorPicker: CompoundedComponent = (props) => { } }} content={ - + + + } overlayClassName={mergePopupCls} {...popoverProps} diff --git a/components/color-picker/__tests__/index.test.tsx b/components/color-picker/__tests__/index.test.tsx index 295ee5bfd1..f32a41bf24 100644 --- a/components/color-picker/__tests__/index.test.tsx +++ b/components/color-picker/__tests__/index.test.tsx @@ -5,6 +5,7 @@ import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { waitFakeTimer } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; +import Form from '../../form'; import theme from '../../theme'; import ColorPicker from '../ColorPicker'; import type { Color } from '../color'; @@ -398,7 +399,7 @@ describe('ColorPicker', () => { expect(componentContainer).toMatchSnapshot(); }); - it('Should onChangeComplete work', async () => { + it('Should null work as expect', async () => { spyElementPrototypes(HTMLElement, { getBoundingClientRect: () => ({ x: 0, @@ -407,6 +408,51 @@ describe('ColorPicker', () => { height: 100, }), }); + const { container } = render(); + expect( + container.querySelector('.ant-color-picker-alpha-input input')?.getAttribute('value'), + ).toEqual('0%'); + expect( + container.querySelector('.ant-color-picker-hex-input input')?.getAttribute('value'), + ).toEqual('000000'); + doMouseMove(container, 0, 999); + expect( + container.querySelector('.ant-color-picker-alpha-input input')?.getAttribute('value'), + ).toEqual('100%'); + }); + + it('should support valid in form', async () => { + const Demo = () => { + const [form] = Form.useForm(); + const submit = () => { + form.validateFields(); + }; + return ( +
+ + + + +
+ ); + }; + const { container } = render(); + expect(container.querySelector('.ant-color-picker-status-error')).toBeFalsy(); + fireEvent.click(container.querySelector('button')!); + await waitFakeTimer(); + expect(container.querySelector('.ant-color-picker-status-error')).toBeTruthy(); + expect(container.querySelector('.ant-form-item-explain-error')?.innerHTML).toEqual( + 'color is required!', + ); + }); + + it('Should onChangeComplete work', async () => { const handleChangeComplete = jest.fn(); const { container } = render(); doMouseMove(container, 0, 999); diff --git a/components/color-picker/color.ts b/components/color-picker/color.ts index 47e54affac..b295eb09f2 100644 --- a/components/color-picker/color.ts +++ b/components/color-picker/color.ts @@ -19,6 +19,9 @@ export class ColorFactory { constructor(color: ColorGenInput) { this.metaColor = new RcColor(color as ColorGenInput); + if (!color) { + this.metaColor.setAlpha(0); + } } toHsb() { diff --git a/components/color-picker/hooks/useColorState.ts b/components/color-picker/hooks/useColorState.ts index 6f90556c72..3722efac67 100644 --- a/components/color-picker/hooks/useColorState.ts +++ b/components/color-picker/hooks/useColorState.ts @@ -1,18 +1,19 @@ import { useEffect, useState } from 'react'; import type { Color } from '../color'; +import type { ColorValueType } from '../interface'; import { generateColor } from '../util'; -function hasValue(value?: Color | string) { +function hasValue(value?: ColorValueType) { return value !== undefined; } const useColorState = ( - defaultStateValue: Color | string, - option: { defaultValue?: Color | string; value?: Color | string }, + defaultStateValue: ColorValueType, + option: { defaultValue?: ColorValueType; value?: ColorValueType }, ): readonly [Color, React.Dispatch>] => { const { defaultValue, value } = option; const [colorValue, setColorValue] = useState(() => { - let mergeState: string | Color | undefined; + let mergeState: ColorValueType | undefined; if (hasValue(value)) { mergeState = value; } else if (hasValue(defaultValue)) { diff --git a/components/color-picker/interface.ts b/components/color-picker/interface.ts index fd66038821..d9dd3ddb1c 100644 --- a/components/color-picker/interface.ts +++ b/components/color-picker/interface.ts @@ -33,3 +33,5 @@ export interface ColorPickerBaseProps { onFormatChange?: ColorPickerProps['onFormatChange']; onChangeComplete?: ColorPickerProps['onChangeComplete']; } + +export type ColorValueType = Color | string | null; diff --git a/components/color-picker/style/index.ts b/components/color-picker/style/index.ts index 8b55240796..b7c035b835 100644 --- a/components/color-picker/style/index.ts +++ b/components/color-picker/style/index.ts @@ -77,6 +77,45 @@ const genClearStyle = ( }; }; +const genStatusStyle = (token: ColorPickerToken): CSSObject => { + const { + componentCls, + colorError, + colorWarning, + colorErrorBorderHover, + colorWarningBorderHover, + colorErrorOutline, + colorWarningOutline, + } = token; + return { + [`&${componentCls}-status-error`]: { + borderColor: colorError, + '&:hover': { + borderColor: colorErrorBorderHover, + }, + [`&${componentCls}-trigger-active`]: { + ...genActiveStyle( + mergeToken(token, { + controlOutline: colorErrorOutline, + }), + ), + }, + }, + [`&${componentCls}-status-warning`]: { + borderColor: colorWarning, + '&:hover': { + borderColor: colorWarningBorderHover, + }, + [`&${componentCls}-trigger-active`]: { + ...genActiveStyle( + mergeToken(token, { + controlOutline: colorWarningOutline, + }), + ), + }, + }, + }; +}; const genSizeStyle = (token: ColorPickerToken): CSSObject => { const { componentCls, @@ -202,6 +241,7 @@ const genColorPickerStyle: GenerateStyle = (token) => { }, ...genClearStyle(token, controlHeightSM), ...genColorBlockStyle(token, controlHeightSM), + ...genStatusStyle(token), ...genSizeStyle(token), }, ...genRtlStyle(token), diff --git a/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap index 15e28e5f15..becaf3ca30 100644 --- a/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -20842,6 +20842,414 @@ exports[`renders components/form/demo/validate-other.tsx extend context correctl +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/form/__tests__/__snapshots__/demo.test.tsx.snap b/components/form/__tests__/__snapshots__/demo.test.tsx.snap index 0a0a2d5cfc..4934a36315 100644 --- a/components/form/__tests__/__snapshots__/demo.test.tsx.snap +++ b/components/form/__tests__/__snapshots__/demo.test.tsx.snap @@ -9703,6 +9703,49 @@ exports[`renders components/form/demo/validate-other.tsx correctly 1`] = `
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/form/demo/validate-other.tsx b/components/form/demo/validate-other.tsx index a90cd194a2..3ee2c1bc24 100644 --- a/components/form/demo/validate-other.tsx +++ b/components/form/demo/validate-other.tsx @@ -3,6 +3,7 @@ import { Button, Checkbox, Col, + ColorPicker, Form, InputNumber, Radio, @@ -40,7 +41,12 @@ const App: React.FC = () => ( name="validate_other" {...formItemLayout} onFinish={onFinish} - initialValues={{ 'input-number': 3, 'checkbox-group': ['A', 'B'], rate: 3.5 }} + initialValues={{ + 'input-number': 3, + 'checkbox-group': ['A', 'B'], + rate: 3.5, + 'color-picker': null, + }} style={{ maxWidth: 600 }} > @@ -168,7 +174,6 @@ const App: React.FC = () => ( - @@ -180,6 +185,13 @@ const App: React.FC = () => ( + + + diff --git a/components/watermark/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/watermark/__tests__/__snapshots__/demo-extend.test.ts.snap index a330ac2f0a..3465a130da 100644 --- a/components/watermark/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/watermark/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -238,7 +238,7 @@ exports[`renders components/watermark/demo/custom.tsx extend context correctly 1 class="ant-color-picker-input-container" >