diff --git a/packages/color-picker/demos/Controlled.tsx b/packages/color-picker/demos/Controlled.tsx new file mode 100644 index 00000000..7b6951e2 --- /dev/null +++ b/packages/color-picker/demos/Controlled.tsx @@ -0,0 +1,18 @@ +import React, { useState } from 'react'; + +import SketchPicker from '@arvinxu/color-picker'; + +const Demo = () => { + const [x, setX] = useState(); + return ( + { + console.log(e); + setX(e); + }} + /> + ); +}; + +export default Demo; diff --git a/packages/color-picker/package.json b/packages/color-picker/package.json index ef4dff12..44f87769 100644 --- a/packages/color-picker/package.json +++ b/packages/color-picker/package.json @@ -23,8 +23,14 @@ "clean": "rm -rf es lib dist build coverage .umi" }, "dependencies": { - "reactcss": "^1.2.3", + "chroma-js": "^2.1.1", + "classnames": "^2.3.1", "lodash": "^4.17.21", - "tinycolor2": "^1.4.2" + "reactcss": "^1.2.3", + "tinycolor2": "^1.4.2", + "zustand": "^3.7.1" + }, + "devDependencies": { + "@types/lodash": "^4.14.181" } } diff --git a/packages/color-picker/src/ColorPicker.md b/packages/color-picker/src/ColorPicker.md index 4c352aa3..b530c465 100644 --- a/packages/color-picker/src/ColorPicker.md +++ b/packages/color-picker/src/ColorPicker.md @@ -1,5 +1,5 @@ --- -title: ColorPicker +title: ColorPicker 取色器 order: 2 group: path: / @@ -7,7 +7,7 @@ nav: path: /components --- -# ColorPicker +# ColorPicker 取色器 一个更好用的取色器 diff --git a/packages/color-picker/src/components/sketch/__snapshots__/spec.js.snap b/packages/color-picker/src/__snapshots__/spec.js.snap similarity index 100% rename from packages/color-picker/src/components/sketch/__snapshots__/spec.js.snap rename to packages/color-picker/src/__snapshots__/spec.js.snap diff --git a/packages/color-picker/src/components/sketch/SketchFields.tsx b/packages/color-picker/src/components/SketchFields.tsx similarity index 92% rename from packages/color-picker/src/components/sketch/SketchFields.tsx rename to packages/color-picker/src/components/SketchFields.tsx index 9be2df43..d6cdc428 100644 --- a/packages/color-picker/src/components/sketch/SketchFields.tsx +++ b/packages/color-picker/src/components/SketchFields.tsx @@ -2,21 +2,18 @@ import React from 'react'; import reactCSS from 'reactcss'; -import * as color from '../../helpers/color'; +import * as color from '../helpers/color'; -import { EditableInput } from '../common'; -import type { HSLColor, RGBColor } from './Sketch'; +import { EditableInput } from './common'; +import { colorSelector, useStore } from '@arvinxu/color-picker/store'; +import isEqual from 'lodash/isEqual'; export const SketchFields: React.FC<{ - hex?: string; - hsl?: HSLColor; - rgb?: RGBColor; - hsv?: RGBColor; - renderers?: any; - width?: any; onChange?: (color: any, e: React.ChangeEvent) => void | undefined; disableAlpha?: boolean; -}> = ({ onChange, rgb, hsl, hex, disableAlpha }) => { +}> = ({ onChange, disableAlpha }) => { + const { rgb, hsl, hex } = useStore(colorSelector, isEqual); + const styles = reactCSS( { default: { diff --git a/packages/color-picker/src/components/sketch/SketchPresetColors.tsx b/packages/color-picker/src/components/SketchPresetColors.tsx similarity index 91% rename from packages/color-picker/src/components/sketch/SketchPresetColors.tsx rename to packages/color-picker/src/components/SketchPresetColors.tsx index 5454b8a4..5e608af5 100644 --- a/packages/color-picker/src/components/sketch/SketchPresetColors.tsx +++ b/packages/color-picker/src/components/SketchPresetColors.tsx @@ -1,7 +1,8 @@ -import React, { ChangeEvent } from 'react'; +import type { ChangeEvent } from 'react'; +import React from 'react'; -import { Swatch } from '../common'; -import { ColorChangeHandler, ColorResult, PresetColor } from './Sketch'; +import type { ColorChangeHandler, ColorResult, PresetColor } from '../types'; +import { Swatch } from './common'; export const SketchPresetColors: React.FC<{ colors?: PresetColor[] | undefined; diff --git a/packages/color-picker/src/components/common/ColorWrap.js b/packages/color-picker/src/components/common/ColorWrap.js deleted file mode 100644 index fa9e1fe4..00000000 --- a/packages/color-picker/src/components/common/ColorWrap.js +++ /dev/null @@ -1,72 +0,0 @@ -import React, { Component, PureComponent } from 'react'; -import debounce from 'lodash/debounce'; -import * as color from '../../helpers/color'; - -export const ColorWrap = (Picker) => { - class ColorPicker extends (PureComponent || Component) { - constructor(props) { - super(); - - this.state = { - ...color.toState(props.color, 0), - }; - - this.debounce = debounce((fn, data, event) => { - fn(data, event); - }, 100); - } - - static getDerivedStateFromProps(nextProps, state) { - return { - ...color.toState(nextProps.color, state.oldHue), - }; - } - - handleChange = (data, event) => { - const isValidColor = color.simpleCheckForValidColor(data); - if (isValidColor) { - const colors = color.toState(data, data.h || this.state.oldHue); - this.setState(colors); - this.props.onChangeComplete && this.debounce(this.props.onChangeComplete, colors, event); - this.props.onChange && this.props.onChange(colors, event); - } - }; - - handleSwatchHover = (data, event) => { - const isValidColor = color.simpleCheckForValidColor(data); - if (isValidColor) { - const colors = color.toState(data, data.h || this.state.oldHue); - this.props.onSwatchHover && this.props.onSwatchHover(colors, event); - } - }; - - render() { - const optionalEvents = {}; - if (this.props.onSwatchHover) { - optionalEvents.onSwatchHover = this.handleSwatchHover; - } - - return ( - - ); - } - } - - ColorPicker.propTypes = { - ...Picker.propTypes, - }; - - ColorPicker.defaultProps = { - ...Picker.defaultProps, - color: { - h: 250, - s: 0.5, - l: 0.2, - a: 1, - }, - }; - - return ColorPicker; -}; - -export default ColorWrap; diff --git a/packages/color-picker/src/components/common/index.ts b/packages/color-picker/src/components/common/index.ts index e56995ac..a4f14e9e 100644 --- a/packages/color-picker/src/components/common/index.ts +++ b/packages/color-picker/src/components/common/index.ts @@ -1,14 +1,8 @@ -// @ts-ignore +// @ts-nocheck + export { default as Alpha } from './Alpha'; -// @ts-ignore export { default as Checkboard } from './Checkboard'; -// @ts-ignore export { default as Hue } from './Hue'; -// @ts-ignore export { default as Saturation } from './Saturation'; -// @ts-ignore -export { default as ColorWrap } from './ColorWrap'; -// @ts-ignore export { default as EditableInput } from './EditableInput'; -// @ts-ignore export { default as Swatch } from './Swatch'; diff --git a/packages/color-picker/src/components/sketch/Sketch.tsx b/packages/color-picker/src/components/sketch/Sketch.tsx deleted file mode 100644 index 88630da4..00000000 --- a/packages/color-picker/src/components/sketch/Sketch.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import type { CSSProperties } from 'react'; -import React from 'react'; -import reactCSS from 'reactcss'; -import merge from 'lodash/merge'; - -export interface HSLColor { - a?: number | undefined; - h: number; - l: number; - s: number; -} - -export interface RGBColor { - a?: number | undefined; - b: number; - g: number; - r: number; -} - -interface Classes { - default: Partial; - [scope: string]: Partial; -} - -export type Color = string | HSLColor | RGBColor; - -export interface ColorResult { - hex: string; - hsl: HSLColor; - rgb: RGBColor; - source?: string; -} - -export type ColorChangeHandler = ( - color: ColorResult, - event: React.ChangeEvent, -) => void; - -export interface SketchPickerStylesProps { - picker: CSSProperties; - saturation: CSSProperties; - Saturation: CSSProperties; - controls: CSSProperties; - sliders: CSSProperties; - color: CSSProperties; - activeColor: CSSProperties; - hue: CSSProperties; - Hue: CSSProperties; - alpha: CSSProperties; - Alpha: CSSProperties; -} - -export interface ColorPickerProps { - color?: Color | undefined; - className?: string | undefined; - styles?: Partial> | undefined; - onChange?: ColorChangeHandler; - onChangeComplete?: ColorChangeHandler | undefined; -} - -export type PresetColor = { color: string; title: string } | string; - -export interface SketchPickerProps extends ColorPickerProps { - disableAlpha?: boolean | undefined; - presetColors?: PresetColor[] | undefined; - width?: string | undefined; - styles?: Partial> | undefined; - onSwatchHover?: (color: ColorResult, event: MouseEvent) => void; -} - -import { ColorWrap, Saturation, Hue, Alpha, Checkboard } from '../common'; -import SketchFields from './SketchFields'; -import SketchPresetColors from './SketchPresetColors'; - -export const Sketch: React.FC< - SketchPickerProps & { - hex?: string; - hsl?: HSLColor; - rgb?: RGBColor; - hsv?: RGBColor; - renderers?: any; - width?: any; - } -> = ({ - width, - rgb, - hex, - hsv, - hsl, - onChange, - onSwatchHover, - disableAlpha, - presetColors, - renderers, - styles: passedStyles = {}, - className = '', -}) => { - const styles: any = reactCSS( - //@ts-ignore - merge( - { - default: { - picker: { - width, - padding: '10px 10px 0', - boxSizing: 'initial', - background: '#fff', - borderRadius: '4px', - boxShadow: '0 0 0 1px rgba(0,0,0,.15), 0 8px 16px rgba(0,0,0,.15)', - }, - saturation: { - width: '100%', - paddingBottom: '75%', - position: 'relative', - overflow: 'hidden', - }, - Saturation: { - radius: '3px', - shadow: 'inset 0 0 0 1px rgba(0,0,0,.15), inset 0 0 4px rgba(0,0,0,.25)', - }, - controls: { - display: 'flex', - }, - sliders: { - padding: '4px 0', - flex: '1', - }, - color: { - width: '24px', - height: '24px', - position: 'relative', - marginTop: '4px', - marginLeft: '4px', - borderRadius: '50%', - overflow: 'hidden', - }, - activeColor: { - absolute: '0px 0px 0px 0px', - background: `rgba(${rgb!.r},${rgb!.g},${rgb!.b},${rgb!.a})`, - boxShadow: 'inset 0 0 0 1px rgba(0,0,0,.15), inset 0 0 4px rgba(0,0,0,.25)', - }, - hue: { - position: 'relative', - height: '10px', - overflow: 'hidden', - }, - Hue: { - radius: '2px', - shadow: 'inset 0 0 0 1px rgba(0,0,0,.15), inset 0 0 4px rgba(0,0,0,.25)', - }, - - alpha: { - position: 'relative', - height: '10px', - marginTop: '4px', - overflow: 'hidden', - }, - Alpha: { - radius: '2px', - shadow: 'inset 0 0 0 1px rgba(0,0,0,.15), inset 0 0 4px rgba(0,0,0,.25)', - }, - ...passedStyles, - }, - disableAlpha: { - color: { - height: '10px', - }, - hue: { - height: '10px', - }, - alpha: { - display: 'none', - }, - }, - }, - passedStyles, - ), - { disableAlpha }, - ); - - return ( -
-
- -
-
-
-
- -
-
- -
-
-
- -
-
-
- - - -
- ); -}; - -Sketch.defaultProps = { - disableAlpha: false, - width: 200, - styles: {}, - presetColors: [ - '#D0021B', - '#F5A623', - '#F8E71C', - '#8B572A', - '#7ED321', - '#417505', - '#BD10E0', - '#9013FE', - '#4A90E2', - '#50E3C2', - '#B8E986', - '#000000', - '#4A4A4A', - '#9B9B9B', - '#FFFFFF', - ], -}; - -const SketchPicker = ColorWrap(Sketch) as React.FC; - -export { SketchPicker }; diff --git a/packages/color-picker/src/container/Provider.tsx b/packages/color-picker/src/container/Provider.tsx new file mode 100644 index 00000000..a5024230 --- /dev/null +++ b/packages/color-picker/src/container/Provider.tsx @@ -0,0 +1,9 @@ +import type { FC } from 'react'; +import { createStore, Provider } from '../store'; +import React from 'react'; + +const Wrapper: FC = ({ children }) => { + return {children}; +}; + +export default Wrapper; diff --git a/packages/color-picker/src/container/Sketch.tsx b/packages/color-picker/src/container/Sketch.tsx new file mode 100644 index 00000000..ccb9fd05 --- /dev/null +++ b/packages/color-picker/src/container/Sketch.tsx @@ -0,0 +1,167 @@ +import type { FC } from 'react'; +import React from 'react'; +import reactCSS from 'reactcss'; +import merge from 'lodash/merge'; +import isEqual from 'lodash/isEqual'; +import cls from 'classnames'; + +import { Saturation, Hue, Alpha, Checkboard } from '../components/common'; +import SketchFields from '../components/SketchFields'; +import SketchPresetColors from '../components/SketchPresetColors'; + +import { colorSelector, useStore } from '../store'; +import type { ColorPickerProps } from '../types'; + +export const Sketch: FC = ({ onSwatchHover, className }) => { + const { + presetColors, + onChange, + styles: passedStyles, + width, + disableAlpha, + updateAlpha, + updateByHex, + updateHue, + updateByHsv, + } = useStore(); + + const { rgb, hsl, hsv, hex } = useStore(colorSelector, isEqual); + + const styles: any = reactCSS( + //@ts-ignore + merge( + { + default: { + picker: { + width, + padding: '10px 10px 0', + boxSizing: 'initial', + background: '#fff', + borderRadius: '4px', + // boxShadow: '0 0 0 1px rgba(0,0,0,.15), 0 8px 16px rgba(0,0,0,.15)', + boxShadow: '0 0 0 1px rgba(0,0,0,.15)', + }, + saturation: { + width: '100%', + paddingBottom: '75%', + position: 'relative', + overflow: 'hidden', + }, + Saturation: { + radius: '3px', + shadow: 'inset 0 0 0 1px rgba(0,0,0,.15), inset 0 0 4px rgba(0,0,0,.25)', + }, + controls: { + display: 'flex', + }, + sliders: { + padding: '4px 0', + flex: '1', + }, + color: { + width: '24px', + height: '24px', + position: 'relative', + marginTop: '4px', + marginLeft: '4px', + borderRadius: '50%', + overflow: 'hidden', + }, + activeColor: { + absolute: '0px 0px 0px 0px', + background: `rgba(${rgb!.r},${rgb!.g},${rgb!.b},${rgb!.a})`, + boxShadow: 'inset 0 0 0 1px rgba(0,0,0,.15), inset 0 0 4px rgba(0,0,0,.25)', + }, + hue: { + position: 'relative', + height: '10px', + overflow: 'hidden', + }, + Hue: { + radius: '2px', + shadow: 'inset 0 0 0 1px rgba(0,0,0,.15), inset 0 0 4px rgba(0,0,0,.25)', + }, + + alpha: { + position: 'relative', + height: '10px', + marginTop: '4px', + overflow: 'hidden', + }, + Alpha: { + radius: '2px', + shadow: 'inset 0 0 0 1px rgba(0,0,0,.15), inset 0 0 4px rgba(0,0,0,.25)', + }, + ...passedStyles, + }, + disableAlpha: { + color: { + height: '10px', + }, + hue: { + height: '10px', + }, + alpha: { + display: 'none', + }, + }, + }, + passedStyles, + ), + { disableAlpha }, + ); + + return ( +
+
+ +
+
+
+
+ { + updateHue(h); + }} + radius={4} + /> +
+
+ { + updateAlpha(a); + }} + radius={4} + /> +
+
+
+ +
+
+
+ + + { + updateByHex(e.hex); + }} + onSwatchHover={onSwatchHover} + /> +
+ ); +}; + +export default Sketch; diff --git a/packages/color-picker/src/container/index.tsx b/packages/color-picker/src/container/index.tsx new file mode 100644 index 00000000..491bded7 --- /dev/null +++ b/packages/color-picker/src/container/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import type { FC } from 'react'; +import type { ColorPickerProps } from '../types'; + +import Sketch from './Sketch'; +import Wrapper from './Provider'; + +const SketchPicker: FC = (props) => ( + + + +); + +export default SketchPicker; diff --git a/packages/color-picker/src/index.ts b/packages/color-picker/src/index.ts index e64ae0c0..3657471b 100644 --- a/packages/color-picker/src/index.ts +++ b/packages/color-picker/src/index.ts @@ -1,7 +1,6 @@ -import type { SketchPickerProps } from './components/sketch/Sketch'; -import { SketchPicker } from './components/sketch/Sketch'; +import SketchPicker from './container'; -export type { SketchPickerProps }; +export type { ColorPickerProps } from './types'; export { SketchPicker }; diff --git a/packages/color-picker/src/components/sketch/spec.js b/packages/color-picker/src/spec.js similarity index 90% rename from packages/color-picker/src/components/sketch/spec.js rename to packages/color-picker/src/spec.js index 84762eb3..c6f110c8 100644 --- a/packages/color-picker/src/components/sketch/spec.js +++ b/packages/color-picker/src/spec.js @@ -3,13 +3,13 @@ import React from 'react'; import renderer from 'react-test-renderer'; import { mount } from 'enzyme'; -import * as color from '../../helpers/color'; +import * as color from './helpers/color'; // import canvas from 'canvas' -import Sketch from './Sketch'; -import SketchFields from './SketchFields'; -import SketchPresetColors from './SketchPresetColors'; -import { Swatch } from '../common'; +import Sketch from './container/Sketch'; +import SketchFields from './components/SketchFields'; +import SketchPresetColors from './components/SketchPresetColors'; +import { Swatch } from './components/common'; test('Sketch renders correctly', () => { const tree = renderer.create().toJSON(); diff --git a/packages/color-picker/src/store.ts b/packages/color-picker/src/store.ts new file mode 100644 index 00000000..bb2bee9a --- /dev/null +++ b/packages/color-picker/src/store.ts @@ -0,0 +1,110 @@ +import create from 'zustand'; +import createContext from 'zustand/context'; +import type { Color } from 'chroma-js'; +import chroma from 'chroma-js'; + +import type { HSLColor, HSVColor, RGBColor } from './types'; + +type ColorMode = 'rgb' | 'hsl'; +interface ColorPickerState { + disableAlpha: boolean; + width: number; + styles: any; + colorMode: ColorMode; + presetColors: string[]; + onChange?: ({ hex, color }: { hex: string; color: Color }) => void; + colorModel: Color; +} +interface ColorPickerAction { + internalUpdateColor: (color: Color) => void; + updateAlpha: (a: number) => void; + updateHue: (hue: number) => void; + updateByHex: (hex: string) => void; + updateByHsv: (hsv: HSVColor) => void; +} + +export type ColorPickerStore = ColorPickerState & ColorPickerAction; + +const initialState: ColorPickerState = { + disableAlpha: false, + width: 200, + styles: {}, + colorMode: 'rgb', + presetColors: [ + '#D0021B', + '#F5A623', + '#F8E71C', + '#8B572A', + '#7ED321', + '#417505', + '#BD10E0', + '#9013FE', + '#4A90E2', + '#50E3C2', + '#B8E986', + '#000000', + '#4A4A4A', + '#9B9B9B', + '#FFFFFF', + ], + colorModel: chroma('#22194D'), +}; + +const createStore = () => + create((set, get) => ({ + ...initialState, + internalUpdateColor: (color) => { + set({ colorModel: color }); + + if (get().onChange) { + get().onChange({ hex: color.hex('auto'), color }); + } + }, + + updateAlpha: (a) => { + const { colorModel, internalUpdateColor } = get(); + internalUpdateColor(colorModel.alpha(a)); + }, + + updateHue: (h) => { + const { colorModel, internalUpdateColor } = get(); + internalUpdateColor(colorModel.set('hsl.h', h)); + }, + + updateByHex: (hex) => { + const { internalUpdateColor } = get(); + + internalUpdateColor(chroma(hex)); + }, + + updateByHsv: (hex) => { + const { h, s, v, a } = hex; + const { internalUpdateColor } = get(); + + internalUpdateColor(chroma([h, s, v, a], 'hsv')); + }, + })); + +const { Provider, useStore } = createContext(); + +export { Provider, useStore, createStore }; + +export const colorSelector = ( + s: ColorPickerState, +): { + rgb: RGBColor; + hsl: HSLColor; + hsv: HSVColor; + hex: string; +} => { + const [r, g, b, a] = s.colorModel.rgba(); + const hsv = s.colorModel.hsv(); + const hsl = s.colorModel.hsl(); + + return { + rgb: { r, g, b, a }, + hsv: { h: hsv[0], s: hsv[1], v: hsv[2], a }, + hsl: { h: hsl[0], s: hsl[1], l: hsl[2], a }, + hex: s.colorModel.hex(), + }; +}; diff --git a/packages/color-picker/src/types.ts b/packages/color-picker/src/types.ts new file mode 100644 index 00000000..d516ee43 --- /dev/null +++ b/packages/color-picker/src/types.ts @@ -0,0 +1,72 @@ +import type { CSSProperties } from 'react'; +import type React from 'react'; + +export interface HSLColor { + a?: number | undefined; + h: number; + l: number; + s: number; +} + +export interface HSVColor { + a?: number | undefined; + h: number; + s: number; + v: number; +} + +export interface RGBColor { + a?: number | undefined; + b: number; + g: number; + r: number; +} + +interface Classes { + default: Partial; + [scope: string]: Partial; +} + +export type Color = string | HSLColor | RGBColor; + +export interface ColorResult { + hex: string; + hsl: HSLColor; + rgb: RGBColor; + source?: string; +} + +export type ColorChangeHandler = ( + color: ColorResult, + event: React.ChangeEvent, +) => void; + +export interface SketchPickerStylesProps { + picker: CSSProperties; + saturation: CSSProperties; + Saturation: CSSProperties; + controls: CSSProperties; + sliders: CSSProperties; + color: CSSProperties; + activeColor: CSSProperties; + hue: CSSProperties; + Hue: CSSProperties; + alpha: CSSProperties; + Alpha: CSSProperties; +} + +export type PresetColor = { color: string; title: string } | string; + +export interface ColorPickerProps { + color?: Color | undefined; + className?: string | undefined; + + onChange?: ColorChangeHandler; + onChangeComplete?: ColorChangeHandler | undefined; + + disableAlpha?: boolean | undefined; + presetColors?: PresetColor[] | undefined; + width?: string | undefined; + styles?: Partial> | undefined; + onSwatchHover?: (color: ColorResult, event: MouseEvent) => void; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d45f238..0bb4399a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,13 +168,22 @@ importers: packages/color-picker: specifiers: + '@types/lodash': ^4.14.181 + chroma-js: ^2.1.1 + classnames: ^2.3.1 lodash: ^4.17.21 reactcss: ^1.2.3 tinycolor2: ^1.4.2 + zustand: ^3.7.1 dependencies: + chroma-js: 2.4.2 + classnames: 2.3.1 lodash: 4.17.21 reactcss: 1.2.3 tinycolor2: 1.4.2 + zustand: 3.7.1 + devDependencies: + '@types/lodash': 4.14.181 packages/float-label-input: specifiers: @@ -6306,6 +6315,10 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/lodash/4.14.181: + resolution: {integrity: sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==} + dev: true + /@types/mapbox-gl/1.13.2: resolution: {integrity: sha512-sv69WkijddNCIdLLyUsG90+X3Lh67a26lKsqaL8WbmXMkWITDrshe+sc9BI8oUV7sh+XD0jraI3qBe0NtJs7dw==} dependencies: