Selaa lähdekoodia

Merge branch 'plus/colorPicker' into release

DaiQiangReal 1 vuosi sitten
vanhempi
sitoutus
c0381dacb9
28 muutettua tiedostoa jossa 1853 lisäystä ja 9 poistoa
  1. 143 0
      content/input/colorpicker/index.md
  2. 62 0
      packages/semi-foundation/colorPicker/AlphaSliderFoundation.ts
  3. 86 0
      packages/semi-foundation/colorPicker/ColorChooseAreaFoundation.ts
  4. 61 0
      packages/semi-foundation/colorPicker/ColorSliderFoundation.ts
  5. 113 0
      packages/semi-foundation/colorPicker/DataPartFoundation.ts
  6. 113 0
      packages/semi-foundation/colorPicker/colorPicker.scss
  7. 11 0
      packages/semi-foundation/colorPicker/constants.ts
  8. 206 0
      packages/semi-foundation/colorPicker/foundation.ts
  9. 51 0
      packages/semi-foundation/colorPicker/interface.ts
  10. 228 0
      packages/semi-foundation/colorPicker/utils/convert.ts
  11. 3 0
      packages/semi-foundation/colorPicker/utils/round.ts
  12. 40 0
      packages/semi-foundation/colorPicker/utils/split.ts
  13. 28 0
      packages/semi-foundation/colorPicker/variables.scss
  14. 25 0
      packages/semi-icons/src/icons/IconEyedropper.tsx
  15. 25 0
      packages/semi-icons/src/icons/IconStorysStroked.tsx
  16. 2 1
      packages/semi-icons/src/icons/index.ts
  17. 3 1
      packages/semi-icons/src/svgs/eye_opened.svg
  18. 3 0
      packages/semi-icons/src/svgs/eyedropper.svg
  19. 6 6
      packages/semi-scss-compile/src/utils/writeFile.ts
  20. 97 0
      packages/semi-ui/colorPicker/AlphaSlider/index.tsx
  21. 109 0
      packages/semi-ui/colorPicker/ColorChooseArea/index.tsx
  22. 91 0
      packages/semi-ui/colorPicker/ColorSlider/index.tsx
  23. 119 0
      packages/semi-ui/colorPicker/DataPart/index.tsx
  24. 47 0
      packages/semi-ui/colorPicker/_story/colorPicker.stories.jsx
  25. 14 0
      packages/semi-ui/colorPicker/_story/colorPicker.stories.tsx
  26. 165 0
      packages/semi-ui/colorPicker/index.tsx
  27. 1 0
      packages/semi-ui/index.ts
  28. 1 1
      packages/semi-ui/tsconfig.json

+ 143 - 0
content/input/colorpicker/index.md

@@ -0,0 +1,143 @@
+---
+localeCode: zh-CN
+order: 0
+category: 输入类
+title: ColorPicker 颜色选择器
+icon: doc-checkbox
+brief: 快速便捷地选择颜色,并提供滴管工具取色
+---
+
+
+
+## 代码演示
+
+### 如何引入
+
+
+```jsx import
+import { ColorPicker } from '@douyinfe/semi-ui';
+```
+
+
+### 基本用法
+
+#### 放在弹层
+
+```jsx live=true
+import { ColorPicker, Button } from '@douyinfe/semi-ui';
+function Demo(){
+    return <div>
+        <ColorPicker alpha={true} onChange={value=>{console.log(value)}} usePopover={true}/>
+        
+        <br/>
+        <div>自定义 trigger</div>
+
+        <ColorPicker alpha={true} onChange={value=>{console.log(value)}} usePopover={true}>
+            <Button> Trigger </Button>
+        </ColorPicker>
+        
+    </div>
+}
+
+```
+
+#### 正常展示
+```jsx live=true
+import { ColorPicker } from '@douyinfe/semi-ui';
+function Demo(){
+    return <ColorPicker alpha={true} onChange={value=>{console.log(value)}}/>
+}
+
+```
+
+### 滴管取色器
+
+使用 `eyeDropper={true}` 开启滴管功能,支持从浏览器内或外部软件屏幕取色。
+
+<Notice title='注意事项'>
+开启此功能需要当前网页部署在 HTTPS 或 localhost 域名等安全 context 下,否则无效果。需用户浏览器版本 Chromium > 95
+</Notice>
+
+
+```jsx live=true
+import { ColorPicker } from '@douyinfe/semi-ui';
+function Demo(){
+    return <ColorPicker alpha={true} eyeDropper={true} onChange={value=>{console.log(value)}}/>
+}
+
+```
+
+### 默认值
+在进行各种颜色表示格式之间相互转换时,部分格式之间存在理论误差,因此 onChange 返回给你的值是同时包含了 hsva hex rgba 三种格式的色值的对象。
+
+你传入的 defaultValue(非受控) 和 value(受控) 也应当是同样包含三种格式的对象。
+
+我们在组件类上提供了静态工具函数 `colorStringToValue`,用于将常见颜色字符串转换为该对象,支持 rgb(57,197,187) #39c5bb hsv(176,71,77) 等字符串直接传入。
+
+```jsx live=true
+import { ColorPicker } from '@douyinfe/semi-ui';
+function Demo(){
+    return <div>
+        <ColorPicker 
+            defaultValue={ColorPicker.colorStringToValue("rgb(57,197,187)")}
+            onChange={(value)=>{
+            console.log(value)
+        }} className={""} alpha={true}/>
+    </div>
+
+}
+
+```
+
+### 受控
+
+通过传入 value 来受控使用
+
+```jsx live=true
+import { ColorPicker } from '@douyinfe/semi-ui';
+function Demo(){
+    const [value,setValue] = useState(ColorPicker.colorStringToValue("#39c5bb"));
+    console.log(value);
+    return <div>
+        <ColorPicker value={value} onChange={(value)=>{
+            setValue(value)
+        }} className={""} alpha={true}/>
+    </div>
+
+}
+
+```
+
+
+### 顶部和底部渲染额外元素
+
+使用 `topSlot` 和 `bottomSlot` 在顶部和底部渲染额外元素
+
+```jsx live=true
+import { ColorPicker } from '@douyinfe/semi-ui';
+function Demo(){
+    return <ColorPicker topSlot={<div>
+        TopSlot
+    </div>} bottomSlot={<div>Bottom Slot</div>} alpha={true} onChange={value=>{console.log(value)}}/>
+}
+
+```
+
+### API 参考
+
+| 参数            | 说明         | 类型            | 默认值  |
+|---------------|------------|---------------|------|
+| onChange | 用户选中颜色的回调 | (value)=>void | - |
+| alpha         | 是否开启透明度选择  | boolean       | true |
+| bottomSlot | 底部渲染额外元素 | ReactNode | - |
+| className | 类名 | string | - |
+| defaultFormat | 默认手动输入时的格式 | rgba hex hsva | hex  |
+| defaultValue  | 默认值        | Object        | -    |
+| eyeDropper    | 是否开启滴管拾色器  | boolean       | true |
+| height | 高度 | number | 280 |
+| style | 样式 | CSSProperties | - | 
+| topSlot | 顶部渲染额外元素 | ReactNode | - |
+| width         | 宽度         | number        | 280  |
+| usePopover | 是否放入Popover渲染 | boolean | false |
+| popoverProps | 放入 Popover 时,Popover 传入的 props | Popover Props | - |
+

+ 62 - 0
packages/semi-foundation/colorPicker/AlphaSliderFoundation.ts

@@ -0,0 +1,62 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+import ColorPickerFoundation, { ColorPickerAdapter, ColorPickerProps, ColorPickerState } from "./foundation";
+import { HsvaColor } from "./interface";
+
+export interface AlphaSliderBaseProps {
+    width: number;
+    height: number;
+    hsva: HsvaColor;
+    handleSize: number;
+    foundation: ColorPickerFoundation
+}
+
+export interface AlphaSliderBaseState {
+    handlePosition: number;
+    isHandleGrabbing: boolean
+}
+
+
+export interface AlphaSliderAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    handleMouseDown: (e: any) => void;
+    handleMouseUp: (e: any) => void;
+    getColorPickerFoundation: () => ColorPickerFoundation;
+    getDOM: () => HTMLDivElement
+}
+
+
+class AlphaSliderFoundation extends BaseFoundation<AlphaSliderAdapter<AlphaSliderBaseProps, AlphaSliderBaseState>, AlphaSliderBaseProps, AlphaSliderBaseState> {
+
+    constructor(adapter: AlphaSliderAdapter<AlphaSliderBaseProps, AlphaSliderBaseState>) {
+        super({
+            ...adapter
+        });
+    }
+
+    handleMouseDown = (e: any) => {
+        this._adapter.handleMouseDown(e);
+    }
+
+
+    handleMouseUp = (e: any) => {
+        this._adapter.handleMouseUp(e);
+    }
+
+
+    setHandlePositionByMousePosition = (e: MouseEvent) => {
+        const rect = this._adapter.getDOM()?.getBoundingClientRect();
+        if (!rect) {
+            return;
+        }
+        const { width, handleSize } = this._adapter.getProps();
+        const colorPickerFoundation = this._adapter.getColorPickerFoundation();
+        const mousePosition = e.clientX - rect.x;
+        const handlePosition = colorPickerFoundation.getAlphaHandlePositionByMousePosition(mousePosition, width, handleSize);
+        colorPickerFoundation.handleAlphaChangeByHandle({ a: Number((Math.min(Math.max(mousePosition / width, 0), 1)).toFixed(2)) });
+        this.setState({ handlePosition });
+    }
+
+
+}
+
+
+export default AlphaSliderFoundation;

+ 86 - 0
packages/semi-foundation/colorPicker/ColorChooseAreaFoundation.ts

@@ -0,0 +1,86 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+import ColorPickerFoundation from "./foundation";
+import { HsvaColor } from "./interface";
+
+
+export interface ColorChooseAreaBaseProps {
+    hsva: HsvaColor;
+    onChange: (newColor: { s: number; v: number }) => void;
+    handleSize: number;
+    width: number;
+    height: number;
+    foundation: ColorPickerFoundation
+}
+
+export interface ColorChooseAreaBaseState {
+    handlePosition: { x: number; y: number };
+    isHandleGrabbing: boolean
+}
+
+
+export interface ColorChooseAreaAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    getColorPickerFoundation: () => ColorPickerFoundation;
+    handleMouseDown: (e: any) => void;
+    handleMouseUp: (e: any) => void;
+    getDOM: () => HTMLDivElement;
+    notifyChange: (newColor: { s: number; v: number }) => void
+}
+
+
+class ColorChooseAreaFoundation extends BaseFoundation<ColorChooseAreaAdapter<ColorChooseAreaBaseProps, ColorChooseAreaBaseState>, ColorChooseAreaBaseProps, ColorChooseAreaBaseState> {
+
+    constructor(adapter: ColorChooseAreaAdapter<ColorChooseAreaBaseProps, ColorChooseAreaBaseState>) {
+        super({
+            ...adapter
+        });
+    }
+
+    getHandlePositionByHSVA = () => {
+        const { hsva, width, height, handleSize } = this.getProps();
+
+        return this._adapter.getColorPickerFoundation().getHandlePositionByHSVA(hsva, {
+            width: width,
+            height: height
+        }, handleSize);
+
+    }
+
+    handleMouseDown = (e: any) => {
+        this._adapter.handleMouseDown(e);
+    }
+
+    handleMouseUp = (e: any) => {
+        this._adapter.handleMouseUp(e);
+    }
+
+
+    setHandlePositionByMousePosition = (e: globalThis.MouseEvent) => {
+        const rect = this._adapter.getDOM()?.getBoundingClientRect();
+        if (!rect) {
+            return;
+        }
+        const mousePosition = {
+            x: e.clientX - rect.x,
+            y: e.clientY - rect.y
+        };
+        const { width, height, handleSize } = this.getProps();
+        const colorPickerFoundation = this._adapter.getColorPickerFoundation();
+        const handlePosition = colorPickerFoundation.getHandlePositionByMousePosition(mousePosition, {
+            width,
+            height
+        }, handleSize);
+        if (handlePosition) {
+            this.setState({ handlePosition });
+            this._adapter.notifyChange({
+                s: Math.round(mousePosition.x / width * 100),
+                v: Math.round(100 - (Math.min(Math.max(mousePosition.y / height, 0), 1)) * 100),
+            });
+        }
+
+    }
+
+
+}
+
+
+export default ColorChooseAreaFoundation;

+ 61 - 0
packages/semi-foundation/colorPicker/ColorSliderFoundation.ts

@@ -0,0 +1,61 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+import ColorPickerFoundation from "./foundation";
+
+export interface ColorSliderBaseProps {
+    width: number;
+    height: number;
+    hue: number;
+    handleSize: number;
+    foundation: ColorPickerFoundation
+}
+
+export interface ColorSliderBaseState {
+    handlePosition: number;
+    isHandleGrabbing: boolean
+}
+
+
+export interface ColorSliderAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    handleMouseDown: (e: any) => void;
+    handleMouseUp: (e: any) => void;
+    getColorPickerFoundation: () => ColorPickerFoundation;
+    getDOM: () => HTMLDivElement
+}
+
+
+class ColorSliderFoundation extends BaseFoundation<ColorSliderAdapter<ColorSliderBaseProps, ColorSliderBaseState>, ColorSliderBaseProps, ColorSliderBaseState> {
+
+    constructor(adapter: ColorSliderAdapter<ColorSliderBaseProps, ColorSliderBaseState>) {
+        super({
+            ...adapter
+        });
+    }
+
+    handleMouseDown = (e: any) => {
+        this._adapter.handleMouseDown(e);
+    }
+
+
+    handleMouseUp = (e: any) => {
+        this._adapter.handleMouseUp(e);
+    }
+
+
+    setHandlePositionByMousePosition = (e: MouseEvent) => {
+        const rect = this._adapter.getDOM()?.getBoundingClientRect();
+        if (!rect) {
+            return;
+        }
+        const { width, handleSize } = this._adapter.getProps();
+        const colorPickerFoundation = this._adapter.getColorPickerFoundation();
+        const mousePosition = e.clientX - rect.x;
+        colorPickerFoundation.handleColorChangeByHandle({ h: Math.round(Math.min(Math.max(mousePosition / width, 0), 1) * 360) });
+        const handlePosition = colorPickerFoundation.getColorHandlePositionByMousePosition(mousePosition, width, handleSize);
+        this.setState({ handlePosition });
+    }
+
+
+}
+
+
+export default ColorSliderFoundation;

+ 113 - 0
packages/semi-foundation/colorPicker/DataPartFoundation.ts

@@ -0,0 +1,113 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+import ColorPickerFoundation, { ColorPickerProps } from "./foundation";
+import split from "./utils/split";
+import { HsvaColor, RgbaColor } from "./interface";
+
+
+type Value = ColorPickerProps['value']
+
+export interface DataPartBaseProps {
+    currentColor: Value;
+    defaultFormat: 'hex' | 'rgba' | 'hsva';
+    width: number;
+    alpha?: boolean;
+    foundation: ColorPickerFoundation;
+    eyeDropper: boolean
+}
+
+export interface DataPartBaseState {
+    format: 'hex' | 'rgba' | 'hsva';
+    inputValue: string
+}
+
+
+export interface DataPartAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    getColorPickerFoundation: () => ColorPickerFoundation
+}
+
+
+class DataPartFoundation extends BaseFoundation<DataPartAdapter<DataPartBaseProps, DataPartBaseState>, DataPartBaseProps, DataPartBaseState> {
+
+    constructor(adapter: DataPartAdapter<DataPartBaseProps, DataPartBaseState>) {
+        super({
+            ...adapter
+        });
+    }
+
+    getInputValue = () => {
+        const { currentColor } = this._adapter.getProps();
+        const { format } = this._adapter.getStates();
+        const rgba = currentColor.rgba;
+        const hsva = currentColor.hsva;
+        const hex = currentColor.hex;
+        if (format === 'rgba') {
+            return `${rgba.r},${rgba.g},${rgba.b}`;
+        } else if (format === 'hsva') {
+            return `${hsva.h},${hsva.s},${hsva.v}`;
+        } else {
+            return hex.slice(0, 7);
+        }
+    }
+
+    getValueByInputValue = (value: string) => {
+        const { format } = this.getStates();
+        if (format === 'rgba') {
+            const result = split(value, format);
+            if (result) {
+                return result as RgbaColor;
+            }
+
+        } else if (format === 'hsva') {
+            const result = split(value, format);
+            if (result) {
+                return result as HsvaColor;
+            }
+        } else if (format === 'hex') {
+            // hack chrome bug, format mismatch with w3c.
+            if (!value.startsWith('#')) {
+                value = '#' + value;
+            }
+            if (/#[\d\w]{6,8}/.test(value)) {
+                return value;
+            }
+        }
+        return false;
+    }
+
+    handlePickValueWithStraw = async () => {
+        const colorPickerFoundation = this._adapter.getColorPickerFoundation();
+        if (!window['EyeDropper']) {
+            return;
+        }
+        //@ts-ignore
+        const eyeDropper = new EyeDropper();
+
+        try {
+            const result = await eyeDropper.open();
+            const color = result['sRGBHex'];
+            if (color.startsWith("#")) {
+                colorPickerFoundation.handleChange(color, 'hex');
+            } else if (color.startsWith('rgba')) {
+                const rgba = ColorPickerFoundation.rgbaStringToRgba(color);
+                rgba.a = 1;
+                colorPickerFoundation.handleChange(rgba, 'rgba');
+            }
+        } catch (e) {
+
+        }
+    }
+
+
+    handleInputValueChange = (value: string) => {
+        this._adapter.setState({ inputValue: value });
+    }
+
+    handleFormatChange = (format: DataPartBaseState['format']) => {
+        this._adapter.setState({ format });
+    }
+
+
+}
+
+
+export default DataPartFoundation;

+ 113 - 0
packages/semi-foundation/colorPicker/colorPicker.scss

@@ -0,0 +1,113 @@
+@import "./variables.scss";
+
+
+$module: #{$prefix}-colorPicker;
+
+
+.#{$module} {
+
+    &-colorChooseArea{
+
+        /**
+         *  Referrer from https://github.com/web-padawan/vanilla-colorful/blob/5d219ee360ae2f29534864b28ca9e6074233b9ce/src/lib/styles/saturation.css
+         */
+        box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
+        position: relative;
+        flex-grow: 1;
+        border-color: transparent;
+        border-radius: $radius-colorPicker-topLeft $radius-colorPicker-topRight $radius-colorPicker-bottomLeft $radius-colorPicker-bottomRight;
+        background-image: linear-gradient(to top, #000, rgba(0, 0, 0, 0)), linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
+    }
+
+
+    &-handle{
+        border-radius: $radius-colorPicker-handle;
+        border: $width-colorPicker_handle-border solid $color-colorPicker_handle-border;
+        position: absolute;
+        box-sizing: border-box;
+        cursor: grab;
+    }
+
+
+
+
+    &-alphaSlider{
+        position: relative;
+        cursor: pointer;
+        margin-top: $spacing-colorPicker_slider-marginTop;
+        border-radius: $radius-colorPicker-handle;
+        background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><rect x="8" width="8" height="8"/><rect y="8" width="8" height="8"/></svg>');
+
+
+        .#{$module}-alphaSliderInner{
+            width: 100%;
+            height: 100%;
+            border-radius: $radius-colorPicker-alphaSliderInner;
+        }
+
+        .#{$module}-alphaHandle{
+            background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><rect x="8" width="8" height="8"/><rect y="8" width="8" height="8"/></svg>');
+            border-radius: $radius-colorPicker-handle;
+            border: $width-colorPicker_handle-border solid $color-colorPicker_handle-border;
+            position: absolute;
+            box-sizing: border-box;
+            cursor: grab;
+
+        }
+
+    }
+
+    &-colorSlider{
+        position: relative;
+        cursor: pointer;
+        margin-top: $spacing-colorPicker_slider-marginTop;
+        border-radius: $radius-colorPicker-handle;
+        background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
+    }
+
+
+
+    &-dataPart{
+        margin-top: $spacing-colorPicker_dataPart-marginTop;
+        display: flex;
+        align-items: center;
+        .#{$module}-colorDemoBlock{
+            border-radius: $radius-colorPicker-demoBlock;
+        }
+        .#{$module}-inputGroup{
+            margin-left: $spacing-colorPicker_inputGroup-marginLeft;
+            width: 100%;
+            flex:1;
+            flex-wrap: nowrap;
+            .#{$module}-colorPickerInput{
+                flex: 1;
+            }
+            .#{$module}-colorPickerInputNumber{
+                width: $width-colorPicker-colorPickerInputNumber;
+                .#{$module}-inputNumberSuffix{
+                    font-size: $font-colorPicker_inputNumberSuffix-fontSize;
+                    padding: $spacing-colorPicker_inputNumberSuffix-vertical $spacing-colorPicker_inputNumberSuffix-horizontal;
+                }
+            }
+        }
+        .#{$module}-formatSelect{
+            width: $width-colorPicker-formatSelect;
+        }
+    }
+
+
+    &-popover{
+        padding: $spacing-colorPicker_popover-padding;
+
+        &-defaultChildren{
+            width:$width-colorPicker-defaultTrigger;
+            height: $width-colorPicker-defaultTrigger;
+            border-radius: $radius-colorPicker-defaultTrigger;
+            cursor: pointer;
+        }
+
+    }
+
+
+
+}

+ 11 - 0
packages/semi-foundation/colorPicker/constants.ts

@@ -0,0 +1,11 @@
+import { BASE_CLASS_PREFIX } from '../base/constants';
+
+const cssClasses = {
+    PREFIX: `${BASE_CLASS_PREFIX}-colorPicker`,
+};
+
+const strings = {
+
+};
+
+export { cssClasses };

+ 206 - 0
packages/semi-foundation/colorPicker/foundation.ts

@@ -0,0 +1,206 @@
+import { HsvaColor, RgbaColor } from './interface';
+import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import {
+    hexToHsva,
+    hexToRgba,
+    hsvaToHex,
+    hsvaToHslaString,
+    hsvaToHslString,
+    hsvaToRgba, rgbaStringToHsva, rgbaStringToRgba,
+    rgbaToHex,
+    rgbaToHsva,
+} from './utils/convert';
+
+
+export type ColorValue = {
+    hsva: HsvaColor;
+    rgba: RgbaColor;
+    hex: string
+}
+export interface ColorPickerProps {
+    eyeDropper?: boolean;
+    defaultValue?: ColorValue;
+    value?: ColorValue;
+    onChange: (value: ColorValue) => void;
+    alpha: boolean;
+    width?: number;
+    height?: number;
+    defaultFormat: 'hex' | 'rgba' | 'hsva'
+}
+
+export interface ColorPickerState {
+    currentColor: ColorValue
+}
+
+
+export interface ColorPickerAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    notifyChange: (value: ColorValue) => void
+}
+
+
+class ColorPickerFoundation extends BaseFoundation<ColorPickerAdapter<ColorPickerProps, ColorPickerState>, ColorPickerProps, ColorPickerState> {
+
+    constructor(adapter: ColorPickerAdapter<ColorPickerProps, ColorPickerState>) {
+        super({
+            ...adapter
+        });
+    }
+
+    static hsvaToRgba = hsvaToRgba
+    static rgbaToHsva = rgbaToHsva
+    static rgbaToHex = rgbaToHex
+    static hsvaToHex = hsvaToHex
+    static hexToRgba = hexToRgba
+    static hexToHsva = hexToHsva
+    static hsvaToHslaString = hsvaToHslaString
+    static hsvaToHslString = hsvaToHslString
+    static rgbaStringToHsva = rgbaStringToHsva
+    static rgbaStringToRgba = rgbaStringToRgba
+
+
+    handleChangeH = (currentColor: ColorValue, newH: number) => {
+
+        const hsva = {
+            ...currentColor.hsva,
+            h: newH
+        };
+        const rgba = hsvaToRgba(hsva);
+        const hex = hsvaToHex(hsva);
+
+        const newCurrentColor = {
+            rgba,
+            hsva,
+            hex
+        };
+
+        this._adapter.notifyChange(newCurrentColor);
+        if (!this.getProp("value")) {
+            this._adapter.setState({ currentColor: newCurrentColor });
+        }
+
+    }
+
+
+    handleChangeA = (currentColor: ColorValue, newAlpha: number) => {
+        let alpha = this._adapter.getProp('alpha');
+        if (!alpha) {
+            newAlpha = 1;
+        }
+        const rgba = {
+            ...currentColor.rgba,
+            a: newAlpha
+        };
+        const hex = rgbaToHex(rgba);
+        currentColor = {
+            rgba,
+            hex: alpha ? hex : hex.slice(0, 7),
+            hsva: {
+                ...currentColor.hsva,
+                a: newAlpha
+            }
+        };
+        this._adapter.notifyChange(currentColor);
+        if (!this.getProp("value")) {
+            this._adapter.setState({ currentColor: currentColor });
+        }
+
+    }
+
+    getCurrentColor = ()=>{
+
+        const value = this.getProp("value");
+        const currentColor = this.getState("currentColor");
+        return value || currentColor;
+    }
+
+    handleChange = (color: HsvaColor|RgbaColor|string, format: 'hex'|'rgba'|'hsva')=>{
+        let currentColor;
+
+        if (format === 'hsva') {
+            currentColor = {
+                hsva: color as HsvaColor,
+                rgba: ColorPickerFoundation.hsvaToRgba(color as HsvaColor),
+                hex: ColorPickerFoundation.hsvaToHex(color as HsvaColor)
+            };
+        } else if (format === 'rgba') {
+            currentColor = {
+                rgba: color as RgbaColor,
+                hsva: ColorPickerFoundation.rgbaToHsva(color as RgbaColor),
+                hex: ColorPickerFoundation.rgbaToHex(color as RgbaColor)
+            };
+        } else if (format === 'hex') {
+            currentColor = {
+                hex: color as string,
+                hsva: ColorPickerFoundation.hexToHsva(color as string),
+                rgba: ColorPickerFoundation.hexToRgba(color as string)
+            };
+        } else {
+            throw new Error('format error');
+        }
+
+        this._adapter.notifyChange(currentColor);
+        if (!this.getProp("value")) {
+            this._adapter.setState({ currentColor: currentColor });
+        }
+       
+    }
+
+
+    handleAlphaChangeByHandle = (newAlpha: {a: number})=>{
+        this.handleChangeA(this.getCurrentColor(), newAlpha.a);
+    }
+
+    handleColorChangeByHandle = (newHue: {h: number})=>{
+        this.handleChangeH(this.getCurrentColor(), newHue.h);
+    }
+
+
+    getHandlePositionByHSVA = (hsva: HsvaColor, { width, height }: {width: number;height: number}, handleSize: number)=>{
+
+        const defaultColorPosition = { x: hsva.s / 100 * width, y: (1 - hsva.v / 100) * height };
+        return { x: defaultColorPosition.x - handleSize / 2, y: defaultColorPosition.y - handleSize / 2 };
+    }
+
+    getHandlePositionByMousePosition = (mousePosition: {x: number;y: number}, { width, height }: {width: number;height: number}, handleSize: number)=>{
+        if (mousePosition.x > width || mousePosition.x < 0) {
+            return null;
+        }
+
+        if (mousePosition.y > height || mousePosition.y < 0) {
+            return null;
+        }
+
+        const handlePosition = {
+            x: mousePosition.x - handleSize / 2,
+            y: mousePosition.y - handleSize / 2
+        };
+
+        return handlePosition;
+    }
+
+
+    getAlphaHandlePositionByMousePosition = (mousePosition: number, width: number, handleSize: number)=>{
+        if (mousePosition < 0 || mousePosition > width) {
+            return null;
+        }
+        return mousePosition - handleSize / 2;
+    }
+
+    getColorHandlePositionByMousePosition = (mousePosition: number, width: number, handleSize: number)=>{
+        if (mousePosition < 0 || mousePosition > width) {
+            return null;
+        }
+
+        return mousePosition - handleSize / 2;
+    }
+
+
+    
+
+
+
+
+}
+
+export default ColorPickerFoundation;
+

+ 51 - 0
packages/semi-foundation/colorPicker/interface.ts

@@ -0,0 +1,51 @@
+export interface HsvColor {
+    h: number;
+    s: number;
+    v: number
+}
+
+export interface HsvaColor extends HsvColor {
+    a: number
+}
+
+
+export interface RgbColor {
+    r: number;
+    g: number;
+    b: number
+}
+
+export interface RgbaColor extends RgbColor {
+    a: number
+}
+
+export interface HslColor {
+    h: number;
+    s: number;
+    l: number
+}
+
+export interface HslaColor extends HslColor {
+    a: number
+}
+
+export interface HsvColor {
+    h: number;
+    s: number;
+    v: number
+}
+
+export interface HsvaColor extends HsvColor {
+    a: number
+}
+
+export type ObjectColor = RgbColor | HslColor | HsvColor | RgbaColor | HslaColor | HsvaColor;
+
+export type AnyColor = string | ObjectColor;
+
+export interface ColorModel<T extends AnyColor> {
+    defaultColor: T;
+    toHsva: (defaultColor: T) => HsvaColor;
+    fromHsva: (hsva: HsvaColor) => T;
+    equal: (first: T, second: T) => boolean
+}

+ 228 - 0
packages/semi-foundation/colorPicker/utils/convert.ts

@@ -0,0 +1,228 @@
+import { round } from "./round";
+import { RgbaColor, RgbColor, HslaColor, HslColor, HsvaColor, HsvColor } from "../interface";
+/**
+ * Valid CSS <angle> units.
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/angle
+ */
+
+/**
+ *  Referrer from https://github.com/web-padawan/vanilla-colorful/blob/master/src/lib/utils/convert.ts
+ */
+const angleUnits: Record<string, number> = {
+    grad: 360 / 400,
+    turn: 360,
+    rad: 360 / (Math.PI * 2),
+};
+
+export const hexToHsva = (hex: string): HsvaColor => rgbaToHsva(hexToRgba(hex));
+
+export const hexToRgba = (hex: string): RgbaColor => {
+    if (hex[0] === "#") hex = hex.substring(1);
+
+    const hexToPercent = (str: string) => {
+        const decimal = parseInt(str, 16);
+        if (!isNaN(decimal)) {
+            const percent = decimal / 255;
+            return percent;
+        }
+        return 1;
+    };
+
+
+    return {
+        r: parseInt(hex.substring(0, 2), 16),
+        g: parseInt(hex.substring(2, 4), 16),
+        b: parseInt(hex.substring(4, 6), 16),
+        a: hexToPercent(hex.substring(6, 8)),
+    };
+};
+
+export const parseHue = (value: string, unit = "deg"): number => {
+    return Number(value) * (angleUnits[unit] || 1);
+};
+
+export const hslaStringToHsva = (hslString: string): HsvaColor => {
+    const matcher = /hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+    const match = matcher.exec(hslString);
+
+    if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+    return hslaToHsva({
+        h: parseHue(match[1], match[2]),
+        s: Number(match[3]),
+        l: Number(match[4]),
+        a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
+    });
+};
+
+export const hslStringToHsva = hslaStringToHsva;
+
+export const hslaToHsva = ({ h, s, l, a }: HslaColor): HsvaColor => {
+    s *= (l < 50 ? l : 100 - l) / 100;
+
+    return {
+        h: h,
+        s: s > 0 ? ((2 * s) / (l + s)) * 100 : 0,
+        v: l + s,
+        a,
+    };
+};
+
+export const hsvaToHex = (hsva: HsvaColor): string => rgbaToHex(hsvaToRgba(hsva));
+
+export const hsvaToHsla = ({ h, s, v, a }: HsvaColor): HslaColor => {
+    const hh = ((200 - s) * v) / 100;
+
+    return {
+        h: round(h),
+        s: round(hh > 0 && hh < 200 ? ((s * v) / 100 / (hh <= 100 ? hh : 200 - hh)) * 100 : 0),
+        l: round(hh / 2),
+        a: round(a, 2),
+    };
+};
+
+export const hsvaToHslString = (hsva: HsvaColor): string => {
+    const { h, s, l } = hsvaToHsla(hsva);
+    return `hsl(${h}, ${s}%, ${l}%)`;
+};
+
+export const hsvaToHsvString = (hsva: HsvaColor): string => {
+    const { h, s, v } = roundHsva(hsva);
+    return `hsv(${h}, ${s}%, ${v}%)`;
+};
+
+export const hsvaToHsvaString = (hsva: HsvaColor): string => {
+    const { h, s, v, a } = roundHsva(hsva);
+    return `hsva(${h}, ${s}%, ${v}%, ${a})`;
+};
+
+export const hsvaToHslaString = (hsva: HsvaColor): string => {
+    const { h, s, l, a } = hsvaToHsla(hsva);
+    return `hsla(${h}, ${s}%, ${l}%, ${a})`;
+};
+
+export const hsvaToRgba = ({ h, s, v, a }: HsvaColor): RgbaColor => {
+
+
+    h = (h / 360) * 6;
+    s = s / 100;
+    v = v / 100;
+
+    const hh = Math.floor(h),
+        b = v * (1 - s),
+        c = v * (1 - (h - hh) * s),
+        d = v * (1 - (1 - h + hh) * s),
+        module = hh % 6;
+
+    return {
+        r: round([v, c, b, b, d, v][module] * 255),
+        g: round([d, v, v, c, b, b][module] * 255),
+        b: round([b, b, d, v, v, c][module] * 255),
+        a: round(a, 2),
+    };
+};
+
+export const hsvaToRgbString = (hsva: HsvaColor): string => {
+    const { r, g, b } = hsvaToRgba(hsva);
+    return `rgb(${r}, ${g}, ${b})`;
+};
+
+export const hsvaToRgbaString = (hsva: HsvaColor): string => {
+    const { r, g, b, a } = hsvaToRgba(hsva);
+    return `rgba(${r}, ${g}, ${b}, ${a})`;
+};
+
+export const hsvaStringToHsva = (hsvString: string): HsvaColor => {
+    const matcher = /hsva?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+    const match = matcher.exec(hsvString);
+
+    if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+    return roundHsva({
+        h: parseHue(match[1], match[2]),
+        s: Number(match[3]),
+        v: Number(match[4]),
+        a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
+    });
+};
+
+export const hsvStringToHsva = hsvaStringToHsva;
+
+export const rgbaStringToHsva = (rgbaString: string): HsvaColor => {
+    return rgbaToHsva(rgbaStringToRgba(rgbaString));
+};
+
+export const rgbaStringToRgba = (rgbaString: string): RgbaColor => {
+    const matcher = /rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+    const match = matcher.exec(rgbaString);
+
+    if (!match) return { r: 0, g: 0, b: 0, a: 1 };
+
+    return {
+        r: Number(match[1]) / (match[2] ? 100 / 255 : 1),
+        g: Number(match[3]) / (match[4] ? 100 / 255 : 1),
+        b: Number(match[5]) / (match[6] ? 100 / 255 : 1),
+        a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1),
+    };
+};
+export const rgbStringToRgba = rgbaStringToRgba;
+
+export const rgbStringToHsva = rgbaStringToHsva;
+
+const format = (number: number) => {
+    const hex = number.toString(16);
+    return hex.length < 2 ? "0" + hex : hex;
+};
+
+export const rgbaToHex = ({ r, g, b, a }: RgbaColor): string => {
+    const percentToHex = (p) => {
+        //const percent = Math.max(0, Math.min(100, p)); // bound percent from 0 to 100
+        const intValue = Math.round(p / 100 * 255); // map percent to nearest integer (0 - 255)
+        const hexValue = intValue.toString(16); // get hexadecimal representation
+        return hexValue.padStart(2, '0').toLowerCase(); // format with leading 0 and upper case characters
+    };
+    if (a === undefined || a === 1) {
+        return "#" + format(r) + format(g) + format(b);
+    } else {
+        return "#" + format(r) + format(g) + format(b) + percentToHex(a * 100);
+    }
+
+};
+
+export const rgbaToHsva = ({ r, g, b, a }: RgbaColor): HsvaColor => {
+
+    const max = Math.max(r, g, b);
+    const delta = max - Math.min(r, g, b);
+
+    // prettier-ignore
+    const hh = delta
+        ? max === r
+            ? (g - b) / delta
+            : max === g
+                ? 2 + (b - r) / delta
+                : 4 + (r - g) / delta
+        : 0;
+
+    return {
+        h: round(60 * (hh < 0 ? hh + 6 : hh)),
+        s: round(max ? (delta / max) * 100 : 0),
+        v: round((max / 255) * 100),
+        a,
+    };
+};
+
+export const roundHsva = (hsva: HsvaColor): HsvaColor => ({
+    h: round(hsva.h),
+    s: round(hsva.s),
+    v: round(hsva.v),
+    a: round(hsva.a, 2),
+});
+
+export const rgbaToRgb = ({ r, g, b }: RgbaColor): RgbColor => ({ r, g, b });
+
+export const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });
+
+export const hsvaToHsv = (hsva: HsvaColor): HsvColor => {
+    const { h, s, v } = roundHsva(hsva);
+    return { h, s, v };
+};

+ 3 - 0
packages/semi-foundation/colorPicker/utils/round.ts

@@ -0,0 +1,3 @@
+export const round = (number: number, digits = 0, base = Math.pow(10, digits)): number => {
+    return Math.round(base * number) / base;
+};

+ 40 - 0
packages/semi-foundation/colorPicker/utils/split.ts

@@ -0,0 +1,40 @@
+const split = (str: string, mode: 'rgba' | 'hsva') => {
+    // 12,32,43 => [12,32,43]
+    const reg = /^\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,?\s*([\d.]*)\s*$/;
+    const res = str.match(reg);
+    const result: number[] = [];
+    result[0] = Number(res?.[1]);
+    result[1] = Number(res?.[2]);
+    result[2] = Number(res?.[3]);
+    result[3] = Number((res?.[4] === undefined || res?.[4] === '') ? 1 : res?.[4]);
+
+    const check = (a: number, max: number) => {
+        return !(isNaN(a) || a < 0 || a > max);
+    };
+
+    const ok = check(result[0], mode === 'rgba' ? 255 : 360)
+        && check(result[1], mode === 'rgba' ? 255 : 100)
+        && check(result[2], mode === 'rgba' ? 255 : 100)
+        && check(result[3], 1);
+    if (ok) {
+        if (mode === 'rgba') {
+            return {
+                r: result[0],
+                g: result[1],
+                b: result[2],
+                a: result[3]
+            };
+        } else {
+            return {
+                h: result[0],
+                s: result[1],
+                v: result[2],
+                a: result[3]
+            };
+        }
+    } else {
+        return false;
+    }
+};
+
+export default split;

+ 28 - 0
packages/semi-foundation/colorPicker/variables.scss

@@ -0,0 +1,28 @@
+$radius-colorPicker-topLeft:8px; // 圆角 - 左上
+$radius-colorPicker-topRight:8px; // 圆角 - 右上
+$radius-colorPicker-bottomLeft:0px; // 圆角 - 左下
+$radius-colorPicker-bottomRight:0px; // 圆角 - 右下
+$radius-colorPicker-handle:var(--semi-border-radius-full); // 圆角 - 拖拽把手
+$radius-colorPicker-alphaSliderInner:4px; // 圆角 - 透明度 Slider
+$radius-colorPicker-demoBlock: 4px; // 圆角 - 颜色手动输入区域左侧当前选中颜色色块
+$radius-colorPicker-defaultTrigger:4px; // 默认 trigger 圆角
+
+
+$width-colorPicker_handle-border: 2px; // 把手宽度
+$width-colorPicker-colorPickerInputNumber:58px; // 数组输入器宽度
+$width-colorPicker-formatSelect:80px; // 格式选择下拉 Select 宽度
+$width-colorPicker-defaultTrigger: 24px; // 默认 trigger 大小
+
+$color-colorPicker_handle-border:var(--semi-color-white); // 把手边框颜色
+
+
+$spacing-colorPicker_inputNumberSuffix-horizontal:4px; // 数字输入框后百分比水平距离
+$spacing-colorPicker_inputGroup-marginLeft:4px; //  颜色手动输入区域 左侧距离色块距离
+$spacing-colorPicker_popover-padding:8px; // 弹层模式时整体内边距
+$spacing-colorPicker_inputNumberSuffix-vertical:4px; // 数字输入框后百分比垂直距离
+$spacing-colorPicker_slider-marginTop:6px; // 滑动选择器上边距
+$spacing-colorPicker_dataPart-marginTop:8px; // 颜色手动输入区域 上边距
+
+
+$font-colorPicker_inputNumberSuffix-fontSize:14px; // 颜色手动输入区域百分比字体大小
+

+ 25 - 0
packages/semi-icons/src/icons/IconEyedropper.tsx

@@ -0,0 +1,25 @@
+import * as React from 'react';
+import { convertIcon } from '../components/Icon';
+function SvgComponent(props: React.SVGProps<SVGSVGElement>) {
+    return (
+        <svg
+            viewBox="0 0 24 24"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+            width="1em"
+            height="1em"
+            focusable={false}
+            aria-hidden={true}
+            {...props}
+        >
+            <path
+                fillRule="evenodd"
+                clipRule="evenodd"
+                d="M1.22396 20.7662C-0.415453 19.1136 -0.406853 16.4316 1.24313 14.7897L10.57 5.50874L9.62535 4.55653C9.08135 4.00817 9.08135 3.11909 9.62535 2.57074C10.1694 2.02238 11.0514 2.02238 11.5954 2.57074L12.5526 3.53579L14.8854 1.21459C16.5204 -0.412333 19.1516 -0.403739 20.776 1.2338C22.4154 2.88639 22.4069 5.56845 20.7569 7.21035L18.4626 9.49315L19.4751 10.5139C20.0192 11.0623 20.0192 11.9514 19.4751 12.4998C18.9311 13.0481 18.0491 13.0481 17.5053 12.4998L16.4799 11.4661L7.11458 20.7855C5.47962 22.4122 2.84845 22.4038 1.22396 20.7662ZM12.54 7.49455L14.5099 9.48042L5.1574 18.7869C4.61243 19.3291 3.7354 19.3263 3.19392 18.7805C2.64747 18.2295 2.65033 17.3355 3.20031 16.7883L12.54 7.49455Z"
+                fill="currentColor"
+            />
+        </svg>
+    );
+}
+const IconComponent = convertIcon(SvgComponent, 'eyedropper');
+export default IconComponent;

+ 25 - 0
packages/semi-icons/src/icons/IconStorysStroked.tsx

@@ -0,0 +1,25 @@
+import * as React from 'react';
+import { convertIcon } from '../components/Icon';
+function SvgComponent(props: React.SVGProps<SVGSVGElement>) {
+    return (
+        <svg
+            viewBox="0 0 24 24"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+            width="1em"
+            height="1em"
+            focusable={false}
+            aria-hidden={true}
+            {...props}
+        >
+            <path
+                fillRule="evenodd"
+                clipRule="evenodd"
+                d="M20.36 12C20.36 16.6171 16.6171 20.36 12 20.36C7.3829 20.36 3.64 16.6171 3.64 12C3.64 7.3829 7.3829 3.64 12 3.64C16.6171 3.64 20.36 7.3829 20.36 12ZM23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12ZM17.5534 9.85334C18.0689 9.33784 18.0689 8.50207 17.5534 7.98657C17.0379 7.47108 16.2021 7.47108 15.6866 7.98657L10.02 13.6532L7.65338 11.2866C7.13789 10.7711 6.30211 10.7711 5.78662 11.2866C5.27113 11.8021 5.27113 12.6378 5.78662 13.1533L9.08662 16.4533C9.33417 16.7009 9.66992 16.84 10.02 16.84C10.3701 16.84 10.7058 16.7009 10.9534 16.4533L17.5534 9.85334Z"
+                fill="currentColor"
+            />
+        </svg>
+    );
+}
+const IconComponent = convertIcon(SvgComponent, 'storys_stroked');
+export default IconComponent;

+ 2 - 1
packages/semi-icons/src/icons/index.ts

@@ -144,6 +144,7 @@ export { default as IconExternalOpen } from './IconExternalOpen';
 export { default as IconExternalOpenStroked } from './IconExternalOpenStroked';
 export { default as IconEyeClosed } from './IconEyeClosed';
 export { default as IconEyeClosedSolid } from './IconEyeClosedSolid';
+export { default as IconEyedropper } from "./IconEyedropper";
 export { default as IconEyeOpened } from './IconEyeOpened';
 export { default as IconFacebook } from './IconFacebook';
 export { default as IconFaceuLogo } from './IconFaceuLogo';
@@ -441,4 +442,4 @@ export { default as IconWifi } from './IconWifi';
 export { default as IconWindowAdaptionStroked } from './IconWindowAdaptionStroked';
 export { default as IconWrench } from './IconWrench';
 export { default as IconXiguaLogo } from './IconXiguaLogo';
-export { default as IconYoutube } from './IconYoutube';
+export { default as IconYoutube } from './IconYoutube';

+ 3 - 1
packages/semi-icons/src/svgs/eye_opened.svg

@@ -1 +1,3 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 4C5 4 1 10 1 12C1 14 5 20 12 20C19 20 23 14 23 12C23 10 19 4 12 4ZM17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12ZM12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="black"/></svg>
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M12 4C5 4 1 10 1 12C1 14 5 20 12 20C19 20 23 14 23 12C23 10 19 4 12 4ZM17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12ZM12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="black"/>
+</svg>

+ 3 - 0
packages/semi-icons/src/svgs/eyedropper.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M1.22396 20.7662C-0.415453 19.1136 -0.406853 16.4316 1.24313 14.7897L10.57 5.50874L9.62535 4.55653C9.08135 4.00817 9.08135 3.11909 9.62535 2.57074C10.1694 2.02238 11.0514 2.02238 11.5954 2.57074L12.5526 3.53579L14.8854 1.21459C16.5204 -0.412333 19.1516 -0.403739 20.776 1.2338C22.4154 2.88639 22.4069 5.56845 20.7569 7.21035L18.4626 9.49315L19.4751 10.5139C20.0192 11.0623 20.0192 11.9514 19.4751 12.4998C18.9311 13.0481 18.0491 13.0481 17.5053 12.4998L16.4799 11.4661L7.11458 20.7855C5.47962 22.4122 2.84845 22.4038 1.22396 20.7662ZM12.54 7.49455L14.5099 9.48042L5.1574 18.7869C4.61243 19.3291 3.7354 19.3263 3.19392 18.7805C2.64747 18.2295 2.65033 17.3355 3.20031 16.7883L12.54 7.49455Z" fill="#2B2F36"/>
+</svg>

+ 6 - 6
packages/semi-scss-compile/src/utils/writeFile.ts

@@ -73,19 +73,19 @@ const preProcessScssMap = (scssMapOrigin: ReturnType<typeof generateScssMap>) =>
         const componentNames = Object.keys(scssMap['components']);
         const orderList = ['tooltip', 'anchor', 'autoComplete', 'avatar', 'backtop', 'badge', 'banner', 'breadcrumb', 'button', 'calendar', 'card', 'carousel', 'cascader', 'checkbox', 'collapse', 'collapsible', 'datePicker', 'descriptions', 'divider', 'dropdown', 'empty', 'form', 'grid', 'highlight', 'image', 'input', 'inputNumber', 'list', 'modal', 'navigation', 'notification', 'pagination', 'popconfirm', 'popover', 'progress', 'radio', 'rating', 'scrollList', 'select', 'sideSheet', 'skeleton', 'slider', 'space', 'spin', 'steps', 'switch', 'table', 'tabs', 'tag', 'tagInput', 'timePicker', 'timeline', 'toast', 'transfer', 'tree', 'treeSelect', 'typography', 'upload'];
 
-        componentNames.sort((a,b)=>{
+        componentNames.sort((a, b)=>{
             return orderList.indexOf(a) - orderList.indexOf(b);
         });
 
         for (const componentName of componentNames) {
             if (scssMap['components'][componentName]['variables.scss']) {
-                allCustomRaw+= scssMap['components'][componentName]['variables.scss']+'\n';
+                allCustomRaw += scssMap['components'][componentName]['variables.scss'] + '\n';
             }
         }
-        allCustomRaw+= themeLocalRaw || "";
-        allCustomRaw+="\n";
-        allCustomRaw+=`body:not(:not(body)){${customScssRaw}};`+"\n";
-        scssMap.theme['index.scss'] += '\n'+allCustomRaw;
+        allCustomRaw += themeLocalRaw || "";
+        allCustomRaw += "\n";
+        allCustomRaw += `body:not(:not(body)){${customScssRaw}};` + "\n";
+        scssMap.theme['index.scss'] += '\n' + allCustomRaw;
     }
 
     //----- inject end -----

+ 97 - 0
packages/semi-ui/colorPicker/AlphaSlider/index.tsx

@@ -0,0 +1,97 @@
+import React, { CSSProperties, PropsWithChildren } from 'react';
+import { hsvaToHslaString, hsvaToRgbaString } from "@douyinfe/semi-foundation/colorPicker/utils/convert";
+import { round } from "@douyinfe/semi-foundation/colorPicker/utils/round";
+import { cssClasses } from '@douyinfe/semi-foundation/colorPicker/constants';
+import BaseComponent from "../../_base/baseComponent";
+import AlphaSliderFoundation, {
+    AlphaSliderAdapter,
+    AlphaSliderBaseProps,
+    AlphaSliderBaseState
+} from "@douyinfe/semi-foundation/colorPicker/AlphaSliderFoundation";
+
+export interface AlphaSliderProps extends AlphaSliderBaseProps {
+    className?: string;
+    style?: CSSProperties
+}
+
+export interface AlphaSliderState extends AlphaSliderBaseState {
+
+}
+
+class AlphaSlider extends BaseComponent<PropsWithChildren<AlphaSliderProps>, AlphaSliderState> {
+    private ref: React.RefObject<HTMLDivElement>;
+
+    constructor(props) {
+        super(props);
+        this.foundation = new AlphaSliderFoundation(this.adapter);
+        this.state = {
+            handlePosition: props.hsva.a * props.width - props.handleSize / 2,
+            isHandleGrabbing: false
+        };
+        this.ref = React.createRef<HTMLDivElement>();
+    }
+
+    get adapter(): AlphaSliderAdapter<AlphaSliderProps, AlphaSliderState> {
+        return {
+            ...super.adapter,
+            handleMouseDown: (e: any) => {
+                this.setState({ isHandleGrabbing: true });
+                window.addEventListener('mousemove', this.foundation.setHandlePositionByMousePosition);
+                window.addEventListener('mouseup', this.foundation.handleMouseUp);
+            },
+            handleMouseUp: (e: MouseEvent) => {
+                this.setState({ isHandleGrabbing: false });
+                window.removeEventListener('mousemove', this.foundation.setHandlePositionByMousePosition);
+                window.removeEventListener('mouseup', this.foundation.handleMouseUp);
+            },
+            getColorPickerFoundation: () => this.props.foundation,
+            getDOM: () => this.ref.current
+        };
+    }
+
+    componentDidUpdate(prevProps: Readonly<AlphaSliderBaseProps>, prevState: Readonly<AlphaSliderBaseState>, snapshot?: any) {
+        if (prevProps.hsva.a !== this.props.hsva.a) {
+            this.setState({ handlePosition: this.props.hsva.a * this.props.width - this.props.handleSize / 2 });
+        }
+    }
+
+    handleClick = (e: React.MouseEvent) => {
+        this.foundation.setHandlePositionByMousePosition(e);
+        this.foundation.handleMouseDown(e);
+    }
+
+    render() {
+        const colorFrom = hsvaToHslaString({ ...this.props.hsva, a: 0 });
+        const colorTo = hsvaToHslaString({ ...this.props.hsva, a: 1 });
+
+        const alphaSliderBackground = `linear-gradient(90deg, ${colorFrom}, ${colorTo})`;
+        return <div className={`${cssClasses.PREFIX}-alphaSlider`} ref={this.ref}
+            aria-label="Alpha"
+            aria-valuetext={`${round(this.props.hsva.a * 100)}%`}
+            onMouseDown={this.handleClick}
+            style={{
+                width: this.props.width,
+                height: this.props.height,
+                ...this.props.style
+            }}>
+            <div className={`${cssClasses.PREFIX}-alphaSliderInner`} style={{ background: alphaSliderBackground }}>
+                <div className={`${cssClasses.PREFIX}-alphaHandle`}
+                    style={{
+                        width: this.props.handleSize,
+                        height: this.props.handleSize,
+                        left: this.state.handlePosition,
+                        top: `50%`,
+                        transform: `translateY(-50%)`,
+                        backgroundColor: hsvaToRgbaString(this.props.hsva)
+                    }}
+                    onMouseDown={(e) => this.foundation.handleMouseDown(e)}>
+                </div>
+            </div>
+
+
+        </div>;
+    }
+
+}
+
+export default AlphaSlider;

+ 109 - 0
packages/semi-ui/colorPicker/ColorChooseArea/index.tsx

@@ -0,0 +1,109 @@
+import React, { CSSProperties, PropsWithChildren } from 'react';
+import { hsvaToHslString, hsvaToRgba } from "@douyinfe/semi-foundation/colorPicker/utils/convert";
+import { cssClasses } from '@douyinfe/semi-foundation/colorPicker/constants';
+import cls from 'classnames';
+import ColorChooseAreaFoundation, {
+    ColorChooseAreaAdapter,
+    ColorChooseAreaBaseProps,
+    ColorChooseAreaBaseState
+} from "@douyinfe/semi-foundation/colorPicker/ColorChooseAreaFoundation";
+import BaseComponent from "../../_base/baseComponent";
+import { round } from "@douyinfe/semi-foundation/colorPicker/utils/round";
+
+
+
+
+export interface ColorChooseAreaProps extends ColorChooseAreaBaseProps{
+    className?: string;
+    style?: CSSProperties
+}
+
+export interface ColorChooseAreaState extends ColorChooseAreaBaseState{
+
+}
+
+class ColorChooseArea extends BaseComponent<PropsWithChildren<ColorChooseAreaProps>, ColorChooseAreaState> {
+    ref: React.RefObject<HTMLDivElement>;
+
+    constructor(props) {
+        super(props);
+        this.foundation = new ColorChooseAreaFoundation(this.adapter);
+        this.state = {
+            handlePosition: this.foundation.getHandlePositionByHSVA(),
+            isHandleGrabbing: false,
+        };
+        this.ref = React.createRef();
+    }
+
+    get adapter(): ColorChooseAreaAdapter<ColorChooseAreaProps, ColorChooseAreaState> {
+        return {
+            ...super.adapter,
+            getColorPickerFoundation: ()=>this.props.foundation,
+            handleMouseDown: (e: React.MouseEvent) => {
+                this.setState({ isHandleGrabbing: true });
+                this.ref.current.addEventListener('mousemove', this.foundation.setHandlePositionByMousePosition);
+                window.addEventListener('mouseup', this.foundation.handleMouseUp);
+            },
+            handleMouseUp: () => {
+                this.ref.current.removeEventListener('mousemove', this.foundation.setHandlePositionByMousePosition);
+                window.removeEventListener('mouseup', this.foundation.handleMouseUp);
+                this.setState({ isHandleGrabbing: false });
+            },
+            getDOM: ()=>this.ref.current,
+            notifyChange: (newColor)=>this.props.onChange(newColor)
+
+        };
+    }
+
+
+    componentDidUpdate(prevProps: Readonly<ColorChooseAreaProps>, prevState: Readonly<ColorChooseAreaState>, snapshot?: any) {
+        if (JSON.stringify(prevProps.hsva) !== JSON.stringify(this.props.hsva)) {
+            this.setState({ handlePosition: this.foundation.getHandlePositionByHSVA() });
+        }
+    }
+
+
+
+
+
+    handleClick = (e: React.MouseEvent)=>{
+        this.foundation.setHandlePositionByMousePosition(e);
+        this.foundation.handleMouseDown(e);
+    }
+
+
+
+
+    render() {
+        const areaBgStyle = hsvaToHslString({ h: this.props.hsva.h, s: 100, v: 100, a: 1 });
+        const currentColor = hsvaToRgba(this.props.hsva);
+        return <div className={cls(`${cssClasses.PREFIX}-colorChooseArea`, this.props.className )}
+            style={{
+                backgroundColor: areaBgStyle,
+                width: this.props.width, height: this.props.height,
+                cursor: this.state.isHandleGrabbing ? 'grabbing' : 'pointer',
+                ...this.props.style
+            }}
+            ref={this.ref}
+            aria-label="Color"
+            onMouseDown={this.handleClick}
+            aria-valuetext={`Saturation ${round(this.props.hsva.s)}%, Brightness ${round(this.props.hsva.v)}%`}
+        >
+            <div className={`${cssClasses.PREFIX}-handle`}
+                style={{
+                    width: this.props.handleSize,
+                    height: this.props.handleSize,
+                    left: this.state.handlePosition.x,
+                    top: this.state.handlePosition.y,
+                    backgroundColor: `rgba(${currentColor.r},${currentColor.g},${currentColor.b},${currentColor.a})`
+                }}
+
+                onMouseDown={(e) => this.foundation.handleMouseDown(e)}>
+
+            </div>
+        </div>;
+    }
+
+}
+
+export default ColorChooseArea;

+ 91 - 0
packages/semi-ui/colorPicker/ColorSlider/index.tsx

@@ -0,0 +1,91 @@
+import React, { CSSProperties, PropsWithChildren } from 'react';
+import ColorPickerFoundation from '@douyinfe/semi-foundation/colorPicker/foundation';
+import { cssClasses } from '@douyinfe/semi-foundation/colorPicker/constants';
+import cls from 'classnames';
+import ColorSliderFoundation, {
+    ColorSliderAdapter,
+    ColorSliderBaseProps,
+    ColorSliderBaseState
+} from "@douyinfe/semi-foundation/colorPicker/ColorSliderFoundation";
+import BaseComponent from "../../_base/baseComponent";
+
+export interface ColorSliderProps extends ColorSliderBaseProps {
+    className?: string;
+    style?: CSSProperties
+}
+
+export interface ColorSliderState extends ColorSliderBaseState {
+
+}
+
+class ColorSlider extends BaseComponent<PropsWithChildren<ColorSliderProps>, ColorSliderState> {
+    private readonly ref: React.RefObject<HTMLDivElement>;
+
+    constructor(props: ColorSliderProps) {
+        super(props);
+        this.foundation = new ColorSliderFoundation(this.adapter);
+        this.state = {
+            handlePosition: props.hue / 360 * props.width - props.handleSize / 2,
+            isHandleGrabbing: false
+        };
+        this.ref = React.createRef<HTMLDivElement>();
+    }
+
+    get adapter(): ColorSliderAdapter<ColorSliderProps, ColorSliderState> {
+        return {
+            ...super.adapter,
+            handleMouseDown: (e: any) => {
+                this.setState({ isHandleGrabbing: true });
+                window.addEventListener('mousemove', this.foundation.setHandlePositionByMousePosition);
+                window.addEventListener('mouseup', this.foundation.handleMouseUp);
+            },
+            handleMouseUp: (e: MouseEvent) => {
+                this.setState({ isHandleGrabbing: false });
+                window.removeEventListener('mousemove', this.foundation.setHandlePositionByMousePosition);
+                window.removeEventListener('mouseup', this.foundation.handleMouseUp);
+            },
+            getColorPickerFoundation: () => this.props.foundation,
+            getDOM: () => this.ref.current
+        };
+    }
+
+    componentDidUpdate(prevProps: Readonly<ColorSliderProps>, prevState: Readonly<ColorSliderState>, snapshot?: any) {
+        if (prevProps.hue !== this.props.hue) {
+            this.setState({ handlePosition: this.props.hue / 360 * this.props.width - this.props.handleSize / 2 });
+        }
+    }
+
+
+    handleClick = (e: React.MouseEvent) => {
+        this.foundation.setHandlePositionByMousePosition(e);
+        this.foundation.handleMouseDown(e);
+    }
+
+
+    render() {
+        return <div className={cls(`${cssClasses.PREFIX}-colorSlider`, this.props.className)} ref={this.ref}
+            onMouseDown={this.handleClick}
+            style={{
+                width: this.props.width,
+                height: this.props.height,
+                ...this.props.style
+            }}>
+
+            <div className={`${cssClasses.PREFIX}-handle`}
+                style={{
+                    width: this.props.handleSize,
+                    height: this.props.handleSize,
+                    left: this.state.handlePosition,
+                    top: `50%`,
+                    transform: `translateY(-50%)`,
+                    backgroundColor: ColorPickerFoundation.hsvaToHslString({ h: this.props.hue, s: 100, v: 100, a: 1 })
+                }}
+                onMouseDown={(e) => this.foundation.handleMouseDown(e)}>
+            </div>
+
+        </div>;
+    }
+
+}
+
+export default ColorSlider;

+ 119 - 0
packages/semi-ui/colorPicker/DataPart/index.tsx

@@ -0,0 +1,119 @@
+import React, { PropsWithChildren, ReactNode } from 'react';
+import { HsvaColor, RgbaColor } from '@douyinfe/semi-foundation/colorPicker/interface';
+import Input from "../../input";
+import InputGroup from "../../input/inputGroup";
+import InputNumber from "../../inputNumber";
+import Select from "../../select";
+import Button from "../../button";
+import ColorPickerFoundation, { ColorPickerProps } from '@douyinfe/semi-foundation/colorPicker/foundation';
+import { isEqual } from 'lodash';
+import { IconEyedropper } from '@douyinfe/semi-icons';
+import { cssClasses } from '@douyinfe/semi-foundation/colorPicker/constants';
+import BaseComponent from "../../_base/baseComponent";
+import DataPartFoundation, {
+    DataPartAdapter,
+    DataPartBaseProps,
+    DataPartBaseState
+} from "@douyinfe/semi-foundation/colorPicker/DataPartFoundation";
+
+
+export interface DataPartProps extends DataPartBaseProps {
+
+}
+
+export interface DataPartState extends DataPartBaseState {
+
+}
+
+
+class DataPart extends BaseComponent<PropsWithChildren<DataPartProps>, DataPartState> {
+
+    constructor(props: DataPartProps) {
+        super(props);
+        this.foundation = new DataPartFoundation(this.adapter);
+        this.state = {
+            format: this.props.defaultFormat,
+            inputValue: ''
+        };
+    }
+
+    get adapter(): DataPartAdapter<DataPartBaseProps, DataPartBaseState> {
+        return {
+            ...super.adapter,
+
+            getColorPickerFoundation: () => this.props.foundation,
+        };
+    }
+
+    componentDidMount() {
+        this.foundation.handleInputValueChange(this.foundation.getInputValue());
+    }
+
+    componentDidUpdate(prevProps: Readonly<DataPartProps>, prevState: Readonly<DataPartState>, snapshot?: any) {
+        if (!isEqual(prevProps.currentColor, this.props.currentColor) || prevState.format !== this.state.format) {
+            this.foundation.handleInputValueChange(this.foundation.getInputValue());
+        }
+    }
+
+
+    handleChange = (newColor: RgbaColor | HsvaColor | string) => {
+        this.props.foundation.handleChange(newColor, this.state.format);
+    }
+
+
+    render() {
+        const rgba = this.props.currentColor.rgba;
+        return <div className={`${cssClasses.PREFIX}-dataPart`} style={{ width: this.props.width }}>
+            <div className={`${cssClasses.PREFIX}-colorDemoBlock`} style={{
+                minWidth: 20, minHeight: 20, backgroundColor:
+                    `rgba(${rgba.r},${rgba.g},${rgba.b},${rgba.a})`
+            }}>
+            </div>
+            <InputGroup size={'small'} className={`${cssClasses.PREFIX}-inputGroup`}>
+                <Input className={`${cssClasses.PREFIX}-colorPickerInput`}
+                    value={this.state.inputValue}
+                    onChange={(v) => {
+                        const value = this.foundation.getValueByInputValue(v);
+                        if (value) {
+                            this.handleChange(value);
+                        }
+                        this.foundation.handleInputValueChange(v);
+                    }}
+                />
+                {
+                    this.props.alpha && <InputNumber
+                        min={0}
+                        max={100}
+                        className={`${cssClasses.PREFIX}-colorPickerInputNumber`}
+                        value={Number(Math.round(this.props.currentColor.rgba.a * 100))}
+                        onNumberChange={v => {
+                            if (this.state.format === 'rgba') {
+                                this.handleChange({ ...this.props.currentColor.rgba, a: Number((v / 100).toFixed(2)) });
+                            } else if (this.state.format === 'hex') {
+                                const rgba = { ...this.props.currentColor.rgba, a: Number((v / 100).toFixed(2)) };
+                                const hex = ColorPickerFoundation.rgbaToHex(rgba);
+                                this.handleChange(hex);
+                            } else if (this.state.format === 'hsva') {
+                                const rgba = { ...this.props.currentColor.hsva, a: Number((v / 100).toFixed(2)) };
+                                this.handleChange(rgba);
+                            }
+                        }}
+                        suffix={<span className={`${cssClasses.PREFIX}-inputNumberSuffix`}>%</span>} hideButtons={true}/>
+                }
+                <Select className={`${cssClasses.PREFIX}-formatSelect`}
+                    size={'small'}
+                    value={this.state.format}
+                    onSelect={v => this.foundation.handleFormatChange(v)}
+                    optionList={['hex', 'rgba', 'hsva'].map(type => ({ label: type, value: type }))}/>
+            </InputGroup>
+
+            {this.props.eyeDropper && <Button type={'tertiary'} theme={'light'} size={'small'}
+                onClick={this.foundation.handlePickValueWithStraw}
+                icon={<IconEyedropper/>}/>}
+
+        </div>;
+    }
+
+}
+
+export default DataPart;

+ 47 - 0
packages/semi-ui/colorPicker/_story/colorPicker.stories.jsx

@@ -0,0 +1,47 @@
+import React, { useState } from 'react';
+import ColorPicker from '../index';
+import Button from '../../button';
+
+export default {
+  title: 'ColorPicker',
+}
+
+
+export const Basic = () => {
+
+  return (
+      <div>
+            <ColorPicker alpha={true}/>
+          <ColorPicker alpha={true} usePopover={true} popoverProps={{trigger:'custom',visible:true}}>
+              <Button>test</Button>
+          </ColorPicker>
+      </div>
+  );
+};
+
+
+
+export const Control  = ()=>{
+    const [value,setValue] = useState({
+        "hsva": {
+            "s": 72,
+            "v": 60,
+            "a": 1,
+            "h": 0
+        },
+        "rgba": {
+            "r": 153,
+            "g": 43,
+            "b": 43,
+            "a": 1
+        },
+        "hex": "#992b2b"
+    });
+    console.log(value);
+    return <div>
+        <ColorPicker value={value} onChange={(value)=>{
+            setValue(value)
+        }} className={""} alpha={true}/>
+    </div>
+
+}

+ 14 - 0
packages/semi-ui/colorPicker/_story/colorPicker.stories.tsx

@@ -0,0 +1,14 @@
+import React from 'react';
+import ColorPicker from '../index';
+import { storiesOf } from '@storybook/react';
+
+
+const stories = storiesOf('ColorPicker', module);
+
+stories.add('不同大小', () => (
+  <div>
+      <ColorPicker alpha={false} onChange={(v)=>{
+          console.log(v)
+      }}/>
+  </div>
+));

+ 165 - 0
packages/semi-ui/colorPicker/index.tsx

@@ -0,0 +1,165 @@
+import React, { CSSProperties, PropsWithChildren, ReactNode } from 'react';
+import ColorPickerFoundation, {
+    ColorPickerProps,
+    ColorPickerState
+} from '@douyinfe/semi-foundation/colorPicker/foundation';
+import BaseComponent from '../_base/baseComponent';
+import { PopoverProps } from '../popover';
+import ColorChooseArea from './ColorChooseArea';
+import { ColorPickerAdapter, ColorValue } from '@douyinfe/semi-foundation/colorPicker/foundation';
+import AlphaSlider from './AlphaSlider';
+import ColorSlider from './ColorSlider';
+import DataPart from './DataPart';
+import cls from 'classnames';
+import "@douyinfe/semi-foundation/colorPicker/colorPicker.scss";
+import { cssClasses } from '@douyinfe/semi-foundation/colorPicker/constants';
+import Popover from '../popover';
+import {
+    hexToHsva,
+    hexToRgba, hsvaStringToHsva, hsvaToHex, hsvaToRgba,
+    rgbaStringToHsva,
+    rgbaStringToRgba, rgbaToHex, rgbStringToHsva, rgbStringToRgba,
+} from '@douyinfe/semi-foundation/colorPicker/utils/convert';
+
+
+
+
+export interface ColorPickerReactProps extends ColorPickerProps {
+    usePopover?: boolean;
+    popoverProps?: PopoverProps;
+    className?: string;
+    style?: CSSProperties;
+    bottomSlot?: ReactNode;
+    topSlot?: ReactNode
+}
+
+
+export interface ColorPickerReactState extends ColorPickerState {
+}
+
+
+class ColorPicker extends BaseComponent<PropsWithChildren<ColorPickerReactProps>, ColorPickerReactState> {
+    static __SemiComponentName__ = "ColorPicker";
+    public foundation: ColorPickerFoundation;
+
+    constructor(props: ColorPickerReactProps) {
+        super(props);
+        this.foundation = new ColorPickerFoundation(this.adapter);
+        const initValue = (props.value ?? props.defaultValue);
+        this.state = {
+            currentColor: initValue,
+        };
+    }
+
+    static defaultProps = {
+        defaultValue: {
+            hsva: { h: 176, s: 71, v: 77, a: 1 },
+            rgba: { r: 57, g: 197, b: 187, a: 1 },
+            hex: '#39c5bb'
+        },
+        eyeDropper: true,
+        defaultFormat: 'hex'
+    }
+
+    get adapter(): ColorPickerAdapter<ColorPickerReactProps, ColorPickerReactState> {
+        return {
+            ...super.adapter,
+            notifyChange: (value) => {
+                this.props.onChange?.(value);
+            }
+        };
+    }
+
+    static colorStringToValue = (raw: string) => {
+        if (raw.startsWith("#")) {
+            return {
+                hsva: hexToHsva(raw),
+                rgba: hexToRgba(raw),
+                hex: raw
+            };
+        } else if (raw.startsWith('rgba')) {
+            const rgba = rgbaStringToRgba(raw);
+            return {
+                hsva: rgbaStringToHsva(raw),
+                rgba: rgba,
+                hex: rgbaToHex(rgba)
+            };
+        } else if (raw.startsWith("rgb")) {
+            const rgba = rgbStringToRgba(raw);
+            return {
+                hsva: rgbStringToHsva(raw),
+                rgba: rgba,
+                hex: rgbaToHex(rgba)
+            };
+        } else if (raw.startsWith("hsv")) {
+            const hsva = hsvaStringToHsva(raw);
+            const rgba = hsvaToRgba(hsva);
+            const hex = hsvaToHex(hsva);
+            return {
+                hsva,
+                rgba,
+                hex
+            };
+        } else {
+            throw new Error("Semi ColorPicker: error on static colorStringToValue method, input value is invalid: " + raw);
+        }
+    }
+
+
+    renderPicker() {
+        const { className: userClassName } = this.props;
+        const className = cls(`${cssClasses.PREFIX}`, userClassName);
+        const currentColor = this.foundation.getCurrentColor();
+        return <div className={className}>
+            {this.props.topSlot}
+            <ColorChooseArea hsva={currentColor.hsva} foundation={this.foundation} onChange={({ s, v }) => {
+                this.foundation.handleChange({
+                    s,
+                    v,
+                    a: currentColor.hsva.a,
+                    h: currentColor.hsva.h
+                }, 'hsva');
+            }} handleSize={20} width={this.props.width ?? 280} height={this.props.height ?? 280}/>
+            <ColorSlider width={this.props.width ?? 280}
+                height={10}
+                handleSize={18}
+                hue={currentColor.hsva.h}
+                className={'colorSliderWrapper'}
+                foundation={this.foundation}
+            />
+            {this.props.alpha && <AlphaSlider width={this.props.width ?? 280}
+                height={10}
+                handleSize={18}
+                hsva={currentColor.hsva}
+                className={'alphaSliderWrapper'}
+                foundation={this.foundation}
+            />}
+            <DataPart currentColor={currentColor}
+                eyeDropper={this.props.eyeDropper}
+                alpha={this.props.alpha}
+                width={this.props.width ?? 280}
+                foundation={this.foundation}
+                defaultFormat={this.props.defaultFormat}/>
+            {this.props.bottomSlot}
+        </div>;
+    }
+
+    render() {
+        const currentColor = this.foundation.getCurrentColor();
+        if (this.props.usePopover) {
+            return <Popover {...this.props.popoverProps}
+                className={cls(`${cssClasses.PREFIX}-popover`, this.props.popoverProps?.className)}
+                content={this.renderPicker()}>
+                {this.props.children ?? <div style={{ backgroundColor: currentColor.hex }}
+                    className={cls(`${cssClasses.PREFIX}-popover-defaultChildren`)}></div>}
+            </Popover>;
+        } else {
+            return this.renderPicker();
+        }
+    }
+}
+
+export type { ColorValue };
+export * from "@douyinfe/semi-foundation/colorPicker/interface";
+
+export default ColorPicker;

+ 1 - 0
packages/semi-ui/index.ts

@@ -103,6 +103,7 @@ export { default as Image } from './image';
 export { Preview as ImagePreview } from './image';
 
 export { default as semiGlobal } from "./_utils/semi-global";
+export { default as ColorPicker } from "./colorPicker";
 
 export { default as PinCode } from "./pincode";
 

+ 1 - 1
packages/semi-ui/tsconfig.json

@@ -6,7 +6,7 @@
         "sourceMap": true,
         "allowJs": true,
         "module": "es6",
-        "lib": ["es7", "dom"],
+        "lib": ["esnext", "dom"],
         "moduleResolution": "node",
         "noImplicitAny": false,
         "jsx": "react",