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`] = `
+