+ {monochromatic?.map((c: any, key: number) => (
+
handleClick(c)}
+ />
+ ))}
+
+
+ Triad
+
+
+ {triad?.map((c: any, key: number) => (
+
handleClick(c)}
+ />
+ ))}
+
+
+ Tetrad
+
+
+ {tetrad?.map((c: any, key: number) => (
+
handleClick(c)}
+ />
+ ))}
+
+
+
+ )
+}
+
+export default ComparibleColors
diff --git a/src/color-picker/components/Controls.tsx b/src/color-picker/components/Controls.tsx
new file mode 100644
index 0000000..74784f6
--- /dev/null
+++ b/src/color-picker/components/Controls.tsx
@@ -0,0 +1,286 @@
+import { useState } from 'react'
+import { usePicker } from '../context'
+import {
+ colorTypeBtnStyles,
+ controlBtnStyles,
+ modalBtnStyles,
+} from '../styles/styles'
+import type { LocalesProps } from '../types'
+import ComparibleColors from './ComparibleColors'
+import GradientControls from './GradientControls'
+import AdvancedControls from './advanced-controls'
+import EyeDropper from './eye-dropper'
+import { InputsIcon, PaletteIcon, SlidersIcon } from './icon'
+
+const ColorTypeBtns = ({
+ hideColorTypeBtns,
+ setGradient,
+ isGradient,
+ setSolid,
+ locales,
+}: {
+ hideColorTypeBtns?: boolean
+ isGradient?: boolean
+ setSolid: () => void
+ setGradient: () => void
+ locales?: LocalesProps
+}) => {
+ const { defaultStyles, pickerIdSuffix } = usePicker()
+
+ if (hideColorTypeBtns) {
+ return
+ } else {
+ return (
+
+
+ {locales?.CONTROLS?.SOLID}
+
+
+ {locales?.CONTROLS?.GRADIENT}
+
+
+ )
+ }
+}
+
+const InputTypeDropdown = ({
+ openInputType,
+ setOpenInputType,
+}: {
+ openInputType?: boolean
+ setOpenInputType: (arg0: boolean) => void
+}) => {
+ const { inputType, setInputType, defaultStyles, pickerIdSuffix } = usePicker()
+ const vTrans = openInputType
+ ? 'visibility 0ms linear'
+ : 'visibility 100ms linear 150ms'
+ const zTrans = openInputType
+ ? 'z-index 0ms linear'
+ : 'z-index 100ms linear 150ms'
+ const oTrans = openInputType
+ ? 'opacity 120ms linear'
+ : 'opacity 150ms linear 50ms'
+
+ const handleInputType = (e: any, val: string) => {
+ if (openInputType) {
+ e.stopPropagation()
+ setInputType(val)
+ setOpenInputType(false)
+ }
+ }
+
+ return (
+
+
handleInputType(e, 'rgb')}
+ style={modalBtnStyles(inputType === 'rgb', defaultStyles)}
+ >
+ RGB
+
+
handleInputType(e, 'hsl')}
+ style={modalBtnStyles(inputType === 'hsl', defaultStyles)}
+ >
+ HSL
+
+
handleInputType(e, 'hsv')}
+ style={modalBtnStyles(inputType === 'hsv', defaultStyles)}
+ >
+ HSV
+
+
handleInputType(e, 'cmyk')}
+ style={modalBtnStyles(inputType === 'cmyk', defaultStyles)}
+ >
+ CMYK
+
+
+ )
+}
+
+const Controls = ({
+ locales,
+ hideEyeDrop = false,
+ hideAdvancedSliders = false,
+ hideColorGuide = false,
+ hideInputType = false,
+ hideColorTypeBtns = false,
+ hideGradientControls = false,
+ hideGradientType = false,
+ hideGradientAngle = false,
+ hideGradientStop = false,
+}: {
+ locales?: LocalesProps
+ hideEyeDrop?: boolean
+ hideAdvancedSliders?: boolean
+ hideColorGuide?: boolean
+ hideInputType?: boolean
+ hideColorTypeBtns?: boolean
+ hideGradientControls?: boolean
+ hideGradientType?: boolean
+ hideGradientAngle?: boolean
+ hideGradientStop?: boolean
+}) => {
+ const {
+ config,
+ onChange,
+ isGradient,
+ handleChange,
+ previous,
+ defaultStyles,
+ pickerIdSuffix,
+ } = usePicker()
+ const { defaultColor, defaultGradient } = config
+ const [openComparibles, setOpenComparibles] = useState(false)
+ const [openInputType, setOpenInputType] = useState(false)
+ const [openAdvanced, setOpenAdvanced] = useState(false)
+
+ const noTools =
+ hideEyeDrop && hideAdvancedSliders && hideColorGuide && hideInputType
+
+ const solidColor = previous?.color ?? defaultColor
+ const gradientColor = previous?.gradient ?? defaultGradient
+
+ const setSolid = () => {
+ onChange(solidColor)
+ }
+
+ const setGradient = () => {
+ onChange(gradientColor)
+ }
+
+ const allRightControlsHidden =
+ hideEyeDrop && hideAdvancedSliders && hideColorGuide && hideInputType
+ const allControlsHidden = allRightControlsHidden && hideColorTypeBtns
+
+ if (allControlsHidden) {
+ if (isGradient && !hideGradientControls) {
+ return (
+
+ )
+ } else {
+ return null
+ }
+ } else {
+ return (
+
+
+
+
+ {!allRightControlsHidden && (
+
+ {!hideEyeDrop &&
}
+ {!hideAdvancedSliders && (
+
setOpenAdvanced(!openAdvanced)}
+ // className="rbgcp-control-btn rbgcp-advanced-btn"
+ style={controlBtnStyles(openAdvanced, defaultStyles)}
+ >
+
+
+ )}
+ {!hideColorGuide && (
+
setOpenComparibles(!openComparibles)}
+ // className="rbgcp-control-btn rbgcp-comparibles-btn"
+ id={`rbgcp-comparibles-btn${pickerIdSuffix}`}
+ >
+
+
+ )}
+ {!hideInputType && (
+
setOpenInputType(!openInputType)}
+ // className="rbgcp-control-btn rbgcp-color-model-btn"
+ style={controlBtnStyles(openInputType, defaultStyles)}
+ >
+
+
+
+ )}
+
+ )}
+
+ {!hideAdvancedSliders && (
+
+ )}
+ {!hideColorGuide && (
+
+ )}
+ {isGradient && !hideGradientControls && (
+
+ )}
+
+ )
+ }
+}
+
+export default Controls
diff --git a/src/color-picker/components/GradientControls.tsx b/src/color-picker/components/GradientControls.tsx
new file mode 100644
index 0000000..208f864
--- /dev/null
+++ b/src/color-picker/components/GradientControls.tsx
@@ -0,0 +1,224 @@
+import { usePicker } from '../context'
+import { formatInputValues, low, high } from '../utils/formatters'
+import { controlBtnStyles } from '../styles/styles'
+import TrashIcon, {
+ LinearIcon,
+ RadialIcon,
+ DegreesIcon,
+ StopIcon,
+} from './icon'
+
+const GradientType = () => {
+ const { gradientType, onChange, value, defaultStyles, pickerIdSuffix } =
+ usePicker()
+ const isLinear = gradientType === 'linear-gradient'
+ const isRadial = gradientType === 'radial-gradient'
+
+ const handleLinear = () => {
+ const remaining = value.split(/,(.+)/)[1]
+ onChange(`linear-gradient(90deg, ${remaining}`)
+ }
+
+ const handleRadial = () => {
+ const remaining = value.split(/,(.+)/)[1]
+ onChange(`radial-gradient(circle, ${remaining}`)
+ }
+
+ return (
+
+
{
+ return
+ }}
+ >
+
+
+
{
+ return
+ }}
+ >
+
+
+
+ )
+}
+
+const StopPicker = () => {
+ const {
+ currentLeft,
+ currentColor,
+ defaultStyles,
+ handleGradient,
+ pickerIdSuffix,
+ } = usePicker()
+
+ const handleMove = (newVal: string) => {
+ handleGradient(currentColor, formatInputValues(parseInt(newVal), 0, 100))
+ }
+
+ return (
+
+
+ handleMove(e.target.value)}
+ style={{
+ ...defaultStyles.rbgcpControlInput,
+ ...defaultStyles.rbgcpStopInput,
+ }}
+ // className="rbgcp-control-input rbgcp-stop-input"
+ />
+
+ )
+}
+
+const DegreePicker = () => {
+ const { degrees, onChange, value, defaultStyles, pickerIdSuffix } =
+ usePicker()
+
+ const handleDegrees = (e: any) => {
+ const newValue = formatInputValues(e.target.value, 0, 360)
+ const remaining = value.split(/,(.+)/)[1]
+ onChange(`linear-gradient(${newValue ?? 0}deg, ${remaining}`)
+ }
+
+ return (
+
+ )
+}
+
+const DeleteBtn = () => {
+ const {
+ colors,
+ selectedColor,
+ createGradientStr,
+ defaultStyles,
+ pickerIdSuffix,
+ } = usePicker()
+
+ const deletePoint = () => {
+ if (colors?.length > 2) {
+ const formatted = colors?.map((fc: any, i: number) => ({
+ ...fc,
+ value: i === selectedColor - 1 ? high(fc) : low(fc),
+ }))
+ const remaining = formatted?.filter(
+ (_: any, i: number) => i !== selectedColor
+ )
+ createGradientStr(remaining)
+ }
+ }
+
+ return (
+
{
+ return
+ }}
+ >
+
+
+ )
+}
+
+const GradientControls = ({
+ hideGradientType,
+ hideGradientAngle,
+ hideGradientStop,
+}: {
+ hideGradientType?: boolean
+ hideGradientAngle?: boolean
+ hideGradientStop?: boolean
+}) => {
+ const { gradientType, defaultStyles, pickerIdSuffix } = usePicker()
+ return (
+
+ {!hideGradientType &&
}
+
+ {!hideGradientAngle && gradientType === 'linear-gradient' && (
+
+ )}
+
+ {!hideGradientStop &&
}
+
+
+ )
+}
+
+export default GradientControls
diff --git a/src/color-picker/components/Inputs.tsx b/src/color-picker/components/Inputs.tsx
new file mode 100644
index 0000000..6537246
--- /dev/null
+++ b/src/color-picker/components/Inputs.tsx
@@ -0,0 +1,454 @@
+import React, { useEffect, useRef, useState } from 'react'
+import tc from 'tinycolor2'
+import { InputNumberSelect } from '../../input-number-select'
+import { usePicker } from '../context'
+import { cmykToRgb, getHexAlpha, rgb2cmyk } from '../utils/converters'
+import { round } from '../utils/formatters'
+
+const HexInput = ({
+ opacity,
+ tinyColor,
+ showHexAlpha,
+ handleChange,
+}: {
+ tinyColor: any
+ opacity: number
+ showHexAlpha: boolean
+ handleChange: (arg0: string) => void
+}) => {
+ const [disable, setDisable] = useState('')
+ const hex = tinyColor.toHex()
+ const [newHex, setNewHex] = useState(hex)
+ const dragStartValue = useRef(0)
+ const dragStartX = useRef(0)
+
+ useEffect(() => {
+ if (disable !== 'hex') {
+ setNewHex(hex)
+ }
+ }, [tinyColor, disable, hex])
+
+ const hexFocus = () => {
+ setDisable('hex')
+ }
+
+ const hexBlur = () => {
+ setDisable('')
+ }
+
+ const handleHexInput = (e: React.ChangeEvent
) => {
+ const val = e.target.value
+ setNewHex(val)
+ if (tc(val).isValid()) {
+ const { r, g, b } = tc(val).toRgb()
+ const newColor = `rgba(${r}, ${g}, ${b}, ${opacity})`
+ handleChange(newColor)
+ }
+ }
+
+ const handleMouseDown = (e: React.MouseEvent) => {
+ e.preventDefault()
+ dragStartValue.current = parseInt(tinyColor.toHex(), 16)
+ dragStartX.current = e.clientX
+ setDisable('hex')
+ document.addEventListener('mousemove', handleMouseMove)
+ document.addEventListener('mouseup', handleMouseUp)
+ }
+
+ const handleMouseUp = () => {
+ setDisable('')
+ document.removeEventListener('mousemove', handleMouseMove)
+ document.removeEventListener('mouseup', handleMouseUp)
+ }
+
+ const handleMouseMove = (e: MouseEvent) => {
+ const movementX = e.clientX - dragStartX.current
+ const step = 55000
+ let newColorInt = Math.round(dragStartValue.current + movementX * step)
+ newColorInt = Math.max(0, Math.min(0xffffff, newColorInt))
+
+ const newColorHex = newColorInt.toString(16).padStart(6, '0')
+ setNewHex(newColorHex)
+
+ const { r, g, b } = tc(newColorHex).toRgb()
+ handleChange(`rgba(${r}, ${g}, ${b}, ${opacity})`)
+ }
+
+ const displayValue = showHexAlpha
+ ? `${newHex}${getHexAlpha(opacity)}`
+ : newHex
+
+ const wrapperStyle: React.CSSProperties = {
+ display: 'inline-flex',
+ alignItems: 'center',
+ overflow: 'hidden',
+ borderRadius: '6px',
+ border: '1px solid #333',
+ backgroundColor: '#1a1a1a',
+ height: 28,
+ width: '108px',
+ }
+
+ const handleStyle: React.CSSProperties = {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ width: 28,
+ cursor: 'ew-resize',
+ color: '#888',
+ fontWeight: 'bold',
+ fontFamily: 'monospace',
+ userSelect: 'none',
+ fontSize: '16px',
+ }
+
+ const inputStyle: React.CSSProperties = {
+ width: '100%',
+ textAlign: 'left',
+ paddingLeft: 8,
+ border: 'none',
+ borderRadius: 0,
+ height: '100%',
+ backgroundColor: 'transparent',
+ color: '#D4D4D4',
+ outline: 'none',
+ fontSize: '14px',
+ }
+
+ return (
+
+ )
+}
+
+const RGBInputs = ({
+ hc,
+ handleChange,
+}: {
+ hc: any
+ handleChange: (arg0: string) => void
+}) => {
+ const handleRgb = ({ r, g, b }: { r: number; g: number; b: number }) => {
+ handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
+ }
+
+ const createHandler =
+ (channel: 'r' | 'g' | 'b') => (newValue: string | number) => {
+ const numValue = Number(newValue) || 0
+ const newColor = { r: hc?.r, g: hc?.g, b: hc?.b }
+ newColor[channel] = numValue
+ handleRgb(newColor as { r: number; g: number; b: number })
+ }
+
+ return (
+
+
+
+
+
+ )
+}
+
+const HSLInputs = ({
+ hc,
+ setHc,
+ tinyColor,
+ handleChange,
+}: {
+ hc: any
+ tinyColor: any
+ setHc: (arg0: any) => void
+ handleChange: (arg0: string) => void
+}) => {
+ const { s, l } = tinyColor.toHsl()
+
+ const handleH = (h: number) => {
+ const { r, g, b } = tc({ h: h, s: s, l: l }).toRgb()
+ handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
+ setHc({ ...hc, h })
+ }
+
+ const handleSl = (value: { s: number; l: number }) => {
+ const { r, g, b } = tc({ h: hc?.h, ...value }).toRgb()
+ handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
+ }
+
+ return (
+
+ handleH(Number(newVal) || 0)}
+ className="flex-basis-1/3 text-[#888] h-7 bg-[#1A1A1A] border border-[#333333] text-xs dark:bg-[#1A1A1A]"
+ classNameInput="text-xs px-0 text-[#D4D4D4]"
+ />
+ handleSl({ s: (Number(newVal) || 0) / 100, l: l })}
+ className="flex-basis-1/3 text-[#888] h-7 bg-[#1A1A1A] border border-[#333333] text-xs dark:bg-[#1A1A1A]"
+ classNameInput="text-xs px-0 text-[#D4D4D4]"
+ />
+ handleSl({ s: s, l: (Number(newVal) || 0) / 100 })}
+ className="flex-basis-1/3 text-[#888] h-7 bg-[#1A1A1A] border border-[#333333] text-xs dark:bg-[#1A1A1A]"
+ classNameInput="text-xs px-0 text-[#D4D4D4]"
+ />
+
+ )
+}
+
+const HSVInputs = ({
+ hc,
+ setHc,
+ handleChange,
+}: {
+ hc: any
+ setHc: (arg0: any) => void
+ handleChange: (arg0: string) => void
+}) => {
+ const handleH = (h: number) => {
+ const { r, g, b } = tc({ h: h, s: hc?.s, v: hc?.v }).toRgb()
+ handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
+ setHc({ ...hc, h })
+ }
+
+ const handleSV = (value: { s: number; v: number }) => {
+ const { r, g, b } = tc({ h: hc?.h, ...value }).toRgb()
+ handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
+ }
+
+ return (
+
+ handleH(Number(newVal) || 0)}
+ className="flex-basis-1/3 text-[#888] h-7 bg-[#1A1A1A] border border-[#333333] text-xs dark:bg-[#1A1A1A]"
+ classNameInput="text-xs px-0 text-[#D4D4D4]"
+ />
+
+ handleSV({ s: (Number(newVal) || 0) / 100, v: hc?.v })
+ }
+ className="flex-basis-1/3 text-[#888] h-7 bg-[#1A1A1A] border border-[#333333] text-xs dark:bg-[#1A1A1A]"
+ classNameInput="text-xs px-0 text-[#D4D4D4]"
+ />
+
+ handleSV({ s: hc?.s, v: (Number(newVal) || 0) / 100 })
+ }
+ className="flex-basis-1/3 text-[#888] h-7 bg-[#1A1A1A] border border-[#333333] text-xs dark:bg-[#1A1A1A]"
+ classNameInput="text-xs px-0 text-[#D4D4D4]"
+ />
+
+ )
+}
+
+const CMKYInputs = ({
+ hc,
+ handleChange,
+}: {
+ hc: any
+ handleChange: (arg0: string) => void
+}) => {
+ const { c, m, y, k } = rgb2cmyk(hc?.r, hc?.g, hc?.b)
+
+ const handleCmyk = (value: any) => {
+ const { r, g, b } = cmykToRgb(value)
+ handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
+ }
+
+ const createHandler =
+ (channel: 'c' | 'm' | 'y' | 'k') => (newValue: string | number) => {
+ const numValue = (Number(newValue) || 0) / 100
+ const newColor = { c, m, y, k }
+ newColor[channel] = numValue
+ handleCmyk(newColor)
+ }
+
+ return (
+
+
+
+
+
+
+ )
+}
+
+const Inputs = () => {
+ const {
+ hc,
+ setHc,
+ inputType,
+ tinyColor,
+ hideOpacity: _hideOpacity,
+ showHexAlpha,
+ handleChange,
+ defaultStyles: _defaultStyles,
+ pickerIdSuffix,
+ } = usePicker()
+
+ return (
+
+ )
+}
+
+export default Inputs
diff --git a/src/color-picker/components/Picker.tsx b/src/color-picker/components/Picker.tsx
new file mode 100644
index 0000000..3d33f36
--- /dev/null
+++ b/src/color-picker/components/Picker.tsx
@@ -0,0 +1,81 @@
+import { usePicker } from '../context'
+import { type LocalesProps } from '../types'
+import Controls from './Controls'
+import Inputs from './Inputs'
+import Presets from './Presets'
+import ColorSpectrum from './color-spectrum'
+import GradientBar from './gradient-bar'
+import Hue from './hue'
+import Opacity from './opacity'
+
+const Picker = ({
+ locales,
+ presets,
+ hideHue,
+ hideInputs,
+ hidePresets,
+ hideOpacity,
+ hideEyeDrop,
+ hideControls,
+ hideInputType,
+ hideColorGuide,
+ hidePickerSquare,
+ hideGradientType,
+ hideGradientStop,
+ hideGradientAngle,
+ hideColorTypeBtns,
+ hideAdvancedSliders,
+ hideGradientControls,
+}: PickerProps) => {
+ const { isGradient, pickerIdSuffix } = usePicker()
+
+ return (
+
+ {!hidePickerSquare &&
}
+ {!hideControls && (
+
+ )}
+ {isGradient &&
}
+ {!hideHue &&
}
+ {!hideOpacity &&
}
+ {!hideInputs &&
}
+ {!hidePresets &&
}
+
+ )
+}
+
+export default Picker
+
+type PickerProps = {
+ hideControls?: boolean
+ hideInputs?: boolean
+ hidePresets?: boolean
+ hideOpacity?: boolean
+ hideHue?: boolean
+ presets?: string[]
+ hideEyeDrop?: boolean
+ hideAdvancedSliders?: boolean
+ hideColorGuide?: boolean
+ hideInputType?: boolean
+ hideColorTypeBtns?: boolean
+ hideGradientType?: boolean
+ hideGradientAngle?: boolean
+ hideGradientStop?: boolean
+ hideGradientControls?: boolean
+ locales?: LocalesProps
+ hidePickerSquare?: boolean
+}
diff --git a/src/color-picker/components/Portal.tsx b/src/color-picker/components/Portal.tsx
new file mode 100644
index 0000000..ea1a7c1
--- /dev/null
+++ b/src/color-picker/components/Portal.tsx
@@ -0,0 +1,26 @@
+import { memo, useEffect, useRef, useState, type ReactNode } from 'react'
+import { createPortal } from 'react-dom'
+
+const Portal = ({ children }: { children: ReactNode }) => {
+ const id = 'id' + Math.random().toString(16).slice(2)
+ const el = useRef(
+ document.getElementById(id) ?? document.createElement('div')
+ )
+ const [dynamic] = useState(!el.current.parentElement)
+
+ useEffect(() => {
+ const refValue = el.current
+ if (dynamic) {
+ el.current.id = id
+ document.body.appendChild(el.current)
+ }
+ return () => {
+ if (dynamic && refValue.parentElement) {
+ refValue.parentElement.removeChild(refValue)
+ }
+ }
+ }, [id, dynamic])
+ return createPortal(children, el.current)
+}
+
+export default memo(Portal)
diff --git a/src/color-picker/components/Presets.tsx b/src/color-picker/components/Presets.tsx
new file mode 100644
index 0000000..1b4d3cb
--- /dev/null
+++ b/src/color-picker/components/Presets.tsx
@@ -0,0 +1,97 @@
+import { fakePresets } from '../constants'
+import { usePicker } from '../context'
+
+const Presets = ({ presets = [] }: { presets?: string[] }) => {
+ const {
+ value,
+ onChange,
+ isDarkMode,
+ squareWidth,
+ handleChange,
+ pickerIdSuffix,
+ } = usePicker()
+
+ const getPresets = () => {
+ if (presets?.length > 0) {
+ return presets?.slice(0, 18)
+ } else {
+ return fakePresets
+ }
+ }
+
+ const handlePresetClick = (preset: string) => {
+ if (preset?.includes('gradient')) {
+ onChange(preset)
+ } else {
+ handleChange(preset)
+ }
+ }
+
+ const getBorder = (p: string) => {
+ if (!p || isDarkMode) return ''
+ const c = p?.replace(' ', '')
+ if (c === 'rgba(255,255,255,1)') {
+ return '1px solid #96959c'
+ }
+ return ''
+ }
+
+ return (
+