Skip to content

Commit

Permalink
refactor: useMergeState instead of effect sync (#261)
Browse files Browse the repository at this point in the history
* refactor: simplify controlled value

* test: update test case
  • Loading branch information
zombieJ authored Jul 22, 2024
1 parent c8e7699 commit 1c979b4
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 161 deletions.
16 changes: 12 additions & 4 deletions docs/example/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@ import ColorPicker, { Color } from '@rc-component/color-picker';
import React, { useState } from 'react';
import '../../assets/index.less';

let start = true;

export default () => {
const [value, setValue] = useState(new Color('#163cff'));
const [value, setValue] = useState(new Color('rgba(255,0,0,0)'));

return (
<>
<ColorPicker
color={value}
value={value}
onChange={nextValue => {
console.log('onChange', nextValue.toHsbString(), nextValue);
setValue(nextValue);
let proxyValue = nextValue;

if (start) {
start = false;
proxyValue = nextValue.setA(1);
}

setValue(proxyValue);
}}
/>
<br />
Expand Down
252 changes: 130 additions & 122 deletions src/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const HUE_COLORS = [
},
];

export interface ColorPickerProps extends BaseColorPickerProps {
export interface ColorPickerProps extends Omit<BaseColorPickerProps, 'color'> {
value?: ColorGenInput;
defaultValue?: ColorGenInput;
className?: string;
Expand All @@ -53,130 +53,138 @@ export interface ColorPickerProps extends BaseColorPickerProps {
components?: Components;
}

export default forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) => {
const {
value,
defaultValue,
prefixCls = ColorPickerPrefixCls,
onChange,
onChangeComplete,
className,
style,
panelRender,
disabledAlpha = false,
disabled = false,
components,
} = props;

// ========================== Components ==========================
const [Slider] = useComponent(components);

// ============================ Color =============================
const [colorValue, setColorValue] = useColorState(defaultColor, {
value,
defaultValue,
});
const alphaColor = useMemo(
() => colorValue.setA(1).toRgbString(),
[colorValue],
);

// ============================ Events ============================
const handleChange: BaseColorPickerProps['onChange'] = (data, type) => {
if (!value) {
setColorValue(data);
}
onChange?.(data, type);
};

// Convert
const getHueColor = (hue: number) => new Color(colorValue.setHue(hue));

const getAlphaColor = (alpha: number) =>
new Color(colorValue.setA(alpha / 100));

// Slider change
const onHueChange = (hue: number) => {
handleChange(getHueColor(hue), 'hue');
};

const onAlphaChange = (alpha: number) => {
handleChange(getAlphaColor(alpha), 'alpha');
};

// Complete
const onHueChangeComplete = (hue: number) => {
if (onChangeComplete) {
onChangeComplete(getHueColor(hue));
}
};

const onAlphaChangeComplete = (alpha: number) => {
if (onChangeComplete) {
onChangeComplete(getAlphaColor(alpha));
}
};

// ============================ Render ============================
const mergeCls = classNames(`${prefixCls}-panel`, className, {
[`${prefixCls}-panel-disabled`]: disabled,
});

const sharedSliderProps = {
prefixCls,
disabled,
color: colorValue,
};

const defaultPanel = (
<>
<Picker
onChange={handleChange}
{...sharedSliderProps}
onChangeComplete={onChangeComplete}
/>
<div className={`${prefixCls}-slider-container`}>
<div
className={classNames(`${prefixCls}-slider-group`, {
[`${prefixCls}-slider-group-disabled-alpha`]: disabledAlpha,
})}
>
<Slider
{...sharedSliderProps}
type="hue"
colors={HUE_COLORS}
min={0}
max={359}
value={colorValue.getHue()}
onChange={onHueChange}
onChangeComplete={onHueChangeComplete}
/>
{!disabledAlpha && (
const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>(
(props, ref) => {
const {
value,
defaultValue,
prefixCls = ColorPickerPrefixCls,
onChange,
onChangeComplete,
className,
style,
panelRender,
disabledAlpha = false,
disabled = false,
components,
} = props;

// ========================== Components ==========================
const [Slider] = useComponent(components);

// ============================ Color =============================
const [colorValue, setColorValue] = useColorState(
defaultValue || defaultColor,
value,
);
const alphaColor = useMemo(
() => colorValue.setA(1).toRgbString(),
[colorValue],
);

// ============================ Events ============================
const handleChange: BaseColorPickerProps['onChange'] = (data, type) => {
if (!value) {
setColorValue(data);
}
onChange?.(data, type);
};

// Convert
const getHueColor = (hue: number) => new Color(colorValue.setHue(hue));

const getAlphaColor = (alpha: number) =>
new Color(colorValue.setA(alpha / 100));

// Slider change
const onHueChange = (hue: number) => {
handleChange(getHueColor(hue), 'hue');
};

const onAlphaChange = (alpha: number) => {
handleChange(getAlphaColor(alpha), 'alpha');
};

// Complete
const onHueChangeComplete = (hue: number) => {
if (onChangeComplete) {
onChangeComplete(getHueColor(hue));
}
};

const onAlphaChangeComplete = (alpha: number) => {
if (onChangeComplete) {
onChangeComplete(getAlphaColor(alpha));
}
};

// ============================ Render ============================
const mergeCls = classNames(`${prefixCls}-panel`, className, {
[`${prefixCls}-panel-disabled`]: disabled,
});

const sharedSliderProps = {
prefixCls,
disabled,
color: colorValue,
};

const defaultPanel = (
<>
<Picker
onChange={handleChange}
{...sharedSliderProps}
onChangeComplete={onChangeComplete}
/>
<div className={`${prefixCls}-slider-container`}>
<div
className={classNames(`${prefixCls}-slider-group`, {
[`${prefixCls}-slider-group-disabled-alpha`]: disabledAlpha,
})}
>
<Slider
{...sharedSliderProps}
type="alpha"
colors={[
{ percent: 0, color: 'rgba(255, 0, 4, 0)' },
{ percent: 100, color: alphaColor },
]}
type="hue"
colors={HUE_COLORS}
min={0}
max={100}
value={colorValue.a * 100}
onChange={onAlphaChange}
onChangeComplete={onAlphaChangeComplete}
max={359}
value={colorValue.getHue()}
onChange={onHueChange}
onChangeComplete={onHueChangeComplete}
/>
)}
{!disabledAlpha && (
<Slider
{...sharedSliderProps}
type="alpha"
colors={[
{ percent: 0, color: 'rgba(255, 0, 4, 0)' },
{ percent: 100, color: alphaColor },
]}
min={0}
max={100}
value={colorValue.a * 100}
onChange={onAlphaChange}
onChangeComplete={onAlphaChangeComplete}
/>
)}
</div>
<ColorBlock color={colorValue.toRgbString()} prefixCls={prefixCls} />
</div>
<ColorBlock color={colorValue.toRgbString()} prefixCls={prefixCls} />
</>
);

return (
<div className={mergeCls} style={style} ref={ref}>
{typeof panelRender === 'function'
? panelRender(defaultPanel)
: defaultPanel}
</div>
</>
);

return (
<div className={mergeCls} style={style} ref={ref}>
{typeof panelRender === 'function'
? panelRender(defaultPanel)
: defaultPanel}
</div>
);
});
);
},
);

if (process.env.NODE_ENV !== 'production') {
ColorPicker.displayName = 'ColorPicker';
}

export default ColorPicker;
18 changes: 16 additions & 2 deletions src/components/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Palette from './Palette';

import classNames from 'classnames';
import { useEvent } from 'rc-util';
import type { Color } from '../color';
import { Color } from '../color';
import { calculateColor, calculateOffset } from '../util';
import Gradient from './Gradient';
import Handler from './Handler';
Expand Down Expand Up @@ -71,6 +71,20 @@ const Slider: FC<BaseSliderProps> = props => {
disabledDrag: disabled,
});

const handleColor = React.useMemo(() => {
if (type === 'hue') {
const hsb = color.toHsb();
hsb.s = 1;
hsb.b = 1;
hsb.a = 1;

const lightColor = new Color(hsb);
return lightColor;
}

return color;
}, [color, type]);

// ========================= Gradient =========================
const gradientList = React.useMemo(
() => colors.map(info => `${info.color} ${info.percent}%`),
Expand All @@ -92,7 +106,7 @@ const Slider: FC<BaseSliderProps> = props => {
<Transform offset={offset} ref={transformRef}>
<Handler
size="small"
color={color.toHexString()}
color={handleColor.toHexString()}
prefixCls={prefixCls}
/>
</Transform>
Expand Down
35 changes: 7 additions & 28 deletions src/hooks/useColorState.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,20 @@
import { useEffect, useState } from 'react';
import { useMergedState } from 'rc-util';
import { useMemo } from 'react';
import type { Color } from '../color';
import type { ColorGenInput } from '../interface';
import { generateColor } from '../util';

type ColorValue = ColorGenInput | undefined;

function hasValue(value: ColorValue) {
return value !== undefined;
}

const useColorState = (
defaultStateValue: ColorValue,
option: {
defaultValue?: ColorValue;
value?: ColorValue;
},
defaultValue: ColorValue,
value?: ColorValue,
): [Color, React.Dispatch<React.SetStateAction<Color>>] => {
const { defaultValue, value } = option;
const [colorValue, setColorValue] = useState(() => {
let mergeState;
if (hasValue(value)) {
mergeState = value;
} else if (hasValue(defaultValue)) {
mergeState = defaultValue;
} else {
mergeState = defaultStateValue;
}
return generateColor(mergeState);
});
const [mergedValue, setValue] = useMergedState(defaultValue, { value });

useEffect(() => {
if (value) {
setColorValue(generateColor(value));
}
}, [value]);
const color = useMemo(() => generateColor(mergedValue), [mergedValue]);

return [colorValue, setColorValue];
return [color, setValue];
};

export default useColorState;
Loading

0 comments on commit 1c979b4

Please sign in to comment.