main e97eba085d67 cached
42 files
149.8 KB
44.8k tokens
55 symbols
1 requests
Download .txt
Repository: hxf31891/react-gradient-color-picker
Branch: main
Commit: e97eba085d67
Files: 42
Total size: 149.8 KB

Directory structure:
gitextract__r_hbhn9/

├── .github/
│   └── workflows/
│       ├── pull-request.yml
│       └── release-package.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .prettierrc
├── LICENSE
├── README.md
├── biome.json
├── package.json
├── src/
│   ├── components/
│   │   ├── AdvancedControls.tsx
│   │   ├── ComparibleColors.tsx
│   │   ├── Controls.tsx
│   │   ├── EyeDropper.tsx
│   │   ├── GradientBar.tsx
│   │   ├── GradientControls.tsx
│   │   ├── Hue.tsx
│   │   ├── Inputs.tsx
│   │   ├── Opacity.tsx
│   │   ├── Picker.tsx
│   │   ├── Portal.tsx
│   │   ├── Presets.tsx
│   │   ├── Square.tsx
│   │   ├── icon.tsx
│   │   └── index.tsx
│   ├── constants.ts
│   ├── context.tsx
│   ├── hooks/
│   │   ├── useColorPicker.ts
│   │   ├── usePaintHue.ts
│   │   └── usePaintSquare.ts
│   ├── index.ts
│   ├── shared/
│   │   └── types.ts
│   ├── styles/
│   │   ├── darkStyles.ts
│   │   └── styles.ts
│   └── utils/
│       ├── converters.ts
│       ├── formatters.ts
│       ├── gradientParser.ts
│       └── utils.ts
├── test/
│   ├── gradientParser.spec.js
│   └── utils.spec.js
├── tsconfig.build.json
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/pull-request.yml
================================================
name: Run tests

on: pull_request

jobs:
  run-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
      - run: npm i -g npm@latest
      - run: npm ci
      - run: npm test

  run-eslint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
      - run: npm i -g npm@latest
      - run: npm ci
      - run: npm run eslint


================================================
FILE: .github/workflows/release-package.yml
================================================
name: Release Package

on:
  release:
    types: [created]

jobs:
  run-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
      - run: npm i -g npm@latest
      - run: npm ci
      - run: npm test

  run-eslint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
      - run: npm i -g npm@latest
      - run: npm ci
      - run: npm run eslint

  publish-gpr:
    needs:
      - run-tests
      - run-eslint
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
          registry-url: https://npm.pkg.github.com/
      - run: npm i -g npm@latest
      - run: npm ci
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}


================================================
FILE: .gitignore
================================================
# OS
.DS_Store

# Cache
.cache
.playwright
.tmp
*.tsbuildinfo
.eslintcache

# Yarn
.pnp.*
**/.yarn/*
!**/.yarn/patches
!**/.yarn/plugins
!**/.yarn/releases
!**/.yarn/sdks
!**/.yarn/versions

# Project-generated directories and files
coverage
dist
node_modules
playwright-report
test-results
package.tgz

# Logs
npm-debug.log
yarn-error.log

# .env files
**/.env
**/.env.*
!**/.env.example


================================================
FILE: .npmignore
================================================
/*.log
/*.DS_Store
/.idea
/.gitignore
/node_modules/
/.github/
/src/
/babel.config.js
**/*.spec.js
/.eslintignore
/.prettierrc
/.eslintrc.js


================================================
FILE: .nvmrc
================================================
v16


================================================
FILE: .prettierrc
================================================
{
	"endOfLine": "lf",
	"semi": false,
	"singleQuote": true,
	"useTabs": false,
	"trailingComma": "es5"
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 Harry Fox

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
[![Npm Version][npm-version-image]][npm-version-url]
[![Downloads][downloads-image]][downloads-url]
[![License][license-image]][license-url]


# react-best-gradient-color-picker
- Customizable, easy to use color and gradient picker for React.js
- Simply pass in an rgba or css gradient string as value and an onChange handler 
- Variety of optional tools like eye dropper, advanced color settings, and color guide
- use the useColorPicker hook for complete control over of the picker
- You can completly customize the UI by hiding the included elements and using the hook to build your own
- You can also customize preset options by passing in an array of rgba colors (see custom presets below)

<br /> 

<img alt="" src="https://gradient-package-demo.web.app/gradientPickerImg.png" width="100%" />

<br />

<a id="item-one"></a>
## Install
```
npm install react-best-gradient-color-picker
```

```
yarn add react-best-gradient-color-picker
```

<a id="item-two"></a>
## Demo
See the picker in action [here](https://gradient-package-demo.web.app/)

<br />

**Table of Contents**
- [Basic Example](#item-three)
- [Props](#item-four)
- [API](#item-five)
- [useColorPicker Hook](#item-six)
- [Hook Basic Example](#item-seven)
- [Hook Functions](#item-eight)
- [Hook State](#item-nine)
- [More Hook Examples](#item-ten)
- [Styling](#item-fifteen)
- [Legacy Options](#item-eleven)
- [Roadmap](#item-twelve)
- [License](#item-thirteen)
- [Acknowledgments](#item-fourteen)

<br />

<a id="item-three"></a>
## Basic Example 
```js
import React from 'react'
import ColorPicker from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('rgba(255,255,255,1)');

  return <ColorPicker value={color} onChange={setColor} />
}
```

<br />

<a id="item-four"></a>
### Props

| Name                | Type         | Default                 | Description                                                               |
|---------------------|--------------| ----------------------- |---------------------------------------------------------------------------|
| value               | `string`     | 'rgba(175, 51, 242, 1)' | The starting color                                                        |
| width               | `int`        | 294                     | (optional) The width of the picker                                        |
| height              | `int`        | 294                     | (optional) The height of the picker                                       |
| hideInputs          | `boolean`    | `false`                 | (optional) hide the hex and rgba inputs                                   |
| hideOpacity         | `boolean`    | `false`                 | (optional) hide the opacity bar                                           |
| hideHue             | `boolean`    | `false`                 | (optional) hide the hue bar                                               |
| hideControls        | `boolean`    | `false`                 | (optional) hide the entire top row of various control btns                |
| hideColorTypeBtns   | `boolean`    | `false`                 | (optional) hide the solid/gradient buttons                                |
| hidePresets         | `boolean`    | `false`                 | (optional) hide the preset color options                                  |
| hideEyeDrop         | `boolean`    | `false`                 | (optional) hide (and disable the eye dropper tool                         |
| hideAdvancedSliders | `boolean`    | `false`                 | (optional) hide the additional sliders (saturation, luminence, brightness |
| hideColorGuide      | `boolean`    | `false`                 | (optional) hide the color guide, a tool that shows color pairings         |
| hideInputType       | `boolean`    | `false`                 | (optional) hide the input type selector, looking the type                 |
| hideGradientType    | `boolean`    | `false`                 | (optional) hide the linear/circular gradient type toggle (only relevant in gradient mode)|
| hideGradientAngle   | `boolean`    | `false`                 | (optional) hide the gradient angle input (only relevant in gradient mode with a linear gradient)|
| hideGradientStop    | `boolean`    | `false`                 | (optional) hide the gradient point stop input (only relevant in gradient mode)|
| hideGradientControls| `boolean`    | `false`                 | (optional) hide the all gradient controls (the bar that appears below top controls when in gradient mode)|
| hidePickerSquare    | `boolean`    | `false`                 | (optional) hide the main picker color swatch (the square that appears at the top)|
| showHexAlpha        | `boolean`    | `false`                 | (optional) add alpha (AA) channel to hex value which represents the opacity of the color|
| presets             | `array`      | ['rgba(0,0,0,1)', ...]  | (optional) pass in custom preset options ['rgba()', 'rgba()', ..]         |
| locales             | `object`     | { CONTROLS: { SOLID: 'Solid', GRADIENT: 'Gradient' }}  | (optional) pass in custom locales |
| disableDarkMode     | `boolean`    | false                    | (optional) disable automatic dark mode style adjustments                 |
| disableLightMode    | `boolean`    | false                    | (optional) force the component to only use the dark mode styles          |
| className           | `string`     | ''                       | (optional) a CSS class for the picker parent (see styling for more options)|
| idSuffix            | `string`     | ''                       | (optional) Adds a suffix to all the ids of the picker elements, useful when using multiple pickers on the same page|

<a id="item-five"></a>
### API

| Name             | Description                                                      |
| ---------------- | ---------------------------------------------------------------- |
| onChange         | A function to update color value                                 |

<br />

<a id="item-six"></a>
# useColorPicker 

- Take complete control of the picker
- Get current state
- Convert between color types

<a id="item-seven"></a>
## Basic Example 

- Initialize the hook by passing in the same color value and onChange handler

```js
import React from 'react'
import ColorPicker, { useColorPicker } from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');
  const { setSolid, setGradient } = useColorPicker(color, setColor);

  return(
    <div>
      <button onClick={setSolid}>Solid</button>
      <button onClick={setGradient}>Gradient</button>
      <ColorPicker value={color} onChange={setColor} />
    </div>
   )
}
```

<a id="item-eight"></a>
### Included Functions 

| Name             | Arguments        | Description                                                      |
| ---------------- | ---------------- | ---------------------------------------------------------------- |
| handleChange     | value (rgba string)| Most useful for setting color value of the selectedPoint without overwriting entire gradient string. Only pass this function a single color value, not a gradient |
| setLinear        |                  | Change the type of gradient to linear                            |
| setRadial        |                  | Change the type of gradient to radial                            |
| setDegrees       | degrees (num, 0 - 360)| Change the degrees of a linear gradient                     |
| setSolid         | (optional) new solid color (rgba string) | Change the pickers color mode from gradient to solid |
| setGradient      | (optional) new gradient (CSS gradient) | Change the pickers color mode from solid to gradient |
| setR             | value (num, 0 - 255) | Update the red value of the color                            |
| setG             | value (num, 0 - 255) | Update the green value of the color                          |
| setB             | value (num, 0 - 255) | Update the blue value of the color                           |
| setA             | value (num, 0 - 100) | Update the opacity (alpha) of a color                        |
| setHue           | value (num, 0 - 360) | Update the hue of a color                                    |
| setSaturation    | value (num, 0 - 100) | Update the saturation of a color                             |
| setLightness     | value (num, 0 - 100) | Update the lightness of a color                              |
| valueToHSL       |                  | Get the current value in HSL                                     |
| valueToHSV       |                  | Get the current value in HSV                                     |
| valueToHex       |                  | Get the current value in HEX                                     |
| valueToCmyk      |                  | Get the current value in CMYK                                    |
| setSelectedPoint | index of point (num) | Update which individual color of a gradient is in focus      |
| deletePoint      | index of point (num) | Delete one of the gradients colors                           |
| addPoint         | position of point (num, 0 - 100) | Add a new color to the gradient                  |
| setPointLeft     | value (num, 0 - 100) | Update the position (left) of the currently selected gradient color |
| getGradientObject|                      | get the gradients value parsed into a key/value object (see example below)|

<a id="item-nine"></a>
### Available State

| Name             | Description                                                      |
| ---------------- | ---------------------------------------------------------------- |
| selectedPoint    | returns index of which color point of a gradient is currently selected |
| isGradient       | returns which mode the picker is in, solid or gradient           |
| gradientType     | which gradient type is currently selected, linear or radial      |
| degrees          | current degrees of a radial gradient                             |
| currentLeft      | the position of the selected gradient color                      |
| rgbaArr          | get the current rgba values in an array                          |
| hslArr           | get the current hsl values in an array                           |

<a id="item-ten"></a>
## Various Customization Examples

### Custom Gradient Controls

```js
import React from 'react'
import ColorPicker, { useColorPicker } from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');
  const { gradientType, setLinear, setRadial, addPoint, deletePoint, degrees, setDegrees, setPointLeft, currentLeft, selectedPoint } = useColorPicker(color, setColor);

  return(
    <div>
      <button onClick={setLinear}>Linear</button>
      <button onClick={setRadial}>Radial</button>
      {gradientType === 'linear-gradient' && <input value={degrees} onChange={(e) => setDegrees(e.target.value)} />}
      <input value={currentLeft} onChange={(e) => setPointLeft(e.target.value)} />
      <button onClick={() => addPoint(50)}>Add Color</button>
      <button onClick={() => deletePoint(selectedPoint)}>Delete Color</button>
      <ColorPicker value={color} onChange={setColor} hideControls={true} />
    </div>
   )
}
```

### Custom RGBA Inputs

```js
import React from 'react'
import ColorPicker, { useColorPicker } from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');
  const { setR, setG, setB, setA, rgbaArr } = useColorPicker(color, setColor);

  return(
    <div>
      <input value={rgbaArr[0]} onChange={(e) => setR(e.target.value)} />
      <input value={rgbaArr[1]} onChange={(e) => setG(e.target.value)} />
      <input value={rgbaArr[2]} onChange={(e) => setB(e.target.value)} />
      <input value={rgbaArr[3]} onChange={(e) => setA(e.target.value)} />
      <ColorPicker value={color} onChange={setColor} hideInputs={true} />
    </div>
   )
}
```


### Conversions

```js
import React from 'react'
import ColorPicker, { useColorPicker } from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');
  const { valueToHSL, valueToHSV, valueToHex, valueToCmyk, rgbaArr, hslArr } = useColorPicker(color, setColor);

  const hslString = valueToHSL();
  const hsvString = valueToHSV();
  const hexString = valueToHex();
  const cmykString = valueToCmyk();
  const rgbaArray = rgbaArr;
  const hslArray = hslArr;

  return(
    <div>
      <ColorPicker value={color} onChange={setColor} />
    </div>
   )
}
```

### Custom Presets Example 

```js
import React from 'react'
import ColorPicker from 'react-best-gradient-color-picker'

const customPresets = [
  'rgba(34, 164, 65, 1)',
  'rgba(210, 18, 40, .5)',
  'rgba(90, 110, 232, 1)',
  'rgba(65, 89, 56, 1)',
  'rgba(98, 189, 243, 1)',
  'rgba(255, 210, 198, 1)',
  'rgba(94, 94, 94, 1)'
] //max 18 colors, you can pass in more but the array will be sliced to the first 18

function MyApp() {
  const [color, setColor] = useState('rgba(255,255,255,1');

  return <ColorPicker value={color} onChange={setColor} presets={customPresets} />
}
```

You may also want to provide the users recently used colors in lieu of preset options. This can be easily accomplished use the hook. 

```js
import React from 'react'
import ColorPicker, { useColorPicker } from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');
  const { previousColors } = useColorPicker(color, setColor);

  return(
    <div>
      <ColorPicker value={color} onChange={setColor} presets={previousColors} />
    </div>
   )
}
```

### Custom Locales Example 
You can pass custom locales via `locales` prop.

```js
import React from 'react'
import ColorPicker, { useColorPicker } from 'react-best-gradient-color-picker'

function MyApp() {
  const customLocales = {
    CONTROLS: {
      SOLID: 'Obične',
      GRADIENT: 'Gradijent',
    },
  }
  return (
    <div>
      <ColorPicker locales={customLocales} />
    </div>
  )
}
```

### Getting Value in Object Form
The picker returns the new value as a css gradient string but you may need it parsed as an object. This can easily be accomplised by using the getGradientObject function returned by the useColorPicker hook like so:

```js
import React from 'react'
import ColorPicker, { useColorPicker } from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');
  const { getGradientObject } = useColorPicker(color, setColor);
  const gradientObject = getGradientObject();
  
  // example value
  // {
  //   "isGradient": true,
  //   "gradientType": "linear-gradient",
  //   "degrees": "40deg",
  //   "colors": [
  //       {
  //           "value": "rgba(27,107,235,1)",
  //           "left": 0
  //       },
  //       {
  //           "value": "rgba(25,245,157,1)",
  //           "left": 100
  //       }
  //     ]
  // }

  return(
    <div>
      <ColorPicker value={color} onChange={setColor} presets={previousColors} />
    </div>
   )
}
```

### Only Gradients Example
If you would like to not allow selection of solid colors disable the color type buttons and feed in the initial value as a gradient like below:

NOTE: the same can be done in reverse to only allow selection of solid colors

```js
import React from 'react'
import ColorPicker, { useColorPicker } from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');  

  return(
    <div>
      <ColorPicker
        value={color}
        onChange={setColor}
        hideColorTypeBtns={true}
      />
    </div>
   )
}
```

<br />

<a id="item-fifteen"></a>
## Styling
Most of the pickers components have inline styles applied to them, these essential function as classNames. In order to update, identify the key being applied to the desired component and add any styles as a nested object to the `style` object which can be passed into the picker. See below list of style keys and example.

<br />

| Key              | Description                                                      |
| -------------------------------- | ------------------------------------------------ |
| body                             | ComingSoon |
| rbgcpControlBtnWrapper           | ComingSoon |
| rbgcpControlBtn                  | ComingSoon |
| rbgcpControlIconBtn              | ComingSoon |
| rbgcpControlIcon                 | ComingSoon |
| rbgcpColorModelDropdown          | ComingSoon |
| rbgcpEyedropperCover             | ComingSoon |
| rbgcpControlInputWrap            | ComingSoon |
| rbgcpControlInput                | ComingSoon |
| rbgcpInputLabel                  | ComingSoon |
| rbgcpInput                       | ComingSoon |
| rbgcpHandle                      | ComingSoon |
| rbgcpCanvasWrapper               | ComingSoon |
| rbgcpOpacityOverlay              | ComingSoon |
| rbgcpGradientHandleWrap          | ComingSoon |
| rbgcpGradientHandle              | ComingSoon |
| rbgcpControlIcon2                | ComingSoon |
| rbgcpControlBtnSelected          | ComingSoon |
| rbgcpComparibleLabel             | ComingSoon |
| rbgcpColorModelDropdownBtn       | ComingSoon |
| rbgcpStopInputWrap               | ComingSoon |
| rbgcpStopInput                   | ComingSoon |
| rbgcpDegreeInputWrap             | ComingSoon |
| rbgcpDegreeInput                 | ComingSoon |
| rbgcpDegreeIcon                  | ComingSoon |
| rbgcpEyedropperBtn               | ComingSoon |
| rbgcpHexInput                    | ComingSoon |
| rbgcpInputsWrap                  | ComingSoon |

<br />

<a id="item-eleven"></a>
## LEGACY V1 - Manual Control - Customizing UI

This still works, although most functions are available through the useColorPicker hook, if there is something you need that is not available you could use the below methods to create your desired functionality.

The state of the picker is determined by parsing the value string. You can update props like colorType (solid/gradient), gradientType (linear/radial), gradientDegrees, hex, rgba, opacity and hue simply by updating the value you are passing into the component. Let's say you want to change the colorType from gradient to solid: 

```js
import React from 'react'
import ColorPicker from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');
  
  const setSolid = () => {
    setColor('rgba(255,255,255,1)') //color could be any rgba value
  }

  return(
    <div>
      <button onClick={setSolid}>Solid</button>
      <ColorPicker value={color} onChange={setColor} />
    </div>
   )
}
```
The same can be done in inverse to change colorType from solid to gradient:

```js  
  const setGradient = () => {
    setColor('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)')
  }
```

Example toggling gradientType 

```js  
  const setLinear = () => {
    setColor('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)')
  }
  
  const setRadial = () => {
    setColor('radial-gradient(circle, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)')
  }
```

Custom linear-gradient degrees input 

```js
import React from 'react'
import ColorPicker from 'react-best-gradient-color-picker'

function MyApp() {
  const [color, setColor] = useState('linear-gradient(90deg, rgba(96,93,93,1) 0%, rgba(255,255,255,1) 100%)');
  const degrees = parseInt(value?.split(',')[0]?.split('(')[1])
  
  const handleDegrees = (val) => {
    let num = parseInt(val)
    let nans = isNaN(num) ? 0 : num
    let min = Math.max(nans, 0)
    let max = Math.min(min, 360)
    const remaining = value.split(/,(.+)/)[1]
    setColor(`linear-gradient(${max}deg, ${remaining}`)
  }

  return(
    <div>
      <input value={degrees} onChange={(e) => handleDegrees(e.target.value)} />
      <ColorPicker value={color} onChange={setColor} />
    </div>
   )
}
```

<a id="item-twelve"></a>
## Roadmap
1. enhanced mobile support
2. cross browser eye dropper issue
3. enhanced gradient parsing to allow additional gradient types

<a id="item-thirteen"></a>
## License

Code released under the [MIT](https://github.com/hxf31891/react-gradient-color-picker/blob/main/LICENSE) license.

[build-image]: https://img.shields.io/github/checks-status/hxf31891/react-gradient-color-picker/main?color=%23498af2
[license-image]: https://img.shields.io/npm/l/react-best-gradient-color-picker.svg?color=%23498af2
[license-url]: LICENSE
[downloads-image]: http://img.shields.io/npm/dm/react-best-gradient-color-picker.svg?color=%23498af2
[downloads-url]: http://npm-stat.com/charts.html?package=react-best-gradient-color-picker
[npm-version-image]: https://img.shields.io/npm/v/react-best-gradient-color-picker.svg?color=%23498af2
[npm-version-url]: https://www.npmjs.com/package/react-best-gradient-color-picker

<a id="item-fourteen"></a>
## Acknowledgments

Very special thank you to [Rafael Carício](https://github.com/rafaelcaricio) for his amazing work parsing gradient strings.


================================================
FILE: biome.json
================================================
{
  "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
  "files": {
    "ignore": [
      ".tsimp",
      ".yarn",
      "coverage",
      "dist",
      ".pnp.cjs",
      ".pnp.loader.mjs"
    ]
  },
  "formatter": {
    "lineWidth": 100,
    "indentStyle": "space"
  },
  "linter": {
    "rules": {
      "complexity": {
        "noUselessSwitchCase": "off"
      },
      "suspicious": {
        "noConsoleLog": "warn"
      }
    }
  },
  "css": {
    "formatter": {
      "enabled": true,
      "indentStyle": "space",
      "indentWidth": 2,
      "lineWidth": 100,
      "quoteStyle": "single"
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single"
    }
  },
  "overrides": [
    {
      "include": ["**/package.json"],
      "formatter": {
        "lineWidth": 1
      }
    },
    {
      "include": ["**/vite.config.ts"],
      "linter": {
        "rules": {
          "suspicious": {
            "noConsoleLog": "off"
          }
        }
      }
    }
  ]
}


================================================
FILE: package.json
================================================
{
  "name": "react-best-gradient-color-picker",
  "version": "3.0.14",
  "description": "An easy to use color/gradient picker for React.js",
  "type": "module",
  "sideEffects": [
    "*.css"
  ],
  "main": "./dist/cjs/index.js",
  "module": "./dist/esm/index.js",
  "source": "./src/index.ts",
  "types": "./dist/cjs/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js"
    }
  },
  "scripts": {
    "build": "yarn build-js",
    "build-js": "yarn build-js-esm && yarn build-js-cjs && yarn build-js-cjs-package",
    "build-js-esm": "tsc --project tsconfig.build.json --outDir dist/esm",
    "build-js-cjs": "tsc --project tsconfig.build.json --outDir dist/cjs --module commonjs --moduleResolution node --verbatimModuleSyntax false",
    "build-js-cjs-package": "echo '{\n  \"type\": \"commonjs\"\n}' > dist/cjs/package.json",
    "clean": "rimraf dist",
    "format": "biome format",
    "lint": "biome lint",
    "prepack": "yarn clean && yarn build",
    "test": "yarn lint && yarn tsc && yarn format && yarn unit",
    "tsc": "tsc",
    "unit": "vitest",
    "watch": "yarn build-js-esm --watch & yarn build-js-cjs --watch & node --eval"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/hxf31891/react-gradient-color-picker.git"
  },
  "keywords": [
    "gradient",
    "react",
    "color",
    "picker",
    "react.js",
    "tool",
    "editor"
  ],
  "author": {
    "name": "Harry Fox",
    "email": "hxfox1@gmail.com"
  },
  "dependencies": {
    "html2canvas": "^1.4.1",
    "lodash.throttle": "^4.1.1",
    "tinycolor2": "1.4.2"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.0.0",
    "@testing-library/react": "^14.0.0",
    "@types/lodash.throttle": "*",
    "@types/node": "*",
    "@types/react": "*",
    "@types/reactcss": "^1.2.11",
    "@types/tinycolor2": "^1.4.6",
    "cpy-cli": "^3.1.1",
    "happy-dom": "^12.6.0",
    "nodemon": "^3.0.0",
    "prettier": "^3.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "rimraf": "^3.0.0",
    "typescript": "^5.3.2",
    "vitest": "^1.0.2"
  },
  "peerDependencies": {
    "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
    "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
    "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
  },
  "peerDependenciesMeta": {
    "@types/react": {
      "optional": true
    }
  },
  "files": [
    "dist",
    "src"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/hxf31891/react-gradient-color-picker/issues"
  },
  "homepage": "https://gradient-package-demo.web.app/",
  "publishConfig": {
    "@hxf31891:registry": "https://npm.pkg.github.com"
  }
}

================================================
FILE: src/components/AdvancedControls.tsx
================================================
import React, { useState, useRef, useEffect } from 'react'
import { Styles, Config } from '../shared/types.js'
import { getHandleValue } from '../utils/utils.js'
import { usePicker } from '../context.js'
import {
  usePaintSat,
  usePaintLight,
  usePaintBright,
} from '../hooks/usePaintHue.js'
import tinycolor from 'tinycolor2'

const AdvBar = ({
  value,
  reffy,
  label,
  config,
  callback,
  squareWidth,
  openAdvanced,
  defaultStyles,
  pickerIdSuffix,
}: {
  reffy: any
  value: number
  label: string
  config: Config
  squareWidth: number
  openAdvanced: boolean
  defaultStyles: Styles
  pickerIdSuffix: string
  callback: (arg0: number) => void
}) => {
  const { barSize } = config
  const [dragging, setDragging] = useState<boolean>(false)
  const [handleTop, setHandleTop] = useState<number>(2)
  const left = value * (squareWidth - 18)

  useEffect(() => {
    setHandleTop(reffy?.current?.offsetTop - 2)
  }, [openAdvanced, reffy])

  const stopDragging = () => {
    setDragging(false)
  }

  const handleMove = (e: any) => {
    if (dragging) {
      callback(getHandleValue(e, barSize))
    }
  }

  const handleClick = (e: any) => {
    if (!dragging) {
      callback(getHandleValue(e, barSize))
    }
  }

  const handleDown = () => {
    setDragging(true)
  }

  useEffect(() => {
    const handleUp = () => {
      stopDragging()
    }

    window.addEventListener('mouseup', handleUp)

    return () => {
      window.removeEventListener('mouseup', handleUp)
    }
  }, [])

  return (
    <div style={{ width: '100%', padding: '3px 0px 3px 0px' }}>
      <div
        onMouseMove={(e) => handleMove(e)}
        // className="rbgcp-advanced-bar-wrap"
        style={{ cursor: 'resize', position: 'relative' }}
        id={`rbgcp-advanced-bar-${label}-wrapper${pickerIdSuffix}`}
      >
        <div
          style={{ left, top: handleTop, ...defaultStyles.rbgcpHandle }}
          id={`rbgcp-advanced-bar-${label}-handle${pickerIdSuffix}`}
          // className="rbgcp-advanced-bar-handle"
          onMouseDown={handleDown}
          role="button"
          tabIndex={0}
        />
        <div
          style={{
            textAlign: 'center',
            color: '#fff',
            fontSize: 12,
            fontWeight: 500,
            lineHeight: 1,
            position: 'absolute',
            left: '50%',
            transform: 'translate(-50%, 0%)',
            top: handleTop + 2,
            zIndex: 10,
            textShadow: '1px 1px 1px rgba(0,0,0,.6)',
          }}
          id={`rbgcp-advanced-bar-${label}-label${pickerIdSuffix}`}
          // className="rbgcp-advanced-bar-label"
          onMouseMove={(e) => handleMove(e)}
          onClick={(e) => handleClick(e)}
          tabIndex={0}
          role="button"
          onKeyDown={() => {
            return
          }}
        >
          {label}
        </div>
        <canvas
          ref={reffy}
          height="14px"
          width={`${squareWidth}px`}
          onClick={(e) => handleClick(e)}
          // className="rbgcp-advanced-bar-canvas"
          style={{ position: 'relative', borderRadius: 14 }}
          id={`rbgcp-advanced-bar-${label}-canvas${pickerIdSuffix}`}
        />
      </div>
    </div>
  )
}

const AdvancedControls = ({ openAdvanced }: { openAdvanced: boolean }) => {
  const { config, tinyColor, handleChange, squareWidth, hc, defaultStyles, pickerIdSuffix } = usePicker()
  const { s, l } = tinyColor.toHsl()

  const satRef = useRef(null)
  const lightRef = useRef(null)
  const brightRef = useRef(null)
  usePaintSat(satRef, hc?.h, l * 100, squareWidth)
  usePaintLight(lightRef, hc?.h, s * 100, squareWidth)
  usePaintBright(brightRef, hc?.h, s * 100, squareWidth)

  const satDesat = (value: number) => {
    const { r, g, b } = tinycolor({ h: hc?.h, s: value / 100, l }).toRgb()
    handleChange(`rgba(${r},${g},${b},${hc?.a})`)
  }

  const setLight = (value: number) => {
    const { r, g, b } = tinycolor({ h: hc?.h, s, l: value / 100 }).toRgb()
    handleChange(`rgba(${r},${g},${b},${hc?.a})`)
  }

  const setBright = (value: number) => {
    const { r, g, b } = tinycolor({
      h: hc?.h,
      s: hc?.s * 100,
      v: value,
    }).toRgb()
    handleChange(`rgba(${r},${g},${b},${hc?.a})`)
  }

  return (
    <div
      style={{
        width: '100%',
        height: openAdvanced ? 98 : 0,
        transition: 'all 120ms linear',
      }}
      id={`rbgcp-advanced-controls-wrapper${pickerIdSuffix}`}
      // className="rbgcp-advanced-controls-wrap"
    >
      <div
        style={{
          paddingTop: 11,
          display: openAdvanced ? 'flex' : 'none',
          flexDirection: 'column',
          justifyContent: 'space-between',
          height: openAdvanced ? 98 : 0,
          overflow: 'hidden',
          transition: 'height 100ms linear',
        }}
        id={`rbgcp-advanced-controls-inner${pickerIdSuffix}`}
        // className="rbgcp-advanced-controls-inner"
      >
        <AdvBar
          value={s}
          reffy={satRef}
          config={config}
          label="Saturation"
          callback={satDesat}
          squareWidth={squareWidth}
          openAdvanced={openAdvanced}
          defaultStyles={defaultStyles}
          pickerIdSuffix={pickerIdSuffix}
        />
        <AdvBar
          value={l}
          config={config}
          reffy={lightRef}
          label="Lightness"
          callback={setLight}
          squareWidth={squareWidth}
          openAdvanced={openAdvanced}
          defaultStyles={defaultStyles}
          pickerIdSuffix={pickerIdSuffix}
        />
        <AdvBar
          value={hc?.v}
          config={config}
          reffy={brightRef}
          label="Brightness"
          callback={setBright}
          squareWidth={squareWidth}
          openAdvanced={openAdvanced}
          defaultStyles={defaultStyles}
          pickerIdSuffix={pickerIdSuffix}
        />
      </div>
    </div>
  )
}

export default AdvancedControls


================================================
FILE: src/components/ComparibleColors.tsx
================================================
import React from 'react'
import { usePicker } from '../context.js'

const ComparibleColors = ({
  openComparibles,
}: {
  openComparibles: boolean
}) => {
  const { tinyColor, handleChange, defaultStyles, pickerIdSuffix } = usePicker()

  const analogous = tinyColor.analogous()
  const monochromatic = tinyColor.monochromatic()
  const triad = tinyColor.triad()
  const tetrad = tinyColor.tetrad()

  const handleClick = (tiny: any) => {
    const { r, g, b, a } = tiny.toRgb()
    handleChange(`rgba(${r},${g},${b},${a})`)
  }

  return (
    <div
      style={{
        width: '100%',
        transition: 'all 120ms linear',
        height: openComparibles ? 216 : 0,
      }}
      id={`rbgcp-comparible-colors-wrapper${pickerIdSuffix}`}
      // className="rbgcp-comparible-colors-wrap"
    >
      <div
        style={{
          paddingTop: 11,
          display: openComparibles ? '' : 'none',
          position: 'relative',
        }}
        id={`rbgcp-comparible-colors-inner${pickerIdSuffix}`}
        // className="rbgcp-comparible-colors-inner"
      >
        <div
          style={{
            textAlign: 'center',
            fontSize: 13,
            fontWeight: 600,
            position: 'absolute',
            top: 6.5,
            left: 2,
            ...defaultStyles.rbgcpComparibleLabel,
          }}
          id={`rbgcp-comparible-color-guide-label${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-label"
        >
          Color Guide
        </div>
        <div
          style={{
            textAlign: 'center',
            fontSize: 12,
            fontWeight: 500,
            marginTop: 3,
            ...defaultStyles.rbgcpComparibleLabel,
          }}
          id={`rbgcp-comparible-analogous-colors-label${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-label"
        >
          Analogous
        </div>
        <div
          style={{ borderRadius: 5, overflow: 'hidden', display: 'flex' }}
          id={`rbgcp-comparible-analogous-colors${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-colors"
        >
          {analogous?.map((c: any, key: number) => (
            <div
              key={key}
              id={`rbgcp-comparible-analogous-color-${key}${pickerIdSuffix}`}
              style={{ width: '20%', height: 30, background: c.toHexString() }}
              onClick={() => handleClick(c)}
            />
          ))}
        </div>
        <div
          style={{
            textAlign: 'center',
            fontSize: 12,
            fontWeight: 500,
            marginTop: 3,
            ...defaultStyles.rbgcpComparibleLabel,
          }}
          id={`rbgcp-comparible-monochromatic-colors-label${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-label"
        >
          Monochromatic
        </div>
        <div
          style={{
            borderRadius: 5,
            overflow: 'hidden',
            display: 'flex',
            justifyContent: 'flex-end',
          }}
          id={`rbgcp-comparible-monochromatic-colors${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-colors"
        >
          {monochromatic?.map((c: any, key: number) => (
            <div
              key={key}
              id={`rbgcp-comparible-monochromatic-color-${key}${pickerIdSuffix}`}
              style={{ width: '20%', height: 30, background: c.toHexString() }}
              onClick={() => handleClick(c)}
            />
          ))}
        </div>
        <div
          style={{
            textAlign: 'center',
            fontSize: 12,
            fontWeight: 500,
            marginTop: 3,
            ...defaultStyles.rbgcpComparibleLabel,
          }}
          id={`rbgcp-comparible-triad-colors-label${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-label"
        >
          Triad
        </div>
        <div
          style={{
            borderRadius: 5,
            overflow: 'hidden',
            display: 'flex',
            justifyContent: 'flex-end',
          }}
          id={`rbgcp-comparible-triad-colors${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-colors"
        >
          {triad?.map((c: any, key: number) => (
            <div
              key={key}
              id={`rbgcp-comparible-triad-color-${key}${pickerIdSuffix}`}
              style={{
                width: 'calc(100% / 3)',
                height: 28,
                background: c.toHexString(),
              }}
              onClick={() => handleClick(c)}
            />
          ))}
        </div>
        <div
          style={{
            textAlign: 'center',
            fontSize: 12,
            fontWeight: 500,
            marginTop: 3,
            ...defaultStyles.rbgcpComparibleLabel,
          }}
          id={`rbgcp-comparible-tetrad-colors-label${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-label"
        >
          Tetrad
        </div>
        <div
          style={{
            borderRadius: 5,
            overflow: 'hidden',
            display: 'flex',
            justifyContent: 'flex-end',
          }}
          id={`rbgcp-comparible-tetrad-colors${pickerIdSuffix}`}
          // className="rbgcp-comparible-colors-colors"
        >
          {tetrad?.map((c: any, key: number) => (
            <div
              key={key}
              id={`rbgcp-comparible-tetrad-color-${key}${pickerIdSuffix}`}
              style={{ width: '25%', height: 28, background: c.toHexString() }}
              onClick={() => handleClick(c)}
            />
          ))}
        </div>
      </div>
    </div>
  )
}

export default ComparibleColors


================================================
FILE: src/components/Controls.tsx
================================================
/* eslint-disable react/jsx-no-leaked-render */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useState } from 'react'
import { SlidersIcon, InputsIcon, PaletteIcon } from './icon.js'
import { usePicker } from '../context.js'
import EyeDropper from './EyeDropper.js'
import AdvancedControls from './AdvancedControls.js'
import ComparibleColors from './ComparibleColors.js'
import GradientControls from './GradientControls.js'
import { LocalesProps } from '../shared/types.js'
import { colorTypeBtnStyles, controlBtnStyles, modalBtnStyles } from '../styles/styles.js'

const ColorTypeBtns = ({
  hideColorTypeBtns,
  setGradient,
  isGradient,
  setSolid,
  locales,
}: {
  hideColorTypeBtns?: boolean
  isGradient?: boolean
  setSolid: () => void
  setGradient: () => void
  locales?: LocalesProps
}) => {
  const { defaultStyles, pickerIdSuffix } = usePicker()

  if (hideColorTypeBtns) {
    return <div style={{ width: 1 }} />
  } else {
    return (
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          ...defaultStyles.rbgcpControlBtnWrapper,
        }}
        id={`rbgcp-color-type-btns${pickerIdSuffix}`}
      >
        <div
          onClick={setSolid}
          id={`rbgcp-solid-btn${pickerIdSuffix}`}
          style={colorTypeBtnStyles(!isGradient, defaultStyles)}
          // className="rbgcp-control-btn rbgcp-solid-btn"
        >
          {locales?.CONTROLS?.SOLID}
        </div>
        <div
          onClick={setGradient}
          id={`rbgcp-gradient-btn${pickerIdSuffix}`}
          style={colorTypeBtnStyles(isGradient ?? false, defaultStyles)}
          // className="rbgcp-control-btn rbgcp-gradient-btn"
        >
          {locales?.CONTROLS?.GRADIENT}
        </div>
      </div>
    )
  }
}

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 (
    <div
      // className="rbgcp-color-model-dropdown"
      style={{
        visibility: openInputType ? 'visible' : 'hidden',
        zIndex: openInputType ? '' : -100,
        opacity: openInputType ? 1 : 0,
        transition: `${oTrans}, ${vTrans}, ${zTrans}`,
        ...defaultStyles.rbgcpColorModelDropdown,
      }}
      id={`rbgcp-color-model-dropdown${pickerIdSuffix}`}
    >
      <div
        id={`rbgcp-color-model-rgb-btn${pickerIdSuffix}`}
        onClick={(e) => handleInputType(e, 'rgb')}
        style={modalBtnStyles(inputType === 'rgb', defaultStyles)}
      >
        RGB
      </div>
      <div
        id={`rbgcp-color-model-hsl-btn${pickerIdSuffix}`}
        onClick={(e) => handleInputType(e, 'hsl')}
        style={modalBtnStyles(inputType === 'hsl', defaultStyles)}
      >
        HSL
      </div>
      <div
        id={`rbgcp-color-model-hsv-btn${pickerIdSuffix}`}
        onClick={(e) => handleInputType(e, 'hsv')}
        style={modalBtnStyles(inputType === 'hsv', defaultStyles)}
      >
        HSV
      </div>
      <div
        id={`rbgcp-color-model-cmyk-btn${pickerIdSuffix}`}
        onClick={(e) => handleInputType(e, 'cmyk')}
        style={modalBtnStyles(inputType === 'cmyk', defaultStyles)}
      >
        CMYK
      </div>
    </div>
  )
}

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 (
        <GradientControls
          hideGradientType={hideGradientType}
          hideGradientAngle={hideGradientAngle}
          hideGradientStop={hideGradientStop}
        />
      )
    } else {
      return null
    }
  } else {
    return (
      <div style={{ paddingBottom: 4 }}>
        <div
          style={{
            width: '100%',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'space-between',
          }}
          id={`rbgcp-controls-wrapper${pickerIdSuffix}`}
          // className="rbgcp-controls-wrapper"
        >
          <ColorTypeBtns
            hideColorTypeBtns={hideColorTypeBtns}
            setGradient={setGradient}
            isGradient={isGradient}
            setSolid={setSolid}
            locales={locales}
          />

          {!allRightControlsHidden && (
            <div
              style={{
                display: noTools ? 'none' : '',
                ...defaultStyles.rbgcpControlBtnWrapper,
              }}
              id={`rbgcp-control-rightside-wrapper${pickerIdSuffix}`}
              // className="rbgcp-control-btn-wrapper"
            >
              {!hideEyeDrop && <EyeDropper onSelect={handleChange} />}
              {!hideAdvancedSliders && (
                <div
                  id={`rbgcp-advanced-btn${pickerIdSuffix}`}
                  onClick={() => setOpenAdvanced(!openAdvanced)}
                  // className="rbgcp-control-btn rbgcp-advanced-btn"
                  style={controlBtnStyles(openAdvanced, defaultStyles)}
                >
                  <SlidersIcon color={openAdvanced ? '#568CF5' : ''} />
                </div>
              )}
              {!hideColorGuide && (
                <div
                  style={controlBtnStyles(openComparibles, defaultStyles)}
                  onClick={() => setOpenComparibles(!openComparibles)}
                  // className="rbgcp-control-btn rbgcp-comparibles-btn"
                  id={`rbgcp-comparibles-btn${pickerIdSuffix}`}
                >
                  <PaletteIcon color={openComparibles ? '#568CF5' : ''} />
                </div>
              )}
              {!hideInputType && (
                <div
                  id={`rbgcp-color-model-btn${pickerIdSuffix}`}
                  onClick={() => setOpenInputType(!openInputType)}
                  // className="rbgcp-control-btn rbgcp-color-model-btn"
                  style={controlBtnStyles(openInputType, defaultStyles)}
                >
                  <InputsIcon color={openInputType ? '#568CF5' : ''} />
                  <InputTypeDropdown
                    openInputType={openInputType}
                    setOpenInputType={setOpenInputType}
                  />
                </div>
              )}
            </div>
          )}
        </div>
        {!hideAdvancedSliders && (
          <AdvancedControls openAdvanced={openAdvanced} />
        )}
        {!hideColorGuide && (
          <ComparibleColors openComparibles={openComparibles} />
        )}
        {isGradient && !hideGradientControls && (
          <GradientControls
            hideGradientType={hideGradientType}
            hideGradientAngle={hideGradientAngle}
            hideGradientStop={hideGradientStop}
          />
        )}
      </div>
    )
  }
}

export default Controls;


================================================
FILE: src/components/EyeDropper.tsx
================================================
/* eslint-disable react/jsx-no-leaked-render */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useState } from 'react'
import Portal from './Portal.js'
import html2canvas from 'html2canvas'
import { controlBtnStyles } from '../styles/styles.js'
import tc from 'tinycolor2'
import { usePicker } from '../context.js'

const DropperIcon = ({ color }: { color: string }) => {
  const { defaultStyles } = usePicker()
  const col = color ?? ''
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 16 }}
    >
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          fill: 'none',
          strokeWidth: '1.4px',
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        d="M15.6,7h0L7.78,14.86c-.37.37-1.61.38-2,.75s-.5,1.53-.76,2a3.53,3.53,0,0,1-.52.52,1.6,1.6,0,0,1-2.27-.06l-.32-.32a1.61,1.61,0,0,1-.06-2.27A3.25,3.25,0,0,1,2.4,15c.47-.26,1.65-.35,2-.73s.34-1.64.71-2c1.68-1.73,5.61-5.65,7.91-7.93h0l1.14,1.38L15.6,7Z"
      />
      <polygon
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          strokeWidth: '1.4px',
          ...defaultStyles.rbgcpControlIcon2,
          ...(col && { stroke: col, fill: col }),
        }}
        points="15.7 8.87 11.13 4.29 12.69 2.73 17.25 7.31 15.7 8.87"
      />
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          strokeWidth: '1.4px',
          ...defaultStyles.rbgcpControlIcon2,
          ...(col && { stroke: col, fill: col }),
        }}
        d="M18.18,3.71,16.36,5.53a1.33,1.33,0,0,1-1.88,0h0a1.34,1.34,0,0,1,0-1.89l1.81-1.82a1.34,1.34,0,0,1,1.89,0h0A1.34,1.34,0,0,1,18.18,3.71Z"
      />
    </svg>
  )
}

const Dropper = ({ onSelect }: { onSelect: (arg0: string) => void }) => {
  const { defaultStyles } = usePicker()
  const [pickerCanvas, setPickerCanvas] =
    useState<CanvasRenderingContext2D | null>(null)
  const [coverUp, setCoverUp] = useState(false)
  const [isPicking, setIsPicking] = useState(false)

  const takePick = () => {
    const root = document.getElementById('root')
    setCoverUp(true)

    // @ts-expect-error some error with this imported packages types
    html2canvas(root).then((canvas: any) => {
      const blankCanvas = document.createElement('canvas')
      const ctx = blankCanvas.getContext('2d', { willReadFrequently: true })

      if (root && ctx) {
        blankCanvas.width = root.offsetWidth * 2
        blankCanvas.height = root.offsetHeight * 2
        ctx.drawImage(canvas, 0, 0)
      }

      setPickerCanvas(ctx)
    })
  }

  const getColorLegacy = (e: any) => {
    e.stopPropagation()
    if (pickerCanvas) {
      const { pageX, pageY } = e
      const x1 = pageX * 2
      const y1 = pageY * 2
      const rgb = pickerCanvas.getImageData(x1, y1, 1, 1).data
      onSelect(`rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 1)`)
    }
    setIsPicking(false)
    setCoverUp(false)
  }

  const getEyeDrop = () => {
    setIsPicking(true)
    // @ts-expect-error - ts does not evaluate for window.EyeDropper
    if (!window.EyeDropper) {
      takePick()
    } else {
      // @ts-expect-error - ts does not evaluate for window.EyeDropper
      const eyeDropper = new window.EyeDropper()
      const abortController = new window.AbortController()

      eyeDropper
        .open({ signal: abortController.signal })
        .then((result: any) => {
          const tinyHex = tc(result.sRGBHex)
          const { r, g, b } = tinyHex.toRgb()
          onSelect(`rgba(${r}, ${g}, ${b}, 1)`)
          setIsPicking(false)
        })
        .catch((e: any) => {
          console.log(e)
          setIsPicking(false)
        })
    }
  }

  return (
    <div>
      <div
        onClick={getEyeDrop}
        id="rbgcp-eyedropper-btn"
        style={{
          ...defaultStyles.rbgcpEyedropperBtn,
          ...controlBtnStyles(coverUp, defaultStyles),
        }}
      >
        <DropperIcon color={isPicking ? 'rgb(86, 140, 245)' : ''} />
      </div>

      {coverUp && (
        <Portal>
          <div
            onClick={(e) => getColorLegacy(e)}
            style={defaultStyles.rbgcpEyedropperCover}
          />
        </Portal>
      )}
    </div>
  )
}

export default Dropper


================================================
FILE: src/components/GradientBar.tsx
================================================
/* eslint-disable react/no-array-index-key */
/* eslint-disable react/jsx-no-leaked-render */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useState, useEffect } from 'react'
import { getHandleValue } from '../utils/utils.js'
import { usePicker } from '../context.js'
import { low, high } from '../utils/formatters.js'
import { GradientProps } from '../shared/types.js'

export const Handle = ({
  left,
  i,
  setDragging,
}: {
  left?: number
  i: number
  setDragging: (arg0: boolean) => void
}) => {
  const {
    colors,
    squareWidth,
    selectedColor,
    defaultStyles,
    pickerIdSuffix,
    createGradientStr,
  } = usePicker()
  const isSelected = selectedColor === i
  const leftMultiplyer = (squareWidth - 18) / 100

  const setSelectedColor = (index: number) => {
    const newGradStr = colors?.map((cc: GradientProps, i: number) => ({
      ...cc,
      value: i === index ? high(cc) : low(cc),
    }))
    createGradientStr(newGradStr)
  }

  const handleDown = (e: any) => {
    e.stopPropagation()
    setSelectedColor(i)
    setDragging(true)
  }

  // const handleFocus = () => {
  //   setInFocus('gpoint')
  //   setSelectedColor(i)
  // }

  // const handleBlur = () => {
  //   setInFocus(null)
  // }

  return (
    <div
      // tabIndex={0}
      // onBlur={handleBlur}
      // onFocus={handleFocus}
      onMouseDown={(e) => handleDown(e)}
      id={`rbgcp-gradient-handle-${i}${pickerIdSuffix}`}
      // className="rbgcp-gradient-handle-wrap"
      style={{
        ...defaultStyles.rbgcpGradientHandleWrap,
        left: (left ?? 0) * leftMultiplyer,
      }}
    >
      <div
        // className="rbgcp-gradient-handle"
        style={{
          ...defaultStyles.rbgcpGradientHandle,
          ...(isSelected
            ? {
                boxShadow: '0px 0px 5px 1px rgba(86, 140, 245,.95)',
                border: '2px solid white',
              }
            : {}),
        }}
        id={`rbgcp-gradient-handle-${i}-dot${pickerIdSuffix}`}
      >
        {isSelected && (
          <div
            style={{
              width: 5,
              height: 5,
              borderRadius: '50%',
              background: 'white',
            }}
            id={`rbgcp-gradient-handle-${i}-selected-dot${pickerIdSuffix}`}
          />
        )}
      </div>
    </div>
  )
}

const GradientBar = () => {
  const {
    value,
    colors,
    config,
    squareWidth,
    currentColor,
    handleGradient,
    pickerIdSuffix,
    createGradientStr,
  } = usePicker()
  const { barSize } = config
  const [dragging, setDragging] = useState(false)
  // const [inFocus, setInFocus] = useState<string | null>(null)

  function force90degLinear(color: string) {
    return color.replace(
      /(radial|linear)-gradient\([^,]+,/,
      'linear-gradient(90deg,'
    )
  }

  const addPoint = (e: any) => {
    const left = getHandleValue(e, barSize)
    const newColors = [
      ...colors.map((c: any) => ({ ...c, value: low(c) })),
      { value: currentColor, left: left },
    ]?.sort((a, b) => a.left - b.left)
    createGradientStr(newColors)
  }

  // useEffect(() => {
  //   const selectedEl = window?.document?.getElementById(
  //     `gradient-handle-${selectedColor}`
  //   )
  //   if (selectedEl) selectedEl.focus()
  // }, [selectedColor])

  const stopDragging = () => {
    setDragging(false)
  }

  const handleDown = (e: any) => {
    if (dragging) return;
    addPoint(e)
    setDragging(true)
  }

  const handleMove = (e: any) => {
    if (dragging) handleGradient(currentColor, getHandleValue(e, barSize))
  }

  // const handleKeyboard = (e: any) => {
  //   if (isGradient) {
  //     if (e.keyCode === 8) {
  //       if (inFocus === 'gpoint') {
  //         deletePoint()
  //       }
  //     }
  //   }
  // }

  const handleUp = () => {
    stopDragging()
  }

  useEffect(() => {
    window.addEventListener('mouseup', handleUp)
    // window?.addEventListener('keydown', handleKeyboard)

    return () => {
      window.removeEventListener('mouseup', handleUp)
      // window?.removeEventListener('keydown', handleKeyboard)
    }
  })

  return (
    <div
      style={{
        width: '100%',
        marginTop: 17,
        marginBottom: 4,
        position: 'relative',
      }}
      id={`rbgcp-gradient-bar${pickerIdSuffix}`}
      // className="rbgcp-gradient-bar"
    >
      <div
        style={{
          height: 14,
          borderRadius: 10,
          width: squareWidth,
          backgroundImage: force90degLinear(value),
        }}
        onMouseDown={(e) => handleDown(e)}
        onMouseMove={(e) => handleMove(e)}
        id={`rbgcp-gradient-bar-canvas${pickerIdSuffix}`}
        // className="rbgcp-gradient-bar-canvas"
      />
      {colors?.map((c: any, i) => (
        <Handle
          i={i}
          left={c.left}
          key={`${i}-${c}`}
          setDragging={setDragging}
        />
      ))}
    </div>
  )
}

export default GradientBar


================================================
FILE: src/components/GradientControls.tsx
================================================
import React from 'react'
import { usePicker } from '../context.js'
import { formatInputValues, low, high } from '../utils/formatters.js'
import { controlBtnStyles } from '../styles/styles.js'
import TrashIcon, {
  LinearIcon,
  RadialIcon,
  DegreesIcon,
  StopIcon,
} from './icon.js'

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 (
    <div style={defaultStyles.rbgcpControlBtnWrapper}>
      <div
        onClick={handleLinear}
        id={`rbgcp-linear-btn${pickerIdSuffix}`}
        // className="rbgcp-control-icon-btn rbgcp-linear-btn"
        style={{
          ...defaultStyles.rbgcpControlBtn,
          ...(isLinear && defaultStyles.rbgcpControlBtnSelected),
        }}
        tabIndex={0}
        role="button"
        onKeyDown={() => {
          return
        }}
      >
        <LinearIcon color={isLinear ? '#568CF5' : ''} />
      </div>
      <div
        onClick={handleRadial}
        id={`rbgcp-radial-btn${pickerIdSuffix}`}
        // className="rbgcp-control-icon-btn rbgcp-radial-btn"
        style={{
          ...defaultStyles.rbgcpControlBtn,
          ...(isRadial && defaultStyles.rbgcpControlBtnSelected),
        }}
        tabIndex={0}
        role="button"
        onKeyDown={() => {
          return
        }}
      >
        <RadialIcon color={isRadial ? '#568CF5' : ''} />
      </div>
    </div>
  )
}

const StopPicker = () => {
  const {
    currentLeft,
    currentColor,
    defaultStyles,
    handleGradient,
    pickerIdSuffix,
  } = usePicker()

  const handleMove = (newVal: string) => {
    handleGradient(currentColor, formatInputValues(parseInt(newVal), 0, 100))
  }

  return (
    <div
      // className="rbgcp-stop-input-wrap"
      style={{
        ...defaultStyles.rbgcpControlBtnWrapper,
        ...defaultStyles.rbgcpControlInputWrap,
        ...defaultStyles.rbgcpStopInputWrap,
        paddingLeft: 8,
      }}
      id={`rbgcp-stop-input-wrapper${pickerIdSuffix}`}
    >
      <StopIcon />
      <input
        value={currentLeft}
        id={`rbgcp-stop-input${pickerIdSuffix}`}
        onChange={(e) => handleMove(e.target.value)}
        style={{
          ...defaultStyles.rbgcpControlInput,
          ...defaultStyles.rbgcpStopInput,
        }}
        // className="rbgcp-control-input rbgcp-stop-input"
      />
    </div>
  )
}

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 (
    <div
      // className="rbgcp-degree-input-wrap"
      style={{
        ...defaultStyles.rbgcpControlBtnWrapper,
        ...defaultStyles.rbgcpControlInputWrap,
        ...defaultStyles.rbgcpDegreeInputWrap,
      }}
      id={`rbgcp-degree-input-wrapper${pickerIdSuffix}`}
    >
      <DegreesIcon />
      <input
        value={degrees}
        onChange={(e) => handleDegrees(e)}
        id={`rbgcp-degree-input${pickerIdSuffix}`}
        // className="rbgcp-control-input rbgcp-degree-input"
        style={{
          ...defaultStyles.rbgcpControlInput,
          ...defaultStyles.rbgcpDegreeInput,
        }}
      />
      <div
        // className="rbgcp-degree-circle-icon"
        style={{
          ...defaultStyles.rbgcpDegreeIcon,
          position: 'absolute',
          right: degrees > 99 ? 0 : degrees < 10 ? 7 : 3,
          top: 1,
          fontWeight: 400,
          fontSize: 13,
        }}
      >
        °
      </div>
    </div>
  )
}

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 (
    <div
      onClick={deletePoint}
      style={{ ...controlBtnStyles(false, defaultStyles), width: 28 }}
      id={`rbgcp-point-delete-btn${pickerIdSuffix}`}
      // className="rbgcp-control-btn rbgcp-point-delete-btn"
      tabIndex={0}
      role="button"
      onKeyDown={() => {
        return
      }}
    >
      <TrashIcon />
    </div>
  )
}

const GradientControls = ({
  hideGradientType,
  hideGradientAngle,
  hideGradientStop,
}: {
  hideGradientType?: boolean
  hideGradientAngle?: boolean
  hideGradientStop?: boolean
}) => {
  const { gradientType, defaultStyles, pickerIdSuffix } = usePicker()
  return (
    <div
      style={{
        ...defaultStyles.rbgcpControlBtnWrapper,
        marginTop: 12,
        marginBottom: -4,
        justifyContent: 'space-between',
        paddingLeft: hideGradientType ? 4 : 0,
      }}
      id={`rbgcp-gradient-controls-wrap${pickerIdSuffix}`}
      // className="rbgcp-gradient-controls-wrap"
    >
      {!hideGradientType && <GradientType />}
      <div style={{ width: 53 }}>
        {!hideGradientAngle && gradientType === 'linear-gradient' && (
          <DegreePicker />
        )}
      </div>
      {!hideGradientStop && <StopPicker />}
      <DeleteBtn />
    </div>
  )
}

export default GradientControls


================================================
FILE: src/components/Hue.tsx
================================================
import React, { useRef, useState, useEffect } from 'react'
import { usePicker } from '../context.js'
import usePaintHue from '../hooks/usePaintHue.js'
import { getHandleValue } from '../utils/utils.js'
import tinycolor from 'tinycolor2'

const Hue = () => {
  const barRef = useRef<HTMLCanvasElement>(null)
  const { config, handleChange, squareWidth, hc, setHc, pickerIdSuffix } = usePicker()
  const [dragging, setDragging] = useState(false)
  const { barSize } = config
  usePaintHue(barRef, squareWidth)

  const stopDragging = () => {
    setDragging(false)
  }

  const handleDown = () => {
    setDragging(true)
  }

  const handleHue = (e: any) => {
    const newHue = getHandleValue(e, barSize) * 3.6
    const tinyHsv = tinycolor({ h: newHue, s: hc?.s, v: hc?.v })
    const { r, g, b } = tinyHsv.toRgb()
    handleChange(`rgba(${r}, ${g}, ${b}, ${hc.a})`)
    setHc({ ...hc, h: newHue })
  }

  const handleMove = (e: any) => {
    if (dragging) {
      handleHue(e)
    }
  }

  const handleClick = (e: any) => {
    if (!dragging) {
      handleHue(e)
    }
  }

  useEffect(() => {
    const handleUp = () => {
      stopDragging()
    }

    window.addEventListener('mouseup', handleUp)

    return () => {
      window.removeEventListener('mouseup', handleUp)
    }
  }, [])

  return (
    <div
      style={{
        height: 14,
        marginTop: 17,
        marginBottom: 4,
        cursor: 'ew-resize',
        position: 'relative',
      }}
      onMouseMove={(e) => handleMove(e)}
      id={`rbgcp-hue-wrap${pickerIdSuffix}`}
      // className="rbgcp-hue-wrap"
    >
      <div
        tabIndex={0}
        role="button"
        // className="rbgcp-handle rbgcp-handle-hue"
        style={{
          border: '2px solid white',
          borderRadius: '50%',
          boxShadow: '0px 0px 3px rgba(0, 0, 0, 0.5)',
          width: '18px',
          height: '18px',
          zIndex: 1000,
          transition: 'all 10ms linear',
          position: 'absolute',
          left: hc?.h * ((squareWidth - 18) / 360),
          top: -2,
          cursor: 'ew-resize',
          boxSizing: 'border-box',
        }}
        onMouseDown={handleDown}
        id={`rbgcp-hue-handle${pickerIdSuffix}`}
      />
      <canvas
        ref={barRef}
        height="14px"
        // className="rbgcp-hue-bar"
        width={`${squareWidth}px`}
        onClick={(e) => handleClick(e)}
        id={`rbgcp-hue-bar${pickerIdSuffix}`}
        style={{
          borderRadius: 14,
          position: 'relative',
          verticalAlign: 'top',
        }}
      />
    </div>
  )
}

export default Hue


================================================
FILE: src/components/Inputs.tsx
================================================
import React, { useState, useEffect } from 'react'
import { Styles } from '../shared/types.js'
import { formatInputValues, round } from '../utils/formatters.js'
import { rgb2cmyk, cmykToRgb, getHexAlpha } from '../utils/converters.js'
import { usePicker } from '../context.js'
import tc from 'tinycolor2'

const Input = ({
  label,
  value,
  callback,
  max = 100,
  hideOpacity,
  defaultStyles,
  pickerIdSuffix,
}: {
  max?: number
  label: string
  value: number
  hideOpacity: boolean
  defaultStyles: Styles
  pickerIdSuffix: string
  callback: (arg0: number) => void
}) => {
  const [temp, setTemp] = useState(value)
  const width = hideOpacity ? '25%' : '20%'

  useEffect(() => {
    setTemp(value)
  }, [value])

  const onChange = (e: any) => {
    const newVal = formatInputValues(parseFloat(e.target.value), 0, max)
    setTemp(newVal)
    callback(newVal)
  }

  return (
    <div
      style={{ width: width, flexShrink: 1 }}
      id={`rbgcp-${label}-input-wrapper${pickerIdSuffix}`}
    >
      <input
        value={temp}
        onChange={(e) => onChange(e)}
        style={{ ...defaultStyles.rbgcpInput }}
        id={`rbgcp-${label}-input${pickerIdSuffix}`}
        // className="rbgcp-input"
      />
      <div style={{ ...defaultStyles.rbgcpInputLabel }}>{label}</div>
    </div>
  )
}

const HexInput = ({
  opacity,
  tinyColor,
  showHexAlpha,
  handleChange,
  defaultStyles,
  pickerIdSuffix,
}: {
  tinyColor: any
  opacity: number
  showHexAlpha: boolean
  defaultStyles: Styles
  pickerIdSuffix: string
  handleChange: (arg0: string) => void
}) => {
  const [disable, setDisable] = useState('')
  const hex = tinyColor.toHex()
  const [newHex, setNewHex] = useState(hex)

  useEffect(() => {
    if (disable !== 'hex') {
      setNewHex(hex)
    }
  }, [tinyColor, disable, hex])

  const hexFocus = () => {
    setDisable('hex')
  }

  const hexBlur = () => {
    setDisable('')
  }

  const handleHex = (e: any) => {
    const tinyHex = tc(e.target.value)
    setNewHex(e.target.value)
    if (tinyHex.isValid()) {
      const { r, g, b } = tinyHex.toRgb()
      const newColor = `rgba(${r}, ${g}, ${b}, ${opacity})`
      handleChange(newColor)
    }
  }

  const displayValue = showHexAlpha ? `${newHex}${getHexAlpha(opacity)}` : newHex
  const label = showHexAlpha ? 'HEXA' : 'HEX'
  const width = showHexAlpha ? 88 : 76

  return (
    <div
      style={{ width, flexShrink: 0 }}
      id={`rbgcp-hex-input-wrapper${pickerIdSuffix}`}
    >
      <input
        onBlur={hexBlur}
        onFocus={hexFocus}
        onChange={(e) => handleHex(e)}
        value={displayValue?.toUpperCase()}
        id={`rbgcp-hex-input${pickerIdSuffix}`}
        style={{ ...defaultStyles.rbgcpInput, ...defaultStyles.rbgcpHexInput }}
      />
      <div style={{ ...defaultStyles.rbgcpInputLabel }}>{label}</div>
    </div>
  )
}

const RGBInputs = ({
  hc,
  hideOpacity,
  handleChange,
  defaultStyles,
  pickerIdSuffix,
}: {
  hc: any
  hideOpacity: boolean
  defaultStyles: Styles
  pickerIdSuffix: string
  handleChange: (arg0: string) => void
}) => {
  const handleRgb = ({ r, g, b }: { r: number; g: number; b: number }) => {
    handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
  }

  return (
    <>
      <Input
        label="R"
        max={255}
        value={hc?.r}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleRgb({ r: newVal, g: hc?.g, b: hc?.b })}
      />
      <Input
        label="G"
        max={255}
        value={hc?.g}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleRgb({ r: hc?.r, g: newVal, b: hc?.b })}
      />
      <Input
        label="B"
        max={255}
        value={hc?.b}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleRgb({ r: hc?.r, g: hc?.g, b: newVal })}
      />
    </>
  )
}

const HSLInputs = ({
  hc,
  setHc,
  tinyColor,
  hideOpacity,
  handleChange,
  defaultStyles,
  pickerIdSuffix,
}: {
  hc: any
  tinyColor: any
  hideOpacity: boolean
  defaultStyles: Styles
  pickerIdSuffix: string
  setHc: (arg0: any) => void
  handleChange: (arg0: string) => void
}) => {
  const { s, l } = tinyColor.toHsl()

  const handleH = (h: number, s: number, l: 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: any) => {
    const { r, g, b } = tc(value).toRgb()
    handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
  }

  return (
    <>
      <Input
        label="H"
        max={360}
        value={round(hc?.h)}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleH(newVal, s, l)}
      />
      <Input
        label="S"
        value={round(s * 100)}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleSl({ h: hc?.h, s: newVal, l: l })}
      />
      <Input
        label="L"
        value={round(l * 100)}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleSl({ h: hc?.h, s: s, l: newVal })}
      />
    </>
  )
}

const HSVInputs = ({
  hc,
  setHc,
  hideOpacity,
  handleChange,
  defaultStyles,
  pickerIdSuffix,
}: {
  hc: any
  hideOpacity: boolean
  defaultStyles: Styles
  pickerIdSuffix: string
  setHc: (arg0: any) => void
  handleChange: (arg0: string) => void
}) => {
  const handleH = (h: number, s: number, v: number) => {
    const { r, g, b } = tc({ h: h, s: s, v: v }).toRgb()
    handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
    setHc({ ...hc, h })
  }

  const handleSV = (value: any) => {
    const { r, g, b } = tc(value).toRgb()
    handleChange(`rgba(${r}, ${g}, ${b}, ${hc?.a})`)
  }

  return (
    <>
      <Input
        label="H"
        max={360}
        value={round(hc?.h)}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleH(newVal, hc?.s, hc?.v)}
      />
      <Input
        label="S"
        hideOpacity={hideOpacity}
        value={round(hc?.s * 100)}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleSV({ h: hc?.h, s: newVal, v: hc?.v })}
      />
      <Input
        label="V"
        hideOpacity={hideOpacity}
        value={round(hc?.v * 100)}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleSV({ h: hc?.h, s: hc?.s, v: newVal })}
      />
    </>
  )
}

const CMKYInputs = ({
  hc,
  hideOpacity,
  handleChange,
  defaultStyles,

  pickerIdSuffix,
}: {
  hc: any
  hideOpacity: boolean
  defaultStyles: Styles
  pickerIdSuffix: string
  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})`)
  }

  return (
    <>
      <Input
        label="C"
        value={round(c * 100)}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleCmyk({ c: newVal / 100, m: m, y: y, k: k })}
      />
      <Input
        label="M"
        value={round(m * 100)}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleCmyk({ c: c, m: newVal / 100, y: y, k: k })}
      />
      <Input
        label="Y"
        value={round(y * 100)}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleCmyk({ c: c, m: m, y: newVal / 100, k: k })}
      />
      <Input
        label="K"
        value={round(k * 100)}
        hideOpacity={hideOpacity}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
        callback={(newVal) => handleCmyk({ c: c, m: m, y: y, k: newVal / 100 })}
      />
    </>
  )
}

const Inputs = () => {
  const {
    hc,
    setHc,
    inputType,
    tinyColor,
    hideOpacity,
    showHexAlpha,
    handleChange,
    defaultStyles,
    pickerIdSuffix,
  } = usePicker()

  return (
    <div
      style={{
        columnGap: 6,
        paddingTop: 14,
        display: 'flex',
        justifyContent: 'space-between',
        ...defaultStyles.rbgcpInputsWrap,
      }}
      id={`rbgcp-inputs-wrap${pickerIdSuffix}`}
    >
      {inputType !== 'cmyk' && (
        <HexInput
          opacity={hc?.a}
          tinyColor={tinyColor}
          showHexAlpha={showHexAlpha}
          handleChange={handleChange}
          defaultStyles={defaultStyles}
          pickerIdSuffix={pickerIdSuffix}
        />
      )}
      {inputType === 'hsl' && (
        <HSLInputs
          hc={hc}
          setHc={setHc}
          tinyColor={tinyColor}
          hideOpacity={hideOpacity}
          handleChange={handleChange}
          defaultStyles={defaultStyles}
          pickerIdSuffix={pickerIdSuffix}
        />
      )}
      {inputType === 'rgb' && (
        <RGBInputs
          hc={hc}
          hideOpacity={hideOpacity}
          handleChange={handleChange}
          defaultStyles={defaultStyles}
          pickerIdSuffix={pickerIdSuffix}
        />
      )}
      {inputType === 'hsv' && (
        <HSVInputs
          hc={hc}
          setHc={setHc}
          hideOpacity={hideOpacity}
          handleChange={handleChange}
          defaultStyles={defaultStyles}
          pickerIdSuffix={pickerIdSuffix}
        />
      )}
      {inputType === 'cmyk' && (
        <CMKYInputs
          hc={hc}
          hideOpacity={hideOpacity}
          handleChange={handleChange}
          defaultStyles={defaultStyles}
          pickerIdSuffix={pickerIdSuffix}
        />
      )}

      {!hideOpacity && (
        <Input
          label="A"
          hideOpacity={hideOpacity}
          defaultStyles={defaultStyles}
          value={Math.round(hc?.a * 100)}
          pickerIdSuffix={pickerIdSuffix}
          callback={(newVal: number) =>
            handleChange(`rgba(${hc?.r}, ${hc?.g}, ${hc?.b}, ${newVal / 100})`)
          }
        />
      )}
    </div>
  )
}

export default Inputs


================================================
FILE: src/components/Opacity.tsx
================================================
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useState, useEffect } from 'react'
import { usePicker } from '../context.js'
import { getHandleValue } from '../utils/utils.js'

const Opacity = () => {
  const {
    config,
    hc = {},
    squareWidth,
    handleChange,
    defaultStyles,
    pickerIdSuffix,
  } = usePicker()
  const [dragging, setDragging] = useState(false)
  const { r, g, b } = hc
  const bg = `linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(${r},${g},${b},.5) 100%)`
  const { barSize } = config

  const stopDragging = () => {
    setDragging(false)
  }

  const handleDown = () => {
    setDragging(true)
  }

  const handleOpacity = (e: any) => {
    const newO = getHandleValue(e, barSize) / 100
    const newColor = `rgba(${r}, ${g}, ${b}, ${newO})`
    handleChange(newColor)
  }

  const handleMove = (e: any) => {
    if (dragging) {
      handleOpacity(e)
    }
  }

  const handleClick = (e: any) => {
    if (!dragging) {
      handleOpacity(e)
    }
  }

  const left = squareWidth - 18

  useEffect(() => {
    const handleUp = () => {
      stopDragging()
    }

    window.addEventListener('mouseup', handleUp)

    return () => {
      window.removeEventListener('mouseup', handleUp)
    }
  }, [])

  return (
    <div
      onMouseDown={handleDown}
      onMouseMove={(e) => handleMove(e)}
      style={{
        height: 14,
        marginTop: 17,
        marginBottom: 4,
        cursor: 'ew-resize',
        position: 'relative',
      }}
      id={`rbgcp-opacity-wrapper${pickerIdSuffix}`}
      // className="rbgcp-opacity-wrap"
    >
      <div
        // className="rbgcp-opacity-checkered"
        id={`rbgcp-opacity-checkered-bg${pickerIdSuffix}`}
        style={{ ...defaultStyles.rbgcpCheckered, width: '100%', height: 14 }}
      />
      <div
        // className="rbgcp-handle rbgcp-handle-opacity"
        id={`rbgcp-opacity-handle${pickerIdSuffix}`}
        style={{ ...defaultStyles.rbgcpHandle, left: left * hc?.a, top: -2 }}
      />
      <div
        style={{ ...defaultStyles.rbgcpOpacityOverlay, background: bg }}
        id={`rbgcp-opacity-overlay${pickerIdSuffix}`}
        // className="rbgcp-opacity-overlay"
        onClick={(e) => handleClick(e)}
      />
    </div>
  )
}

export default Opacity


================================================
FILE: src/components/Picker.tsx
================================================
import React from 'react'
import Hue from './Hue.js'
import Inputs from './Inputs.js'
import Square from './Square.js'
import Opacity from './Opacity.js'
import Presets from './Presets.js'
import Controls from './Controls.js'
import { usePicker } from '../context.js'
import GradientBar from './GradientBar.js'
import { LocalesProps } from '../shared/types.js'

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 (
    <div style={{ userSelect: 'none' }} id={`rbgcp-color-picker${pickerIdSuffix}`}>
      {!hidePickerSquare && <Square />}
      {!hideControls && (
        <Controls
          locales={locales}
          hideEyeDrop={hideEyeDrop}
          hideInputType={hideInputType}
          hideColorGuide={hideColorGuide}
          hideGradientType={hideGradientType}
          hideGradientStop={hideGradientStop}
          hideColorTypeBtns={hideColorTypeBtns}
          hideGradientAngle={hideGradientAngle}
          hideAdvancedSliders={hideAdvancedSliders}
          hideGradientControls={hideGradientControls}
        />
      )}
      {isGradient && <GradientBar />}
      {!hideHue && <Hue />}
      {!hideOpacity && <Opacity />}
      {!hideInputs && <Inputs />}
      {!hidePresets && <Presets presets={presets} />}
    </div>
  )
}

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
}


================================================
FILE: src/components/Portal.tsx
================================================
import { memo, useEffect, useRef, useState, 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)
      }
    }
    //eslint-disable-next-line
  }, [id])
  return createPortal(children, el.current)
}

export default memo(Portal)


================================================
FILE: src/components/Presets.tsx
================================================
/* eslint-disable react/no-array-index-key */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React from 'react'
import { usePicker } from '../context.js'
import { fakePresets } from '../constants.js'

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 (
    <div
      style={{
        marginTop: 14,
        display: 'flex',
        justifyContent: 'space-between',
      }}
      id={`rbgcp-footer-wrapper${pickerIdSuffix}`}
      // className="rbgcp-presets-wrap"
    >
      <div
        style={{
          width: 50,
          height: 50,
          flexShrink: 0,
          borderRadius: 6,
          background: value,
          border: getBorder(value),
        }}
        id={`rbgcp-preview${pickerIdSuffix}`}
        // className="rbgcp-preset-color-preview"
      />
      <div
        style={{
          rowGap: 3,
          display: 'flex',
          flexWrap: 'wrap',
          width: squareWidth - 57,
          justifyContent: 'space-between',
        }}
        id={`rbgcp-presets-wrapper${pickerIdSuffix}`}
        // className="rbgcp-presets-list"
      >
        {getPresets().map((p: any, key: number) => (
          <div
            key={`${p}-${key}`}
            id={`rbgcp-preset-${key}-wrapper${pickerIdSuffix}`}
            style={{ width: `calc(100% / 9)`, paddingLeft: 3 }}
          >
            <div
              style={{
                height: 23.5,
                width: '100%',
                background: p,
                borderRadius: 4,
                border: getBorder(p),
              }}
              // className="rbgcp-preset-color"
              onClick={() => handlePresetClick(p)}
              id={`rbgcp-preset-${key}${pickerIdSuffix}`}
            />
          </div>
        ))}
      </div>
    </div>
  )
}

export default Presets


================================================
FILE: src/components/Square.tsx
================================================
/* eslint-disable jsx-a11y/no-static-element-interactions */
import { computePickerPosition, computeSquareXY } from '../utils/utils.js'
import React, { useRef, useState, useEffect } from 'react'
import usePaintSquare from '../hooks/usePaintSquare.js'
import { usePicker } from '../context.js'
import throttle from 'lodash.throttle'
import tinycolor from 'tinycolor2'

const Square = () => {
  const {
    hc,
    config,
    squareWidth,
    squareHeight,
    handleChange,
    defaultStyles,
    pickerIdSuffix,
  } = usePicker()
  const { crossSize } = config
  const [dragging, setDragging] = useState(false)
  const canvas = useRef<HTMLCanvasElement>(null)
  const [x, y] = computeSquareXY(hc?.s, hc?.v * 100, squareWidth, squareHeight, crossSize)
  const [dragPos, setDragPos] = useState({ x, y })

  usePaintSquare(canvas, hc?.h, squareWidth, squareHeight)

  useEffect(() => {
    if (!dragging) {
      setDragPos({ x: hc?.v === 0 ? dragPos.x : x, y })
    }
  }, [x, y])

  const handleColor = (e: any) => {
    const onMouseMove = throttle(() => {
      const [x, y] = computePickerPosition(e, crossSize)
      if (x && y) {
        const x1 = Math.min(x + crossSize / 2, squareWidth - 1)
        const y1 = Math.min(y + crossSize / 2, squareHeight - 1)
        const newS = (x1 / squareWidth) * 100
        const newY = 100 - (y1 / squareHeight) * 100
        setDragPos({ x: newY === 0 ? dragPos?.x : x, y })
        const updated = tinycolor(
          `hsva(${hc?.h}, ${newS}%, ${newY}%, ${hc?.a})`
        )
        handleChange(updated.toRgbString())
      }
    }, 250)

    onMouseMove()
  }

  const stopDragging = () => {
    setDragging(false)
  }

  const handleMove = (e: any) => {
    if (dragging) {
      handleColor(e)
    }
  }

  // const handleTouchMove = (e: any) => {
  //   if (dragging && isMobile) {
  //     document.body.style.overflow = 'hidden'
  //     handleColor(e)
  //   }
  // }

  const handleClick = (e: any) => {
    if (!dragging) {
      handleColor(e)
    }
  }

  const handleMouseDown = () => {
    setDragging(true)
  }

  const handleCanvasDown = (e: any) => {
    setDragging(true)
    handleColor(e)
  }

  useEffect(() => {
    const handleUp = () => {
      stopDragging()
    }

    window.addEventListener('mouseup', handleUp)

    return () => {
      window.removeEventListener('mouseup', handleUp)
    }
  }, [])

  return (
    <div
      style={{ position: 'relative', marginBottom: 12 }}
      id={`rbgcp-square-wrapper${pickerIdSuffix}`}
    >
      <div
        onMouseUp={stopDragging}
        onTouchEnd={stopDragging}
        onMouseDown={handleCanvasDown}
        onTouchStart={handleCanvasDown}
        onMouseMove={(e) => handleMove(e)}
        id={`rbgcp-square${pickerIdSuffix}`}
        style={{ position: 'relative', cursor: 'ew-cross' }}
        // className="rbgcp-square-wrap"
      >
        <div
          style={{
            ...defaultStyles.rbgcpHandle,
            transform: `translate(${dragPos?.x ?? 0}px, ${dragPos?.y ?? 0}px)`,
            ...(dragging ? { transition: '' } : {}),
          }}
          onMouseDown={handleMouseDown}
          id={`rbgcp-square-handle${pickerIdSuffix}`}
          // className="rbgcp-handle rbgcp-handle-square"
        />
        <div
          style={{ ...defaultStyles.rbgcpCanvasWrapper, height: squareHeight }}
          id={`rbgcp-square-canvas-wrapper${pickerIdSuffix}`}
          // className="rbgcp-canvas-wrapper"
          onClick={(e) => handleClick(e)}
        >
          <canvas
            ref={canvas}
            // className="rbgcp-canvas"
            width={`${squareWidth}px`}
            height={`${squareHeight}px`}
            id={`rbgcp-square-canvas${pickerIdSuffix}`}
          />
        </div>
      </div>
    </div>
  )
}

export default Square


================================================
FILE: src/components/icon.tsx
================================================
import React from 'react'
import { usePicker } from '../context.js'

type ColorProps = {
  color: string
}

const TrashIcon = () => {
  const { defaultStyles } = usePicker()

  const styles = {
    fill: 'none',
    strokeWidth: '1.8px',
  }
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 15 }}
    >
      <polyline
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{ ...styles, ...defaultStyles.rbgcpControlIcon }}
        points="17.96 4.31 2.04 4.3 3.75 4.3 4.81 17.29 5.16 17.96 5.74 18.47 6.59 18.62 13.64 18.62 14.52 18.32 15.07 17.68 15.29 17.12 16.28 4.3 12.87 4.3 12.87 2.38 12.48 1.75 11.83 1.46 8.4 1.46 7.64 1.68 7.26 2.21 7.16 2.52 7.17 4.23"
      />
    </svg>
  )
}

export default TrashIcon

export const LinearIcon = ({ color }: ColorProps) => {
  const { defaultStyles } = usePicker()

  const col = color ?? ''
  const styles = {
    fill: 'none',
    strokeWidth: '1.8px',
  }
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 14 }}
    >
      <polyline
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...styles,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        points="0.9 12.73 0.9 19.1 7.27 19.1 0.9 19.1 19.1 0.9 12.73 0.9 19.1 0.9 19.1 7.27"
      />
    </svg>
  )
}

export const RadialIcon = ({ color }: ColorProps) => {
  const { defaultStyles } = usePicker()

  const col = color ?? ''
  const styles = {
    fill: 'none',
    strokeMiterlimit: 10,
    strokeWidth: '1.8px',
  }
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 15 }}
    >
      <circle
        style={{
          ...styles,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        cx="10"
        cy="10"
        r="9"
      />
      <circle
        style={{
          ...styles,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        cx="10"
        cy="10"
        r="5"
      />
    </svg>
  )
}

export const SlidersIcon = ({ color }: ColorProps) => {
  const { defaultStyles } = usePicker()

  const col = color ?? ''
  const style1 = {
    fill: 'none',
    strokeWidth: '1.8px',
  }
  const style2 = {
    strokeWidth: '1.8px',
  }

  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 17 }}
    >
      <polyline
        fillRule="evenodd"
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style1,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        points="3.74 2.75 3.74 12.69 0.9 12.71 6.59 12.71"
      />
      <line
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style2,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col, fill: col }),
        }}
        x1="3.74"
        y1="17.26"
        x2="3.74"
        y2="15.21"
      />
      <polyline
        fillRule="evenodd"
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style1,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        points="10.1 17.25 10.1 7.31 12.95 7.29 7.26 7.29"
      />
      <line
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style2,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col, fill: col }),
        }}
        x1="10.1"
        y1="2.74"
        x2="10.1"
        y2="4.79"
      />
      <polyline
        fillRule="evenodd"
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style1,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        points="16.26 2.75 16.26 12.69 13.41 12.71 19.1 12.71"
      />
      <line
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style2,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col, fill: col }),
        }}
        x1="16.26"
        y1="17.26"
        x2="16.26"
        y2="15.21"
      />
    </svg>
  )
}

export const InputsIcon = ({ color }: ColorProps) => {
  const { defaultStyles } = usePicker()

  const col = color ?? ''
  const style1 = {
    fill: 'none',
    strokeWidth: '1.8px',
  }
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 17 }}
    >
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style1,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        d="M6.35,2.72a4.85,4.85,0,0,1,1.86.16,7.94,7.94,0,0,1,.88.43,3.66,3.66,0,0,0,.85.49c.25,0,.58-.27.81-.39A8.25,8.25,0,0,1,11.7,3a4,4,0,0,1,1.79-.23,3.21,3.21,0,0,0-1.34.09,6.39,6.39,0,0,0-1.47.63c-.45.25-.7.3-.7.86s0,1.18,0,1.78c0,1.3,0,2.61,0,3.92h0v5.63a2.46,2.46,0,0,1,0,.47c-.07.28-.43.42-.7.57a5.29,5.29,0,0,1-2.94.61A9.3,9.3,0,0,0,8,17.15l1.09-.37.89-.52c.06,0,.48.21.56.25.32.14.64.27,1,.38a8.54,8.54,0,0,0,2.12.4"
      />
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style1,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        d="M7.57,5.73C6,5.7,4.5,5.65,3,5.77a2.28,2.28,0,0,0-1.76.74A2.3,2.3,0,0,0,.94,7.83l0,3.82A4.73,4.73,0,0,0,1,12.9a1.64,1.64,0,0,0,.68,1,2.44,2.44,0,0,0,1,.27,25,25,0,0,0,4.74.09"
      />
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        style={{
          ...style1,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        d="M12.43,14.32a44.12,44.12,0,0,0,4.6,0,2.24,2.24,0,0,0,1.76-.74,2.29,2.29,0,0,0,.27-1.32l0-3.81A4.81,4.81,0,0,0,19,7.15a1.62,1.62,0,0,0-.68-1,2.31,2.31,0,0,0-1-.28,26.8,26.8,0,0,0-4.74-.09"
      />
    </svg>
  )
}

export const PaletteIcon = ({ color }: ColorProps) => {
  const { defaultStyles } = usePicker()

  const col = color ?? ''
  const style2 = {
    strokeMiterlimit: 10,
    strokeWidth: '0.5px',
  }
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 17 }}
    >
      <circle
        style={{
          ...defaultStyles.rbgcpControlIcon2,
          ...(col && { fill: col }),
        }}
        cx="9.36"
        cy="5.07"
        r="1.71"
      />
      <circle
        style={{
          ...defaultStyles.rbgcpControlIcon2,
          ...(col && { fill: col }),
        }}
        cx="13.93"
        cy="6.91"
        r="1.71"
      />
      <circle
        style={{
          ...defaultStyles.rbgcpControlIcon2,
          ...(col && { fill: col }),
        }}
        cx="5.8"
        cy="7.55"
        r="1.71"
      />
      <circle
        style={{
          ...defaultStyles.rbgcpControlIcon2,
          ...(col && { fill: col }),
        }}
        cx="5.45"
        cy="12.04"
        r="1.71"
      />
      <path
        style={{
          ...style2,
          ...defaultStyles.rbgcpControlIcon,
          ...defaultStyles.rbgcpControlIcon2,
          ...(col && { fill: col, stroke: col }),
        }}
        d="M19.1,10c0,3.58-2.12,2.94-4.06,2.35-1.15-.34-2.24-.67-2.77-.08-.68.78-.54,2.07-.39,3.33.2,1.79.39,3.5-1.88,3.5A9.1,9.1,0,1,1,19.1,10ZM10,18c.7,0,.74-.19.75-.2a2.67,2.67,0,0,0,.07-1.27c0-.19,0-.42-.06-.67-.06-.53-.13-1.15-.14-1.67a3.82,3.82,0,0,1,.8-2.63,2.14,2.14,0,0,1,1.45-.7,4.36,4.36,0,0,1,1.32.12c.39.08.8.21,1.16.32h0c.39.12.74.23,1.08.3.74.17,1,.1,1.13,0S18,11.32,18,10a8,8,0,1,0-8,8Z"
      />
    </svg>
  )
}

export const DegreesIcon = ({ color }: { color?: string }) => {
  const { defaultStyles } = usePicker()

  const col = color ?? ''
  const style2 = {
    fill: 'none',
    strokeMiterlimit: 10,
    strokeWidth: '1.8px',
  }
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 15 }}
    >
      <polyline
        strokeLinecap="round"
        style={{
          ...style2,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        points="13.86 2.01 1.7 16.99 18.77 16.99"
      />
      <polyline
        strokeLinecap="round"
        style={{
          ...style2,
          ...defaultStyles.rbgcpControlIcon,
          ...(col && { stroke: col }),
        }}
        points="10.96 16.38 10.96 16.38 10.74 15.7 10.44 14.97 10.06 14.21 9.72 13.63 9.21 12.89 8.85 12.44 8.41 11.95 7.91 11.45 7.51 11.1"
      />
    </svg>
  )
}

export const StopIcon = () => {
  const { defaultStyles } = usePicker()

  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 20 20"
      style={{ width: 20, marginRight: 1 }}
    >
      <path
        style={{ ...defaultStyles.rbgcpControlIcon2 }}
        d="M2.39,8c-.63,0-1,.21-1,.63A.49.49,0,0,0,1.67,9a6.48,6.48,0,0,0,1.11.43A3,3,0,0,1,4,10.09a1.47,1.47,0,0,1,.35,1.09,1.75,1.75,0,0,1-.57,1.42,2.21,2.21,0,0,1-1.48.48,8.32,8.32,0,0,1-1.68-.21l-.31-.06.12-.94a13.7,13.7,0,0,0,1.8.16c.61,0,.92-.26.92-.77a.52.52,0,0,0-.21-.44,3.13,3.13,0,0,0-.85-.34A3.32,3.32,0,0,1,.66,9.79a1.43,1.43,0,0,1-.42-1.1A1.6,1.6,0,0,1,.78,7.36a2.32,2.32,0,0,1,1.49-.44,10.46,10.46,0,0,1,1.64.17l.32.07-.1.95C3.31,8,2.73,8,2.39,8Z"
      />
      <path
        style={{ ...defaultStyles.rbgcpControlIcon2 }}
        d="M4.79,8.09V7H9.16V8.09H7.59V13H6.38V8.09Z"
      />
      <path
        style={{ ...defaultStyles.rbgcpControlIcon2 }}
        d="M14,12.34a2.25,2.25,0,0,1-1.91.74,2.24,2.24,0,0,1-1.91-.74A3.85,3.85,0,0,1,9.61,10a4,4,0,0,1,.56-2.34,2.2,2.2,0,0,1,1.91-.77A2.21,2.21,0,0,1,14,7.69,4,4,0,0,1,14.55,10,3.85,3.85,0,0,1,14,12.34Zm-2.88-.77a1,1,0,0,0,1,.46,1,1,0,0,0,1-.46A3.25,3.25,0,0,0,13.3,10,3.45,3.45,0,0,0,13,8.46a1,1,0,0,0-1-.49,1,1,0,0,0-1,.49A3.43,3.43,0,0,0,10.85,10,3.38,3.38,0,0,0,11.11,11.57Z"
      />
      <path
        style={{ ...defaultStyles.rbgcpControlIcon2 }}
        d="M17.77,11.24h-1V13H15.58V7h2.19a1.85,1.85,0,0,1,2.11,2.07,2.21,2.21,0,0,1-.54,1.6A2.07,2.07,0,0,1,17.77,11.24Zm-1-1h1c.6,0,.9-.37.9-1.12a1.18,1.18,0,0,0-.22-.79.88.88,0,0,0-.68-.24h-1Z"
      />
    </svg>
  )
}


================================================
FILE: src/components/index.tsx
================================================
'use client'
import React from 'react'
import PickerContextWrapper from '../context.js'
import Picker from './Picker.js'
import { ColorPickerProps } from '../shared/types.js'
import { defaultLocales } from '../constants.js'
import { objectToString } from '../utils/utils.js'
import { getStyles } from '../styles/styles.js'

export function ColorPicker({
  idSuffix,
  value = 'rgba(175, 51, 242, 1)',
  onChange,
  hideControls = false,
  hideInputs = false,
  hideOpacity = false,
  hidePresets = false,
  hideHue = false,
  presets = [],
  hideEyeDrop = false,
  hideAdvancedSliders = false,
  hideColorGuide = false,
  hideInputType = false,
  hideColorTypeBtns = false,
  hideGradientType = false,
  hideGradientAngle = false,
  hideGradientStop = false,
  hideGradientControls = false,
  locales = defaultLocales,
  width = 294,
  height = 294,
  style = {},
  className,
  disableDarkMode = false,
  disableLightMode = false,
  hidePickerSquare = false,
  showHexAlpha = false,
  config = {},
}: ColorPickerProps) {
  const safeValue = objectToString(value)
  const isDarkMode =
    typeof window === 'undefined' || disableDarkMode
      ? false
      : window.matchMedia('(prefers-color-scheme: dark)').matches ||
          disableLightMode
        ? true
        : false
  // const contRef = useRef<HTMLDivElement>(null)
  const defaultStyles = getStyles(isDarkMode, style)
  const pickerIdSuffix = isDarkMode
    ? `-dark${idSuffix ? `-${idSuffix}` : ''}`
    : idSuffix
      ? `-${idSuffix}`
      : ''

  return (
    <div
      // ref={contRef}
      className={className}
      style={{ ...defaultStyles.body, width: width }}
    >
      <PickerContextWrapper
        value={safeValue}
        onChange={onChange}
        squareWidth={width}
        passedConfig={config}
        squareHeight={height}
        isDarkMode={isDarkMode}
        hideOpacity={hideOpacity}
        showHexAlpha={showHexAlpha}
        defaultStyles={defaultStyles}
        pickerIdSuffix={pickerIdSuffix}
      >
        <Picker
          hideControls={hideControls}
          hideInputs={hideInputs}
          hidePresets={hidePresets}
          hideOpacity={hideOpacity}
          hideHue={hideHue}
          presets={presets}
          hideEyeDrop={hideEyeDrop}
          hideAdvancedSliders={hideAdvancedSliders}
          hideColorGuide={hideColorGuide}
          hideInputType={hideInputType}
          hideColorTypeBtns={hideColorTypeBtns}
          hideGradientType={hideGradientType}
          hideGradientAngle={hideGradientAngle}
          hideGradientStop={hideGradientStop}
          hideGradientControls={hideGradientControls}
          hidePickerSquare={hidePickerSquare}
          locales={locales}
        />
      </PickerContextWrapper>
    </div>
  )
}


================================================
FILE: src/constants.ts
================================================
export const defaultLocales = {
  CONTROLS: {
    SOLID: 'Solid',
    GRADIENT: 'Gradient',
  },
}

export const fakePresets = [
  'rgba(0,0,0,1)',
  'rgba(128,128,128, 1)',
  'rgba(192,192,192, 1)',
  'rgba(255,255,255, 1)',
  'rgba(0,0,128,1)',
  'rgba(0,0,255,1)',
  'rgba(0,255,255, 1)',
  'rgba(0,128,0,1)',
  'rgba(128,128,0, 1)',
  'rgba(0,128,128,1)',
  'rgba(0,255,0, 1)',
  'rgba(128,0,0, 1)',
  'rgba(128,0,128, 1)',
  'rgba(175, 51, 242, 1)',
  'rgba(255,0,255, 1)',
  'rgba(255,0,0, 1)',
  'rgba(240, 103, 46, 1)',
  'rgba(255,255,0, 1)',
]


================================================
FILE: src/context.tsx
================================================
import React, {
  createContext,
  useContext,
  ReactNode,
  useEffect,
  useState,
} from 'react'
import { GradientProps, Styles, PassedConfig, Config } from './shared/types.js'
import { isUpperCase, getColorObj, getDetails } from './utils/utils.js'
import { low, high, getColors } from './utils/formatters.js'
import tinycolor from 'tinycolor2'

const PickerContext = createContext<PickerContextProps | null>(null)

export default function PickerContextWrapper({
  value,
  children,
  onChange,
  isDarkMode,
  squareWidth,
  hideOpacity,
  showHexAlpha,
  squareHeight,
  passedConfig,
  defaultStyles,
  pickerIdSuffix,
}: PCWProps) {
  const config: Config = {
    barSize: passedConfig.barSize ?? defaultConfig.barSize,
    crossSize: passedConfig.crossSize ?? defaultConfig.crossSize,
    defaultColor: passedConfig.defaultColor ?? defaultConfig.defaultColor,
    defaultGradient:
      passedConfig.defaultGradient ?? defaultConfig.defaultGradient,
  }

  const colors = getColors(value, config.defaultColor, config.defaultGradient)
  const { degrees, degreeStr, isGradient, gradientType } = getDetails(value)
  const { currentColor, selectedColor, currentLeft } = getColorObj(colors, config.defaultGradient)
  const [inputType, setInputType] = useState('rgb')
  const [previous, setPrevious] = useState({})
  const tinyColor = tinycolor(currentColor)
  const rgba = tinyColor.toRgb()
  const hsv = tinyColor.toHsv()
  const [hc, setHc] = useState({ ...rgba, ...hsv })

  useEffect(() => {
    if (hsv?.s === 0) {
      setHc({ ...rgba, ...hsv, h: hc?.h })
    } else {
      setHc({ ...rgba, ...hsv })
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentColor])

  const createGradientStr = (newColors: GradientProps[]) => {
    const sorted = newColors.sort(
      (a: GradientProps, b: GradientProps) => a.left - b.left
    )
    const colorString = sorted?.map((cc: any) => `${cc?.value} ${cc.left}%`)
    const newGrade = `${gradientType}(${degreeStr}, ${colorString.join(', ')})`
    setPrevious({ ...previous, gradient: newGrade })
    onChange(newGrade)
  }

  const handleGradient = (newColor: string, left?: number) => {
    const remaining = colors?.filter(
      (c: GradientProps) => !isUpperCase(c.value)
    )
    const newColors = [
      { value: newColor.toUpperCase(), left: left ?? currentLeft },
      ...remaining,
    ]
    createGradientStr(newColors)
  }

  const handleChange = (newColor: string) => {
    if (isGradient) {
      handleGradient(newColor)
    } else {
      setPrevious({ ...previous, color: newColor })
      onChange(newColor)
    }
  }

  const deletePoint = () => {
    if (colors?.length > 2) {
      const formatted = colors?.map((fc: GradientProps, i: number) => ({
        ...fc,
        value: i === selectedColor - 1 ? high(fc) : low(fc),
      }))
      const remaining = formatted?.filter(
        (_: any, i: number) => i !== selectedColor
      )
      createGradientStr(remaining)
    }
  }

  const pickerContext = {
    hc,
    setHc,
    value,
    colors,
    config,
    degrees,
    onChange,
    previous,
    inputType,
    tinyColor,
    isDarkMode,
    isGradient,
    squareWidth,
    hideOpacity,
    currentLeft,
    deletePoint,
    showHexAlpha,
    squareHeight,
    setInputType,
    gradientType,
    handleChange,
    currentColor,
    selectedColor,
    defaultStyles,
    handleGradient,
    pickerIdSuffix,
    createGradientStr,
  }

  return (
    <PickerContext.Provider value={pickerContext}>
      {children}
    </PickerContext.Provider>
  )
}

export function usePicker() {
  const pickerContext = useContext(PickerContext)

  if (!pickerContext) {
    throw new Error('usePicker has to be used within <PickerContext.Provider>')
  }

  return pickerContext
}

type PCWProps = {
  value: string
  squareWidth: number
  children: ReactNode
  squareHeight: number
  hideOpacity: boolean
  onChange: (arg0: string) => void
  defaultStyles: Styles
  isDarkMode: boolean
  pickerIdSuffix: string
  showHexAlpha: boolean
  passedConfig: PassedConfig
}

export type PickerContextProps = {
  hc: any
  config: Config
  value: string
  colors: GradientProps[]
  degrees: number
  onChange: (arg0: string) => void
  inputType: string
  tinyColor: any
  isGradient: boolean
  squareWidth: number
  hideOpacity: boolean
  currentLeft: number
  deletePoint: () => void
  squareHeight: number
  setInputType: (arg0: string) => void
  gradientType?: string
  handleChange: (arg0: string) => void
  currentColor: string
  selectedColor: number
  setHc: (arg0: any) => void
  handleGradient: (arg0: string, arg1?: number) => void
  createGradientStr: (arg0: GradientProps[]) => void
  defaultStyles: Styles
  previous: {
    color?: string
    gradient?: string
  }
  isDarkMode: boolean
  pickerIdSuffix: string
  showHexAlpha: boolean
}

const defaultConfig = {
  barSize: 18,
  crossSize: 18,
  inputSize: 40,
  delay: 150,
  defaultColor: 'rgba(175, 51, 242, 1)',
  defaultGradient:
    'linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%)',
}

================================================
FILE: src/hooks/useColorPicker.ts
================================================
import tc from 'tinycolor2'
import { useState, useEffect } from 'react'
import { rgb2cmyk } from '../utils/converters.js'
import { ColorsProps, GradientProps, Config } from '../shared/types.js'
import { isUpperCase, getDetails, getColorObj } from '../utils/utils.js'
import { low, high, getColors, formatInputValues } from '../utils/formatters.js'

export const useColorPicker = (
  value: string,
  onChange: (arg0: string) => void,
  config?: Config
) => {
  const {
    defaultColor = 'rgba(175, 51, 242, 1)',
    defaultGradient = 'linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%)',
  } = config ?? {}

  let colors = getColors(value, defaultColor, defaultGradient)
  const { degrees, degreeStr, isGradient, gradientType } = getDetails(value)
  const { currentColor, selectedColor, currentLeft } = getColorObj(
    colors,
    defaultGradient
  )
  const [previousColors, setPreviousColors] = useState([])

  const getGradientObject = (currentValue: string) => {
    if (currentValue) {
      colors = getColors(currentValue, defaultColor, defaultGradient)
    }
    if (value) {
      if (isGradient) {
        return {
          isGradient: true,
          gradientType: gradientType,
          degrees: degreeStr,
          colors: colors?.map((c: ColorsProps) => ({
            ...c,
            value: c.value?.toLowerCase(),
          })),
        }
      } else {
        return {
          isGradient: false,
          gradientType: null,
          degrees: null,
          colors: colors?.map((c: ColorsProps) => ({
            ...c,
            value: c.value?.toLowerCase(),
          })),
        }
      }
    } else {
      console.log(
        'RBGCP ERROR - YOU MUST PASS A VALUE AND CALLBACK TO THE useColorPicker HOOK'
      )
    }
  }

  const tiny = tc(currentColor)
  const { r, g, b, a } = tiny.toRgb()
  const { h, s, l } = tiny.toHsl()

  useEffect(() => {
    if (tc(currentColor)?.isValid() && previousColors[0] !== currentColor) {
      // @ts-expect-error - currentColor type issue
      setPreviousColors([currentColor, ...previousColors.slice(0, 19)])
    }
  }, [currentColor, previousColors])

  const setLinear = () => {
    const remaining = value.split(/,(.+)/)[1]
    onChange(`linear-gradient(90deg, ${remaining}`)
  }

  const setRadial = () => {
    const remaining = value.split(/,(.+)/)[1]
    onChange(`radial-gradient(circle, ${remaining}`)
  }

  const setDegrees = (newDegrees: number) => {
    const remaining = value.split(/,(.+)/)[1]
    onChange(
      `linear-gradient(${formatInputValues(
        newDegrees,
        0,
        360
      )}deg, ${remaining}`
    )
    if (gradientType !== 'linear-gradient') {
      console.log(
        'Warning: you are updating degrees when the gradient type is not linear. This will change the gradients type which may be undesired'
      )
    }
  }

  const setSolid = (startingColor: string) => {
    const newValue = startingColor ?? defaultColor ?? 'rgba(175, 51, 242, 1)'
    onChange(newValue)
  }

  const setGradient = (startingGradiant: string) => {
    const newValue =
      startingGradiant ??
      defaultGradient ??
      'linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%)'
    onChange(newValue)
  }

  const createGradientStr = (newColors: GradientProps[]) => {
    const sorted = newColors.sort(
      (a: GradientProps, b: GradientProps) => a.left - b.left
    )
    const colorString = sorted?.map(
      (cc: ColorsProps) => `${cc?.value} ${cc.left}%`
    )
    onChange(`${gradientType}(${degreeStr}, ${colorString.join(', ')})`)
  }

  const handleGradient = (newColor: string, left?: number) => {
    const remaining = colors?.filter((c: ColorsProps) => !isUpperCase(c.value))
    const newColors = [
      { value: newColor.toUpperCase(), left: left ?? currentLeft },
      ...remaining,
    ]
    createGradientStr(newColors)
  }

  const handleChange = (newColor: string) => {
    newColor = newColor?.replace(/\s+/g, '')
    if (isGradient) {
      handleGradient(newColor)
    } else {
      onChange(newColor)
    }
  }

  const setR = (newR: number) => {
    const newVal = formatInputValues(newR, 0, 255)
    handleChange(`rgba(${newVal}, ${g}, ${b}, ${a})`)
  }

  const setG = (newG: number) => {
    const newVal = formatInputValues(newG, 0, 255)
    handleChange(`rgba(${r}, ${newVal}, ${b}, ${a})`)
  }

  const setB = (newB: number) => {
    const newVal = formatInputValues(newB, 0, 255)
    handleChange(`rgba(${r}, ${g}, ${newVal}, ${a})`)
  }

  const setA = (newA: number) => {
    const newVal = formatInputValues(newA, 0, 100)
    handleChange(`rgba(${r}, ${g}, ${b}, ${newVal / 100})`)
  }

  const setHue = (newHue: number) => {
    const newVal = formatInputValues(newHue, 0, 360)
    const tinyNew = tc({ h: newVal, s: s, l: l })
    const { r, g, b } = tinyNew.toRgb()
    handleChange(`rgba(${r}, ${g}, ${b}, ${a})`)
  }

  const setSaturation = (newSat: number) => {
    const newVal = formatInputValues(newSat, 0, 100)
    const tinyNew = tc({ h: h, s: newVal / 100, l: l })
    const { r, g, b } = tinyNew.toRgb()
    handleChange(`rgba(${r}, ${g}, ${b}, ${a})`)
  }

  const setLightness = (newLight: number) => {
    const newVal = formatInputValues(newLight, 0, 100)
    const tinyNew = tc({ h: h, s: s, l: newVal / 100 })
    if (tinyNew?.isValid()) {
      const { r, g, b } = tinyNew.toRgb()
      handleChange(`rgba(${r}, ${g}, ${b}, ${a})`)
    } else {
      console.log(
        'The new color was invalid, perhaps the lightness you passed in was a decimal? Please pass the new value between 0 - 100'
      )
    }
  }

  const valueToHSL = () => {
    return tiny.toHslString()
  }

  const valueToHSV = () => {
    return tiny.toHsvString()
  }

  const valueToHex = () => {
    return tiny.toHexString()
  }

  const valueToCmyk = () => {
    const { c, m, y, k } = rgb2cmyk(r, g, b)
    return `cmyk(${c}, ${m}, ${y}, ${k})`
  }

  const setSelectedPoint = (index: number) => {
    if (isGradient) {
      const newGradStr = colors?.map((cc: GradientProps, i: number) => ({
        ...cc,
        value: i === index ? high(cc) : low(cc),
      }))
      createGradientStr(newGradStr)
    } else {
      console.log(
        'This function is only relevant when the picker is in gradient mode'
      )
    }
  }

  const addPoint = (left: number) => {
    const newColors = [
      ...colors.map((c: GradientProps) => ({ ...c, value: low(c) })),
      { value: currentColor, left: left },
    ]
    createGradientStr(newColors)
    if (!left) {
      console.log(
        'You did not pass a stop value (left amount) for the new color point so it defaulted to 50'
      )
    }
  }

  const deletePoint = (index: number) => {
    if (colors?.length > 2) {
      const pointToDelete = index ?? selectedColor
      const remaining = colors?.filter(
        (rc: ColorsProps, i: number) => i !== pointToDelete
      )
      createGradientStr(remaining)
      if (!index) {
        console.log(
          'You did not pass in the index of the point you wanted to delete so the function default to the currently selected point'
        )
      }
    } else {
      console.log(
        'A gradient must have atleast two colors, disable your delete button when necessary'
      )
    }
  }

  const setPointLeft = (left: number) => {
    handleGradient(currentColor, formatInputValues(left, 0, 100))
  }

  const rgbaArr = [r, g, b, a]
  const hslArr = [h, s, l]

  return {
    setR,
    setG,
    setB,
    setA,
    setHue,
    addPoint,
    setSolid,
    setLinear,
    setRadial,
    valueToHSL,
    valueToHSV,
    valueToHex,
    valueToCmyk,
    setDegrees,
    setGradient,
    setLightness,
    setSaturation,
    setSelectedPoint,
    deletePoint,
    isGradient,
    gradientType,
    degrees,
    setPointLeft,
    currentLeft,
    rgbaArr,
    hslArr,
    handleChange,
    previousColors,
    getGradientObject,
    selectedPoint: selectedColor,
  }
}


================================================
FILE: src/hooks/usePaintHue.ts
================================================
import { useEffect, RefObject } from 'react'
import tinycolor from 'tinycolor2'

const usePaintHue = (
  canvas: RefObject<HTMLCanvasElement>,
  squareWidth: number
) => {
  useEffect(() => {
    const ctx = canvas?.current?.getContext('2d', { willReadFrequently: true })
    if (ctx) {
      ctx.rect(0, 0, squareWidth, 14)

      const gradient = ctx.createLinearGradient(0, 0, squareWidth, 0)
      for (let i = 0; i <= 360; i += 30) {
        gradient.addColorStop(i / 360, `hsl(${i}, 100%, 50%)`)
      }
      ctx.fillStyle = gradient
      ctx.fill()
    }
  }, [canvas, squareWidth])
}

export default usePaintHue

export const usePaintSat = (
  canvas: RefObject<HTMLCanvasElement>,
  h: number,
  l: number,
  squareWidth: number
) => {
  useEffect(() => {
    const ctx = canvas?.current?.getContext('2d', { willReadFrequently: true })
    if (ctx) {
      ctx.rect(0, 0, squareWidth, 14)

      const gradient = ctx.createLinearGradient(0, 0, squareWidth, 0)
      for (let i = 0; i <= 100; i += 10) {
        gradient.addColorStop(i / 100, `hsl(${h}, ${i}%, ${l}%)`)
      }
      ctx.fillStyle = gradient
      ctx.fill()
    }
  }, [canvas, h, l, squareWidth])
}

export const usePaintLight = (
  canvas: RefObject<HTMLCanvasElement>,
  h: number,
  s: number,
  squareWidth: number
) => {
  useEffect(() => {
    const ctx = canvas?.current?.getContext('2d', { willReadFrequently: true })
    if (ctx) {
      ctx.rect(0, 0, squareWidth, 14)

      const gradient = ctx.createLinearGradient(0, 0, squareWidth, 0)
      for (let i = 0; i <= 100; i += 10) {
        gradient.addColorStop(i / 100, `hsl(${h}, ${s}%, ${i}%)`)
      }
      ctx.fillStyle = gradient
      ctx.fill()
    }
  }, [canvas, h, s, squareWidth])
}

export const usePaintBright = (
  canvas: RefObject<HTMLCanvasElement>,
  h: number,
  s: number,
  squareWidth: number
) => {
  useEffect(() => {
    const ctx = canvas?.current?.getContext('2d', { willReadFrequently: true })
    if (ctx) {
      ctx.rect(0, 0, squareWidth, 14)

      const gradient = ctx.createLinearGradient(0, 0, squareWidth, 0)
      for (let i = 0; i <= 100; i += 10) {
        const hsl = tinycolor({ h: h, s: s, v: i })
        gradient.addColorStop(i / 100, hsl.toHslString())
      }
      ctx.fillStyle = gradient
      ctx.fill()
    }
  }, [canvas, h, s, squareWidth])
}


================================================
FILE: src/hooks/usePaintSquare.ts
================================================
import { useEffect, RefObject } from 'react'

const usePaintSquare = (
  canvas: RefObject<HTMLCanvasElement>,
  hue: number,
  squareWidth: number,
  squareHeight: number
) => {
  useEffect(() => {
    if (canvas.current) {
      const ctx = canvas.current.getContext('2d', { willReadFrequently: true })
      if (ctx) {
        ctx.fillStyle = `hsl(${hue}, 100%, 50%)`
        ctx.fillRect(0, 0, squareWidth, squareHeight)
        const gradientWhite = ctx.createLinearGradient(0, 0, squareWidth, 0)
        gradientWhite.addColorStop(0, `rgba(255, 255, 255, 1)`)
        gradientWhite.addColorStop(1, `rgba(255, 255, 255, 0)`)
        ctx.fillStyle = gradientWhite
        ctx.fillRect(0, 0, squareWidth, squareHeight)
        const gradientBlack = ctx.createLinearGradient(0, 0, 0, squareHeight)
        gradientBlack.addColorStop(0, `rgba(0, 0, 0, 0)`)
        gradientBlack.addColorStop(1, `rgba(0, 0, 0, 1)`)
        ctx.fillStyle = gradientBlack
        ctx.fillRect(0, 0, squareWidth, squareHeight)
      }
    }
  }, [canvas, hue, squareWidth, squareHeight])
}

export default usePaintSquare


================================================
FILE: src/index.ts
================================================
import { ColorPicker } from './components/index.js'
export { useColorPicker } from './hooks/useColorPicker.js'

export type {
  Styles,
  ColorsProps,
  PassedConfig,
  LocalesProps,
  GradientProps,
  ColorPickerProps,
} from './shared/types.js'

export default ColorPicker


================================================
FILE: src/shared/types.ts
================================================
export type ColorPickerProps = {
  idSuffix?: string
  value?: string
  onChange: (value: string) => void
  hideControls?: boolean
  hideInputs?: boolean
  hideOpacity?: boolean
  hidePresets?: boolean
  hideHue?: boolean
  presets?: string[]
  hideEyeDrop?: boolean
  hideAdvancedSliders?: boolean
  hideColorGuide?: boolean
  hideInputType?: boolean
  hideColorTypeBtns?: boolean
  hideGradientType?: boolean
  hideGradientAngle?: boolean
  hideGradientStop?: boolean
  hideGradientControls?: boolean
  width?: number
  height?: number
  style?: Styles
  className?: any
  locales?: LocalesProps
  disableDarkMode?: boolean
  disableLightMode?: boolean
  hidePickerSquare?: boolean
  showHexAlpha?: boolean
  config?: PassedConfig
}

export type ColorsProps = {
  value: string
  index?: number
  left?: number
}

export type GradientProps = {
  value: string
  index: number
  left: number
}

export type LocalesProps = {
  CONTROLS: controlsProps
}

type controlsProps = {
  SOLID: string
  GRADIENT: string
}

export type ThemeProps = {
  light: ThemeMode
  dark: ThemeMode
}

export type ThemeMode = {
  color?: string
  background?: string
  highlights?: string
  accent?: string
}

export type Styles = Partial<{
  body: React.CSSProperties
  rbgcpControlBtn: React.CSSProperties
  rbgcpControlIcon: React.CSSProperties
  rbgcpControlIconBtn: React.CSSProperties
  rbgcpControlBtnWrapper: React.CSSProperties
  rbgcpColorModelDropdown: React.CSSProperties
  rbgcpEyedropperCover: React.CSSProperties
  rbgcpControlInput: React.CSSProperties
  rbgcpInputLabel: React.CSSProperties
  rbgcpInput: React.CSSProperties
  rbgcpHandle: React.CSSProperties
  rbgcpCanvasWrapper: React.CSSProperties
  rbgcpCheckered: React.CSSProperties
  rbgcpOpacityOverlay: React.CSSProperties
  rbgcpGradientHandleWrap: React.CSSProperties
  rbgcpGradientHandle: React.CSSProperties
  rbgcpControlIcon2: React.CSSProperties
  rbgcpControlBtnSelected: React.CSSProperties
  rbgcpComparibleLabel: React.CSSProperties
  rbgcpColorModelDropdownBtn: React.CSSProperties
  rbgcpControlInputWrap: React.CSSProperties
  rbgcpStopInputWrap: React.CSSProperties
  rbgcpStopInput: React.CSSProperties
  rbgcpDegreeInputWrap: React.CSSProperties
  rbgcpDegreeInput: React.CSSProperties
  rbgcpDegreeIcon: React.CSSProperties
  rbgcpEyedropperBtn: React.CSSProperties
  rbgcpHexInput: React.CSSProperties
  rbgcpInputsWrap: React.CSSProperties
}>

export type PassedConfig = {
  barSize?: number
  crossSize?: number
  defaultColor?: string
  defaultGradient?: string
}

export type Config = {
  barSize: number
  crossSize: number
  defaultColor: string
  defaultGradient: string
}


================================================
FILE: src/styles/darkStyles.ts
================================================
export const darkStyles: Record<string, Record<string, string | number>> = {
    body: {
      background: 'rgb(32, 32, 32)',
    },
    rbgcpInputLabel: {
      color: 'rgb(212, 212, 212)',
    },
    rbgcpControlBtnWrapper: {
      background: 'rgb(54, 54, 54)',
    },
    rbgcpInput: {
      border: 'none',
      color: 'white',
      background: 'rgb(54, 54, 54)',
    },
    rbgcpControlBtn: {
      color: 'rgb(212, 212, 212)',
    },
    rbgcpControlIcon: {
      stroke: 'rgb(212, 212, 212)',
    },
    rbgcpControlIcon2: {
      fill: 'rgb(212, 212, 212)',
    },
    rbgcpControlInput: {
      color: 'white',
    },
    rbgcpControlBtnSelected: {
      background: 'black',
      color: '#568cf5',
    },
    rbgcpDegreeIcon: {
      color: 'rgb(212, 212, 212)',
    },
    rbgcpColorModelDropdown: {
      background: 'rgb(32, 32, 32)',
    },
    rbgcpComparibleLabel: {
      color: 'rgb(212, 212, 212)',
    }
}

================================================
FILE: src/styles/styles.ts
================================================
import { darkStyles } from './darkStyles.js';
import { Styles } from '../shared/types.js';

const styles: Styles = {
  body: {
    boxSizing: 'border-box',
    background: 'rgb(255, 255, 255)',
  },
  rbgcpControlBtn: {
    paddingLeft: '8px',
    paddingRight: '8px',
    lineHeight: '1',
    borderRadius: '4px',
    fontWeight: 700,
    fontSize: '12px',
    height: '24px',
    transition: 'all 160ms ease',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    background: 'rgba(255, 255, 255, 0)',
    boxShadow: '1px 1px 3px rgba(0, 0, 0, 0)',
    color: 'rgb(86, 86, 86)',
  },
  rbgcpControlIcon: {
    stroke: 'rgb(50, 49, 54)',
  },
  rbgcpControlIconBtn: {
    width: '30px',
    height: '24px',
    borderRadius: '4px',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    position: 'relative',
  },
  rbgcpControlBtnWrapper: {
    height: '28px',
    background: '#e9e9f5',
    borderRadius: '6px',
    padding: '2px',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    position: 'relative',
  },
  rbgcpColorModelDropdown: {
    position: 'absolute',
    right: '-2px',
    top: '34px',
    padding: '5px',
    background: '#e9e9f5',
    zIndex: 100000000,
    borderRadius: '6px',
    boxShadow: '1px 1px 14px 1px rgba(0, 0, 0, 0.25)',
  },
  rbgcpEyedropperCover: {
    position: 'fixed',
    left: '0px',
    top: '0px',
    zIndex: 100000000,
    width: '100vw',
    height: '100vh',
    cursor: 'copy',
  },
  rbgcpControlInput: {
    height: '24px',
    borderRadius: '4px',
    border: 'none',
    outline: 'none',
    textAlign: 'center',
    width: '34px',
    fontWeight: 500,
    color: 'rgb(50, 49, 54)',
    fontSize: '13px',
    background: 'transparent',
  },
  rbgcpInputLabel: {
    textAlign: 'center',
    lineHeight: '1.2',
    fontWeight: 700,
    color: 'rgb(86, 86, 86)',
    fontSize: '11px',
  },
  rbgcpInput: {
    height: '32px',
    borderRadius: '6px',
    border: '1px solid #bebebe',
    width: '100%',
    padding: '2px',
    outline: 'none',
    color: 'black',
    fontWeight: 400,
    textAlign: 'center',
    background: 'transparent',
    fontSize: '15px',
  },
  rbgcpHandle: {
    position: 'absolute',
    border: '2px solid white',
    borderRadius: '50%',
    boxShadow: '0px 0px 3px rgba(0, 0, 0, 0.5)',
    width: '18px',
    height: '18px',
    zIndex: 1000,
    transition: 'all 30ms linear',
    boxSizing: 'border-box',
    willChange: 'transform',
    outline: 'none',
  },
  rbgcpCanvasWrapper: {
    borderRadius: '6px',
    overflow: 'hidden',
    height: '294px',
  },
  rbgcpCheckered: {
    background: `linear-gradient(
      45deg,
      rgba(0, 0, 0, 0.18) 25%,
      transparent 25%,
      transparent 75%,
      rgba(0, 0, 0, 0.18) 75%,
      rgba(0, 0, 0, 0.18) 0
    ),
    linear-gradient(
      45deg,
      rgba(0, 0, 0, 0.18) 25%,
      transparent 25%,
      transparent 75%,
      rgba(0, 0, 0, 0.18) 75%,
      rgba(0, 0, 0, 0.18) 0
    ),
    white`,
    backgroundRepeat: 'repeat, repeat',
    backgroundPosition: '0px 0, 7px 7px',
    transformOrigin: '0 0 0',
    backgroundOrigin: 'padding-box, padding-box',
    backgroundClip: 'border-box, border-box',
    backgroundSize: '14px 14px, 14px 14px',
    boxShadow: 'none',
    textShadow: 'none',
    transition: 'none',
    transform: 'scaleX(1) scaleY(1) scaleZ(1)',
    borderRadius: '10px',
  },
  rbgcpOpacityOverlay: {
    position: 'absolute',
    left: '0px',
    top: '0px',
    width: '100%',
    height: '100%',
    borderRadius: '10px',
  },
  rbgcpGradientHandleWrap: {
    position: 'absolute',
    zIndex: 10000,
    top: '-2px',
    outline: 'none',
  },
  rbgcpGradientHandle: {
    border: '2px solid white',
    borderRadius: '50%',
    boxShadow: '0px 0px 3px rgba(0, 0, 0, 0.5)',
    width: '18px',
    height: '18px',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
  },
  rbgcpControlIcon2: {
    fill: '#323136',
  },
  rbgcpControlBtnSelected: {
    background: 'white',
    color: '#568cf5',
    boxShadow: '1px 1px 3px rgba(0, 0, 0, 0.2)',
  },
  rbgcpComparibleLabel: {
    color: '#323136',
  }
};

export const getStyles = (isDarkMode: boolean, passedStyles: Styles) => {
  const mergedStyles = { ...styles }

  if (isDarkMode) {
    for (const key in darkStyles) {
      if (Object.prototype.hasOwnProperty.call(darkStyles, key)) {
        ;(mergedStyles as Record<string, any>)[key] = {
          ...(Object.prototype.hasOwnProperty.call(mergedStyles, key)
            ? (mergedStyles as Record<string, any>)[key]
            : {}),
          ...(darkStyles as Record<string, any>)[key],
        }
      }
    }
  }

  for (const key in passedStyles) {
    if (Object.prototype.hasOwnProperty.call(passedStyles, key)) {
      ;(mergedStyles as Record<string, any>)[key] = {
        ...(Object.prototype.hasOwnProperty.call(mergedStyles, key)
          ? (mergedStyles as Record<string, any>)[key]
          : {}),
        ...(passedStyles as Record<string, any>)[key],
      }
    }
  }

  return mergedStyles
}

export const colorTypeBtnStyles = (selected: boolean, styles: Styles): React.CSSProperties => {
  if (selected) {
    return {...styles.rbgcpControlBtn, ...styles.rbgcpControlBtnSelected}
  } else {
    return { ...styles.rbgcpControlBtn }
  }
}

export const controlBtnStyles = (selected: boolean, styles: Styles): React.CSSProperties => {
  if (selected) {
    return { ...styles.rbgcpControlIconBtn, ...styles.rbgcpControlBtnSelected }
  } else {
    return { ...styles.rbgcpControlIconBtn }
  }
}

export const modalBtnStyles = (selected: boolean, styles: Styles): React.CSSProperties => {
  if (selected) {
    return { ...styles.rbgcpControlBtn, ...styles.rbgcpColorModelDropdownBtn, ...styles.rbgcpControlBtnSelected }
  } else {
    return { ...styles.rbgcpControlBtn, ...styles.rbgcpColorModelDropdownBtn }
  }
}

================================================
FILE: src/utils/converters.ts
================================================
export function rgb2cmyk(r: number, g: number, b: number) {
  let computedC = 0
  let computedM = 0
  let computedY = 0
  let computedK = 0

  if (
    r === null ||
    g === null ||
    b === null ||
    isNaN(r) ||
    isNaN(g) ||
    isNaN(b)
  ) {
    console.log('Please enter numeric RGB values!')
    return { c: 0, m: 0, k: 0, y: 1 }
  }
  if (r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255) {
    console.log('RGB values must be in the range 0 to 255.')
    return { c: 0, m: 0, k: 0, y: 1 }
  }

  if (r === 0 && g === 0 && b === 0) {
    computedK = 1
    return { c: 0, m: 0, k: 0, y: 1 }
  }

  computedC = 1 - r / 255
  computedM = 1 - g / 255
  computedY = 1 - b / 255

  const minCMY = Math.min(computedC, Math.min(computedM, computedY))
  computedC = (computedC - minCMY) / (1 - minCMY)
  computedM = (computedM - minCMY) / (1 - minCMY)
  computedY = (computedY - minCMY) / (1 - minCMY)
  computedK = minCMY

  return { c: computedC, m: computedM, y: computedY, k: computedK }
}

export const cmykToRgb = ({
  c,
  m,
  y,
  k,
}: {
  c: number
  m: number
  y: number
  k: number
}) => {
  const r = 255 * (1 - c) * (1 - k)
  const g = 255 * (1 - m) * (1 - k)
  const b = 255 * (1 - y) * (1 - k)

  return { r: r, g: g, b: b }
}

export const getHexAlpha = (opacityPercent: number): string => {
  if (typeof opacityPercent !== 'number') {
    return 'FF'
  }

  if (opacityPercent < 0) {
    return '00'
  }

  if (opacityPercent > 1) {
    return 'FF'
  }

  return Math.round(opacityPercent * 255)
    .toString(16)
    .padStart(2, '0')
    .toUpperCase()
}


================================================
FILE: src/utils/formatters.ts
================================================
import { ColorsProps } from '../shared/types.js'
import { gradientParser } from './gradientParser.js'

export const low = (color: ColorsProps) => {
  return color.value.toLowerCase()
}

export const high = (color: ColorsProps) => {
  return color.value.toUpperCase()
}

export const getColors = (value: string, defaultColor: string, defaultGradient: string) => {
  const isGradient = value?.includes('gradient')
  if (isGradient) {
    const isConic = value?.includes('conic')
    const safeValue = !isConic ? value : defaultGradient
    if (isConic) {
      console.log('Sorry we cant handle conic gradients yet')
    }
    const obj = gradientParser(safeValue)
    return obj?.colorStops
  } else {
    const safeValue = value || defaultColor
    return [{ value: safeValue }]
  }
}

export const formatInputValues = (value: number, min: number, max: number) => {
  return isNaN(value) ? min : value < min ? min : value > max ? max : value
}

export const round = (val: number) => {
  return Math.round(val)
}


================================================
FILE: src/utils/gradientParser.ts
================================================
import { high, low } from './formatters.js'
import { isUpperCase } from './utils.js'
import tinycolor from 'tinycolor2'

export const gradientParser = (input = '') => {
  const tokens = {
    linearGradient: /^(-(webkit|o|ms|moz)-)?(linear-gradient)/i,
    repeatingLinearGradient:
      /^(-(webkit|o|ms|moz)-)?(repeating-linear-gradient)/i,
    radialGradient: /^(-(webkit|o|ms|moz)-)?(radial-gradient)/i,
    repeatingRadialGradient:
      /^(-(webkit|o|ms|moz)-)?(repeating-radial-gradient)/i,
    sideOrCorner:
      /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i,
    extentKeywords:
      /^(closest-side|closest-corner|farthest-side|farthest-corner|contain|cover)/,
    positionKeywords: /^(left|center|right|top|bottom)/i,
    pixelValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))px/,
    percentageValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))%/,
    emValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))em/,
    angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/,
    startCall: /^\(/,
    endCall: /^\)/,
    comma: /^,/,
    hexColor: /^#([0-9a-fA-F]+)/,
    literalColor: /^([a-zA-Z]+)/,
    rgbColor: /^rgb/i,
    spacedRgbColor: /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s+\/\s+([0-1](\.\d+)?)/,
    rgbaColor: /^rgba/i,
    hslColor: /^hsl/i,
    hsvColor: /^hsv/i,
    number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/,
  }

  function error(msg: any) {
    const err = new Error(input + ': ' + msg)
    // err.source = input
    throw err
  }

  function consume(size: any) {
    input = input.substr(size)
  }

  function scan(regexp: any) {
    const blankCaptures = /^[\n\r\t\s]+/.exec(input)
    if (blankCaptures) {
      consume(blankCaptures[0].length)
    }

    const captures = regexp.exec(input)
    if (captures) {
      consume(captures[0].length)
    }

    return captures
  }

  function matchListing(matcher: any) {
    let captures = matcher()
    const result = []

    if (captures) {
      result.push(captures)
      while (scan(tokens.comma)) {
        captures = matcher()
        if (captures) {
          result.push(captures)
        } else {
          error('One extra comma')
        }
      }
    }

    return result
  }

  function match(type: any, pattern: any, captureIndex: any) {
    const captures = scan(pattern)
    if (captures) {
      return {
        type: type,
        value: captures[captureIndex],
      }
    }
  }

  function matchHexColor() {
    const hexObj = match('hex', tokens.hexColor, 1)
    if (hexObj?.value) {
      const { r, g, b, a } = tinycolor(hexObj?.value).toRgb()
      return {
        value: `rgba(${r}, ${g}, ${b}, ${a})`,
      }
    }
  }

  const checkCaps = (val: any) => {
    const capIt = isUpperCase(val?.[0])
    return {
      value: `${capIt ? 'RGBA' : 'rgba'}(${matchListing(matchNumber)})`,
    }
  }

  function matchCall(pattern: any, callback: any) {
    const captures = scan(pattern)

    if (captures) {
      if (!scan(tokens.startCall)) {
        error('Missing (')
      }

      const result = callback(captures)

      if (!scan(tokens.endCall)) {
        error('Missing )')
      }

      return result
    }
  }

  function matchHSLColor() {
    return matchCall(tokens.hslColor, convertHsl)
  }

  function matchRGBAColor() {
    return matchCall(tokens.rgbaColor, checkCaps)
  }

  function matchRGBColor() {
    return matchCall(tokens.rgbColor, convertRgb)
  }

  function matchLiteralColor() {
    const litObj = match('literal', tokens.literalColor, 0)
    if (litObj?.value) {
      const { r, g, b, a } = tinycolor(litObj?.value).toRgb()
      return {
        value: `rgba(${r}, ${g}, ${b}, ${a})`,
      }
    }
  }

  function matchHSVColor() {
    return matchCall(tokens.hsvColor, convertHsv)
  }

  function matchColor() {
    return (
      matchHexColor() ||
      matchHSLColor() ||
      matchRGBAColor() ||
      matchRGBColor() ||
      matchLiteralColor() ||
      matchHSVColor()
    )
  }

  function matchColorStop() {
    const color = matchColor()

    if (!color) {
      error('Expected color definition')
    }

    color.left = parseInt(matchDistance()?.value)
    return color
  }

  function matchGradient(
    gradientType: any,
    pattern: any,
    orientationMatcher: any
  ) {
    return matchCall(pattern, function () {
      const orientation = orientationMatcher()
      if (orientation) {
        if (!scan(tokens.comma)) {
          error('Missing comma before color stops')
        }
      }

      return {
        type: gradientType,
        orientation: orientation,
        colorStops: matchListing(matchColorStop),
      }
    })
  }

  function matchLinearOrientation() {
    return matchSideOrCorner() || matchAngle()
  }

  function matchDefinition() {
    return (
      matchGradient(
        'linear-gradient',
        tokens.linearGradient,
        matchLinearOrientation
      ) ||
      matchGradient(
        'repeating-linear-gradient',
        tokens.repeatingLinearGradient,
        matchLinearOrientation
      ) ||
      matchGradient(
        'radial-gradient',
        tokens.radialGradient,
        matchListRadialOrientations
      ) ||
      matchGradient(
        'repeating-radial-gradient',
        tokens.repeatingRadialGradient,
        matchListRadialOrientations
      )
    )
  }

  function matchListDefinitions() {
    return matchListing(matchDefinition)
  }

  function getAST() {
    const ast = matchListDefinitions()

    if (input.length > 0) {
      error('Invalid input not EOF')
    }

    const ast0 = ast[0]
    const checkSelected = ast0?.colorStops?.filter((c: any) =>
      isUpperCase(c.value)
    ).length

    const getGradientObj = () => {
      if (checkSelected > 0) {
        return ast0
      } else {
        const val = (c: any, i: number) => (i === 0 ? high(c) : low(c))
        return {
          ...ast0,
          colorStops: ast0.colorStops.map((c: any, i: number) => ({
            ...c,
            value: val(c, i),
          })),
        }
      }
    }

    return getGradientObj()
  }

  function matchSideOrCorner() {
    return match('directional', tokens.sideOrCorner, 1)
  }

  function matchAngle() {
    return match('angular', tokens.angleValue, 1)
  }

  function matchListRadialOrientations() {
    var radialOrientations,
      radialOrientation = matchRadialOrientation(),
      lookaheadCache

    if (radialOrientation) {
      radialOrientations = []
      radialOrientations.push(radialOrientation)

      lookaheadCache = input
      if (scan(tokens.comma)) {
        radialOrientation = matchRadialOrientation()
        if (radialOrientation) {
          radialOrientations.push(radialOrientation)
        } else {
          input = lookaheadCache
        }
      }
    }

    return radialOrientations
  }

  function matchRadialOrientation() {
    let radialType = matchCircle() || matchEllipse()

    if (radialType) {
      // @ts-expect-error - need to circle back for these types
      radialType.at = matchAtPosition()
    } else {
      const extent = matchExtentKeyword()
      if (extent) {
        radialType = extent
        const positionAt = matchAtPosition()
        if (positionAt) {
          // @ts-expect-error - need to circle back for these types
          radialType.at = positionAt
        }
      } else {
        const defaultPosition = matchPositioning()
        if (defaultPosition) {
          radialType = {
            type: 'default-radial',
            // @ts-expect-error - need to circle back for these types
            at: defaultPosition,
          }
        }
      }
    }

    return radialType
  }

  function matchLength() {
    return match('px', tokens.pixelValue, 1) || match('em', tokens.emValue, 1)
  }

  function matchCircle() {
    const circle = match('shape', /^(circle)/i, 0)

    if (circle) {
      // @ts-expect-error - need to circle back for these types
      circle.style = matchLength() || matchExtentKeyword()
    }

    return circle
  }

  function matchEllipse() {
    const ellipse = match('shape', /^(ellipse)/i, 0)

    if (ellipse) {
      // @ts-expect-error - need to circle back for these types
      ellipse.style = matchDistance() || matchExtentKeyword()
    }

    return ellipse
  }

  function matchExtentKeyword() {
    return match('extent-keyword', tokens.extentKeywords, 1)
  }

  function matchAtPosition() {
    if (match('position', /^at/, 0)) {
      const positioning = matchPositioning()

      if (!positioning) {
        error('Missing positioning value')
      }

      return positioning
    }
  }

  function matchPositioning() {
    const location = matchCoordinates()

    if (location.x || location.y) {
      return {
        type: 'position',
        value: location,
      }
    }
  }

  function matchCoordinates() {
    return {
      x: matchDistance(),
      y: matchDistance(),
    }
  }

  function matchNumber() {
    return scan(tokens.number)[1]
  }

  const convertHsl = (val: any) => {
    const capIt = isUpperCase(val?.[0])
    const hsl = matchListing(matchNumber)
    const { r, g, b, a } = tinycolor({
      h: hsl[0],
      s: hsl[1],
      l: hsl[2],
      a: hsl[3] || 1,
    }).toRgb()
    return {
      value: `${capIt ? 'RGBA' : 'rgba'}(${r}, ${g}, ${b}, ${a})`,
    }
  }

  const convertHsv = (val: any) => {
    const capIt = isUpperCase(val?.[0])
    const hsv = matchListing(matchNumber)
    const { r, g, b, a } = tinycolor({
      h: hsv[0],
      s: hsv[1],
      v: hsv[2],
      a: hsv[3] || 1,
    }).toRgb()
    return {
      value: `${capIt ? 'RGBA' : 'rgba'}(${r}, ${g}, ${b}, ${a})`,
    }
  }

  const convertRgb = (val: any) => {
    const capIt = isUpperCase(val?.[0])
    const captures = scan(tokens.spacedRgbColor)
    const [, r, g, b, a = 1] = captures || [null, ...matchListing(matchNumber)]
    return {
      value: `${capIt ? 'RGBA' : 'rgba'}(${r}, ${g}, ${b}, ${a})`,
    }
  }

  function matchDistance() {
    return (
      match('%', tokens.percentageValue, 1) ||
      matchPositionKeyword() ||
      matchLength()
    )
  }

  function matchPositionKeyword() {
    return match('position-keyword', tokens.positionKeywords, 1)
  }

  return getAST()
}


================================================
FILE: src/utils/utils.ts
================================================
import { formatInputValues } from './formatters.js'
import { ColorsProps } from '../shared/types.js'

export const safeBounds = (e: any) => {
  const client = e.target.parentNode.getBoundingClientRect()
  const className = e.target.className
  const adjuster = className === 'c-resize ps-rl' ? 15 : 0
  return {
    offsetLeft: client?.x + adjuster,
    offsetTop: client?.y,
    clientWidth: client?.width,
    clientHeight: client?.height,
  }
}

export function getHandleValue(e: any, barSize: number) {
  const { offsetLeft, clientWidth } = safeBounds(e)
  const pos = e.clientX - offsetLeft - barSize / 2
  const adjuster = clientWidth - 18
  const bounded = formatInputValues(pos, 0, adjuster)
  return Math.round(bounded / (adjuster / 100))
}

export function computeSquareXY(
  s: number,
  v: number,
  squareWidth: number,
  squareHeight: number,
  crossSize: number
) {
  const x = s * squareWidth - crossSize / 2
  const y = ((100 - v) / 100) * squareHeight - crossSize / 2
  return [x, y]
}

const getClientXY = (e: any) => {
  if (e.clientX) {
    return { clientX: e.clientX, clientY: e.clientY }
  } else {
    const touch = e.touches[0] || {}
    return { clientX: touch.clientX, clientY: touch.clientY }
  }
}

export function computePickerPosition(e: any, crossSize: number) {
  const { offsetLeft, offsetTop, clientWidth, clientHeight } = safeBounds(e)
  const { clientX, clientY } = getClientXY(e)

  const getX = () => {
    const xPos = clientX - offsetLeft - crossSize / 2
    return formatInputValues(xPos, -9, clientWidth - 10)
  }
  const getY = () => {
    const yPos = clientY - offsetTop - crossSize / 2
    return formatInputValues(yPos, -9, clientHeight - 10)
  }

  return [getX(), getY()]
}

// export const getGradientType = (value: string) => {
//   return value?.split('(')[0]
// }

export const isUpperCase = (str: string) => {
  return str?.[0] === str?.[0]?.toUpperCase()
}

// export const compareGradients = (g1: string, g2: string) => {
//   const ng1 = g1?.toLowerCase()?.replaceAll(' ', '')
//   const ng2 = g2?.toLowerCase()?.replaceAll(' ', '')
//   if (ng1 === ng2) {
//     return true
//   } else {
//     return false
//   }
// }

const convertShortHandDeg = (dir: any) => {
  if (dir === 'to top') {
    return 0
  } else if (dir === 'to bottom') {
    return 180
  } else if (dir === 'to left') {
    return 270
  } else if (dir === 'to right') {
    return 90
  } else if (dir === 'to top right') {
    return 45
  } else if (dir === 'to bottom right') {
    return 135
  } else if (dir === 'to bottom left') {
    return 225
  } else if (dir === 'to top left') {
    return 315
  } else {
    const safeDir = dir || 0
    return parseInt(safeDir)
  }
}

export const objectToString = (value: any) => {
  if (typeof value === 'string') {
    return value
  } else {
    if (value?.type?.includes('gradient')) {
      const sorted = value?.colorStops?.sort(
        (a: any, b: any) => a?.left - b?.left
      )
      const string = sorted
        ?.map((c: any) => `${c?.value} ${c?.left}%`)
        ?.join(', ')
      const type = value?.type
      const degs = convertShortHandDeg(value?.orientation?.value)
      const gradientStr = type === 'linear-gradient' ? `${degs}deg` : 'circle'
      return `${type}(${gradientStr}, ${string})`
    } else {
      const color = value?.colorStops[0]?.value || 'rgba(175, 51, 242, 1)'
      return color
    }
  }
}

export const getColorObj = (colors: ColorsProps[], defaultGradient: string) => {
  const idxCols = colors?.map((c: ColorsProps, i: number) => ({
    ...c,
    index: i,
  }))

  const upperObj = idxCols?.find((c: ColorsProps) => isUpperCase(c.value))
  const ccObj = upperObj || idxCols[0]

  return {
    currentColor: ccObj?.value || defaultGradient,
    selectedColor: ccObj?.index || 0,
    currentLeft: ccObj?.left || 0,
  }
}

const getDegrees = (value: string) => {
  const s1 = value?.split(',')[0]
  const s2 = s1?.split('(')[1]?.replace('deg', '')
  return convertShortHandDeg(s2)
}

export const getDetails = (value: string) => {
  const isGradient = value?.includes('gradient')
  const gradientType = value?.split('(')[0]
  const degrees = getDegrees(value)
  const degreeStr =
    gradientType === 'linear-gradient' ? `${degrees}deg` : 'circle'

  return {
    degrees,
    degreeStr,
    isGradient,
    gradientType,
  }
}


================================================
FILE: test/gradientParser.spec.js
================================================
import { gradientParser } from '../src/utils/gradientParser'
import { describe, expect, it } from 'vitest'

describe('gradientParser', () => {
  it('should parse linear gradient with hex colors', () => {
    const gradient = 'linear-gradient(45deg, #012345 0%, #6789AB 100%)'

    expect(gradientParser(gradient)).toEqual({
      colorStops: [
        { left: 0, type: 'hex', value: '012345' },
        { left: 100, type: 'hex', value: '6789AB' },
      ],
      orientation: { type: 'angular', value: '45' },
      type: 'linear-gradient',
    })
  })

  it('should parse linear gradient with comma-separated rgba colors', () => {
    const gradient =
      'linear-gradient(45deg, rgba(1, 2, 3, 0.123) 0%, rgba(4, 5, 6, 0.456) 100%)'

    expect(gradientParser(gradient)).toEqual({
      colorStops: [
        { left: 0, value: 'RGBA(1,2,3,0.123)' },
        { left: 100, value: 'rgba(4,5,6,0.456)' },
      ],
      orientation: { type: 'angular', value: '45' },
      type: 'linear-gradient',
    })
  })

  it('should parse linear gradient with rgb + alpha colors', () => {
    const gradient =
      'linear-gradient(45deg, rgb(1 2 3 / 0.123) 0%, rgb(4 5 6 / 0.456) 100%)'

    expect(gradientParser(gradient)).toEqual({
      colorStops: [
        { left: 0, value: 'RGBA(1, 2, 3, 0.123)' },
        { left: 100, value: 'rgba(4, 5, 6, 0.456)' },
      ],
      orientation: { type: 'angular', value: '45' },
      type: 'linear-gradient',
    })
  })

  it('should parse linear gradient with comma-separated rgb colors', () => {
    const gradient =
      'linear-gradient(45deg, rgb(1, 2, 3) 0%, rgb(4, 5, 6) 100%)'

    expect(gradientParser(gradient)).toEqual({
      colorStops: [
        { left: 0, value: 'RGBA(1, 2, 3, 1)' },
        { left: 100, value: 'rgba(4, 5, 6, 1)' },
      ],
      orientation: { type: 'angular', value: '45' },
      type: 'linear-gradient',
    })
  })

  // TODO: Figure out pattern of "HSV" colors

  // TODO: Make it accept percentages
  // it('should parse linear gradient with hsl colors', () => {
  //   const gradient = 'linear-gradient(45deg, hsl(0, 100%, 50%) 0%, hsl(100, 50%, 85%) 100%)'
  //
  //   expect(gradientParser(gradient)).toEqual({})
  // })
})


================================================
FILE: test/utils.spec.js
================================================
import {
  isUpperCase,
  getNewHsl,
  getGradientType,
  getDegrees,
} from '../src/utils/utils'
import { describe, expect, it } from 'vitest'

describe('isUpperCase', () => {
  it('should return true when the first letter of the string is upper-cased', () => {
    expect(isUpperCase('Aloha oe')).toBe(true)
  })

  it('should return false when the first letter of the string is lower-cased', () => {
    expect(isUpperCase('aLOHA OE')).toBe(false)
  })
})

describe('getNewHsl', () => {
  it('should return correct RGBA color for given HSL color', () => {
    const callback = () => {}
    const output = getNewHsl(116, 79, 19, 0.5, callback)

    expect(output).toEqual('rgba(15, 87, 10, 0.5)')
  })

  it('should trigger callback with correct arguments', () => {
    const callback = jest.fn()
    getNewHsl(116, 79, 19, 0.5, callback)

    expect(callback).toHaveBeenCalledWith(116)
  })
})

describe('getGradientType', () => {
  it('should pick the correct prefix of gradient values', () => {
    const assertionMap = [
      ['linear-gradient(30deg, #6789AB 0%, #012345 100%)', 'linear-gradient'],
      [
        '-webkit-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        '-webkit-linear-gradient',
      ],
      [
        '-o-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        '-o-linear-gradient',
      ],
      [
        '-ms-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        '-ms-linear-gradient',
      ],
      [
        '-moz-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        '-moz-linear-gradient',
      ],

      [
        'repeating-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        'repeating-linear-gradient',
      ],
      [
        '-webkit-repeating-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        '-webkit-repeating-linear-gradient',
      ],
      [
        '-o-repeating-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        '-o-repeating-linear-gradient',
      ],
      [
        '-ms-repeating-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        '-ms-repeating-linear-gradient',
      ],
      [
        '-moz-repeating-linear-gradient(30deg, #6789AB 0%, #012345 100%)',
        '-moz-repeating-linear-gradient',
      ],

      ['radial-gradient(#6789AB 0%, #012345 100%)', 'radial-gradient'],
      [
        '-webkit-radial-gradient(#6789AB 0%, #012345 100%)',
        '-webkit-radial-gradient',
      ],
      ['-o-radial-gradient(#6789AB 0%, #012345 100%)', '-o-radial-gradient'],
      ['-ms-radial-gradient(#6789AB 0%, #012345 100%)', '-ms-radial-gradient'],
      [
        '-moz-radial-gradient(#6789AB 0%, #012345 100%)',
        '-moz-radial-gradient',
      ],

      [
        'repeating-radial-gradient(#6789AB 0%, #012345 100%)',
        'repeating-radial-gradient',
      ],
      [
        '-webkit-repeating-radial-gradient(#6789AB 0%, #012345 100%)',
        '-webkit-repeating-radial-gradient',
      ],
      [
        '-o-repeating-radial-gradient(#6789AB 0%, #012345 100%)',
        '-o-repeating-radial-gradient',
      ],
      [
        '-ms-repeating-radial-gradient(#6789AB 0%, #012345 100%)',
        '-ms-repeating-radial-gradient',
      ],
      [
        '-moz-repeating-radial-gradient(#6789AB 0%, #012345 100%)',
        '-moz-repeating-radial-gradient',
      ],
    ]
    const outputs = []
    const expected = []

    assertionMap.forEach(([value, expectedValue]) => {
      outputs.push(getGradientType(value))
      expected.push(expectedValue)
    })

    expect(outputs).toEqual(expected)
  })
})

describe('getDegrees', () => {
  it('should pick the correct degree from linear gradient values', () => {
    const assertionMap = [
      ['linear-gradient(0deg, #6789AB 0%, #012345 100%)', 0],
      ['-webkit-linear-gradient(1deg, #6789AB 0%, #012345 100%)', 1],
      ['-o-linear-gradient(2deg, #6789AB 0%, #012345 100%)', 2],
      ['-ms-linear-gradient(3deg, #6789AB 0%, #012345 100%)', 3],
      ['-moz-linear-gradient(4deg, #6789AB 0%, #012345 100%)', 4],

      ['repeating-linear-gradient(5deg, #6789AB 0%, #012345 100%)', 5],
      ['-webkit-repeating-linear-gradient(6deg, #6789AB 0%, #012345 100%)', 6],
      ['-o-repeating-linear-gradient(7deg, #6789AB 0%, #012345 100%)', 7],
      ['-ms-repeating-linear-gradient(8deg, #6789AB 0%, #012345 100%)', 8],
      ['-moz-repeating-linear-gradient(9deg, #6789AB 0%, #012345 100%)', 9],
    ]
    const outputs = []
    const expected = []

    assertionMap.forEach(([value, expectedValue]) => {
      outputs.push(getDegrees(value))
      expected.push(expectedValue)
    })

    expect(outputs).toEqual(expected)
  })
})


================================================
FILE: tsconfig.build.json
================================================
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": false,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"],
  "exclude": ["src/**/*.spec.ts", "src/**/*.spec.tsx"]
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "allowJs": true,
    "declaration": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "jsx": "react",
    "module": "nodenext",
    "noEmit": true,
    "noUncheckedIndexedAccess": true,
    "outDir": "dist",
    "skipLibCheck": true,
    "strict": true,
    "target": "es5",
    "lib": ["es2015", "dom"],
    "downlevelIteration": false,
  },
  "exclude": ["dist"]
}


Download .txt
gitextract__r_hbhn9/

├── .github/
│   └── workflows/
│       ├── pull-request.yml
│       └── release-package.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .prettierrc
├── LICENSE
├── README.md
├── biome.json
├── package.json
├── src/
│   ├── components/
│   │   ├── AdvancedControls.tsx
│   │   ├── ComparibleColors.tsx
│   │   ├── Controls.tsx
│   │   ├── EyeDropper.tsx
│   │   ├── GradientBar.tsx
│   │   ├── GradientControls.tsx
│   │   ├── Hue.tsx
│   │   ├── Inputs.tsx
│   │   ├── Opacity.tsx
│   │   ├── Picker.tsx
│   │   ├── Portal.tsx
│   │   ├── Presets.tsx
│   │   ├── Square.tsx
│   │   ├── icon.tsx
│   │   └── index.tsx
│   ├── constants.ts
│   ├── context.tsx
│   ├── hooks/
│   │   ├── useColorPicker.ts
│   │   ├── usePaintHue.ts
│   │   └── usePaintSquare.ts
│   ├── index.ts
│   ├── shared/
│   │   └── types.ts
│   ├── styles/
│   │   ├── darkStyles.ts
│   │   └── styles.ts
│   └── utils/
│       ├── converters.ts
│       ├── formatters.ts
│       ├── gradientParser.ts
│       └── utils.ts
├── test/
│   ├── gradientParser.spec.js
│   └── utils.spec.js
├── tsconfig.build.json
└── tsconfig.json
Download .txt
SYMBOL INDEX (55 symbols across 9 files)

FILE: src/components/GradientBar.tsx
  function force90degLinear (line 110) | function force90degLinear(color: string) {

FILE: src/components/Picker.tsx
  type PickerProps (line 61) | type PickerProps = {

FILE: src/components/icon.tsx
  type ColorProps (line 4) | type ColorProps = {

FILE: src/components/index.tsx
  function ColorPicker (line 10) | function ColorPicker({

FILE: src/context.tsx
  function PickerContextWrapper (line 15) | function PickerContextWrapper({
  function usePicker (line 135) | function usePicker() {
  type PCWProps (line 145) | type PCWProps = {
  type PickerContextProps (line 159) | type PickerContextProps = {

FILE: src/shared/types.ts
  type ColorPickerProps (line 1) | type ColorPickerProps = {
  type ColorsProps (line 32) | type ColorsProps = {
  type GradientProps (line 38) | type GradientProps = {
  type LocalesProps (line 44) | type LocalesProps = {
  type controlsProps (line 48) | type controlsProps = {
  type ThemeProps (line 53) | type ThemeProps = {
  type ThemeMode (line 58) | type ThemeMode = {
  type Styles (line 65) | type Styles = Partial<{
  type PassedConfig (line 97) | type PassedConfig = {
  type Config (line 104) | type Config = {

FILE: src/utils/converters.ts
  function rgb2cmyk (line 1) | function rgb2cmyk(r: number, g: number, b: number) {

FILE: src/utils/gradientParser.ts
  function error (line 35) | function error(msg: any) {
  function consume (line 41) | function consume(size: any) {
  function scan (line 45) | function scan(regexp: any) {
  function matchListing (line 59) | function matchListing(matcher: any) {
  function match (line 78) | function match(type: any, pattern: any, captureIndex: any) {
  function matchHexColor (line 88) | function matchHexColor() {
  function matchCall (line 105) | function matchCall(pattern: any, callback: any) {
  function matchHSLColor (line 123) | function matchHSLColor() {
  function matchRGBAColor (line 127) | function matchRGBAColor() {
  function matchRGBColor (line 131) | function matchRGBColor() {
  function matchLiteralColor (line 135) | function matchLiteralColor() {
  function matchHSVColor (line 145) | function matchHSVColor() {
  function matchColor (line 149) | function matchColor() {
  function matchColorStop (line 160) | function matchColorStop() {
  function matchGradient (line 171) | function matchGradient(
  function matchLinearOrientation (line 192) | function matchLinearOrientation() {
  function matchDefinition (line 196) | function matchDefinition() {
  function matchListDefinitions (line 221) | function matchListDefinitions() {
  function getAST (line 225) | function getAST() {
  function matchSideOrCorner (line 255) | function matchSideOrCorner() {
  function matchAngle (line 259) | function matchAngle() {
  function matchListRadialOrientations (line 263) | function matchListRadialOrientations() {
  function matchRadialOrientation (line 286) | function matchRadialOrientation() {
  function matchLength (line 316) | function matchLength() {
  function matchCircle (line 320) | function matchCircle() {
  function matchEllipse (line 331) | function matchEllipse() {
  function matchExtentKeyword (line 342) | function matchExtentKeyword() {
  function matchAtPosition (line 346) | function matchAtPosition() {
  function matchPositioning (line 358) | function matchPositioning() {
  function matchCoordinates (line 369) | function matchCoordinates() {
  function matchNumber (line 376) | function matchNumber() {
  function matchDistance (line 417) | function matchDistance() {
  function matchPositionKeyword (line 425) | function matchPositionKeyword() {

FILE: src/utils/utils.ts
  function getHandleValue (line 16) | function getHandleValue(e: any, barSize: number) {
  function computeSquareXY (line 24) | function computeSquareXY(
  function computePickerPosition (line 45) | function computePickerPosition(e: any, crossSize: number) {
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (162K chars).
[
  {
    "path": ".github/workflows/pull-request.yml",
    "chars": 523,
    "preview": "name: Run tests\n\non: pull_request\n\njobs:\n  run-tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checko"
  },
  {
    "path": ".github/workflows/release-package.yml",
    "chars": 1017,
    "preview": "name: Release Package\n\non:\n  release:\n    types: [created]\n\njobs:\n  run-tests:\n    runs-on: ubuntu-latest\n    steps:\n   "
  },
  {
    "path": ".gitignore",
    "chars": 389,
    "preview": "# OS\n.DS_Store\n\n# Cache\n.cache\n.playwright\n.tmp\n*.tsbuildinfo\n.eslintcache\n\n# Yarn\n.pnp.*\n**/.yarn/*\n!**/.yarn/patches\n!"
  },
  {
    "path": ".npmignore",
    "chars": 153,
    "preview": "/*.log\r\n/*.DS_Store\r\n/.idea\r\n/.gitignore\r\n/node_modules/\r\n/.github/\r\n/src/\r\n/babel.config.js\r\n**/*.spec.js\r\n/.eslintigno"
  },
  {
    "path": ".nvmrc",
    "chars": 4,
    "preview": "v16\n"
  },
  {
    "path": ".prettierrc",
    "chars": 112,
    "preview": "{\r\n\t\"endOfLine\": \"lf\",\r\n\t\"semi\": false,\r\n\t\"singleQuote\": true,\r\n\t\"useTabs\": false,\r\n\t\"trailingComma\": \"es5\"\r\n}\r\n"
  },
  {
    "path": "LICENSE",
    "chars": 1066,
    "preview": "MIT License\n\nCopyright (c) 2022 Harry Fox\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
  },
  {
    "path": "README.md",
    "chars": 21601,
    "preview": "[![Npm Version][npm-version-image]][npm-version-url]\n[![Downloads][downloads-image]][downloads-url]\n[![License][license-"
  },
  {
    "path": "biome.json",
    "chars": 1002,
    "preview": "{\n  \"$schema\": \"https://biomejs.dev/schemas/1.8.3/schema.json\",\n  \"files\": {\n    \"ignore\": [\n      \".tsimp\",\n      \".yar"
  },
  {
    "path": "package.json",
    "chars": 2718,
    "preview": "{\n  \"name\": \"react-best-gradient-color-picker\",\n  \"version\": \"3.0.14\",\n  \"description\": \"An easy to use color/gradient p"
  },
  {
    "path": "src/components/AdvancedControls.tsx",
    "chars": 5965,
    "preview": "import React, { useState, useRef, useEffect } from 'react'\nimport { Styles, Config } from '../shared/types.js'\nimport { "
  },
  {
    "path": "src/components/ComparibleColors.tsx",
    "chars": 5672,
    "preview": "import React from 'react'\nimport { usePicker } from '../context.js'\n\nconst ComparibleColors = ({\n  openComparibles,\n}: {"
  },
  {
    "path": "src/components/Controls.tsx",
    "chars": 8648,
    "preview": "/* eslint-disable react/jsx-no-leaked-render */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport Reac"
  },
  {
    "path": "src/components/EyeDropper.tsx",
    "chars": 4331,
    "preview": "/* eslint-disable react/jsx-no-leaked-render */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport Reac"
  },
  {
    "path": "src/components/GradientBar.tsx",
    "chars": 4973,
    "preview": "/* eslint-disable react/no-array-index-key */\n/* eslint-disable react/jsx-no-leaked-render */\n/* eslint-disable jsx-a11y"
  },
  {
    "path": "src/components/GradientControls.tsx",
    "chars": 5847,
    "preview": "import React from 'react'\nimport { usePicker } from '../context.js'\nimport { formatInputValues, low, high } from '../uti"
  },
  {
    "path": "src/components/Hue.tsx",
    "chars": 2606,
    "preview": "import React, { useRef, useState, useEffect } from 'react'\nimport { usePicker } from '../context.js'\nimport usePaintHue "
  },
  {
    "path": "src/components/Inputs.tsx",
    "chars": 10643,
    "preview": "import React, { useState, useEffect } from 'react'\nimport { Styles } from '../shared/types.js'\nimport { formatInputValue"
  },
  {
    "path": "src/components/Opacity.tsx",
    "chars": 2300,
    "preview": "/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React, { useState, useEffect } from 'react'\nimport {"
  },
  {
    "path": "src/components/Picker.tsx",
    "chars": 2091,
    "preview": "import React from 'react'\nimport Hue from './Hue.js'\nimport Inputs from './Inputs.js'\nimport Square from './Square.js'\ni"
  },
  {
    "path": "src/components/Portal.tsx",
    "chars": 770,
    "preview": "import { memo, useEffect, useRef, useState, ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\n\nconst Por"
  },
  {
    "path": "src/components/Presets.tsx",
    "chars": 2470,
    "preview": "/* eslint-disable react/no-array-index-key */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React "
  },
  {
    "path": "src/components/Square.tsx",
    "chars": 3803,
    "preview": "/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport { computePickerPosition, computeSquareXY } from '../"
  },
  {
    "path": "src/components/icon.tsx",
    "chars": 10439,
    "preview": "import React from 'react'\nimport { usePicker } from '../context.js'\n\ntype ColorProps = {\n  color: string\n}\n\nconst TrashI"
  },
  {
    "path": "src/components/index.tsx",
    "chars": 2764,
    "preview": "'use client'\nimport React from 'react'\nimport PickerContextWrapper from '../context.js'\nimport Picker from './Picker.js'"
  },
  {
    "path": "src/constants.ts",
    "chars": 554,
    "preview": "export const defaultLocales = {\n  CONTROLS: {\n    SOLID: 'Solid',\n    GRADIENT: 'Gradient',\n  },\n}\n\nexport const fakePre"
  },
  {
    "path": "src/context.tsx",
    "chars": 5087,
    "preview": "import React, {\n  createContext,\n  useContext,\n  ReactNode,\n  useEffect,\n  useState,\n} from 'react'\nimport { GradientPro"
  },
  {
    "path": "src/hooks/useColorPicker.ts",
    "chars": 7998,
    "preview": "import tc from 'tinycolor2'\nimport { useState, useEffect } from 'react'\nimport { rgb2cmyk } from '../utils/converters.js"
  },
  {
    "path": "src/hooks/usePaintHue.ts",
    "chars": 2339,
    "preview": "import { useEffect, RefObject } from 'react'\nimport tinycolor from 'tinycolor2'\n\nconst usePaintHue = (\n  canvas: RefObje"
  },
  {
    "path": "src/hooks/usePaintSquare.ts",
    "chars": 1102,
    "preview": "import { useEffect, RefObject } from 'react'\n\nconst usePaintSquare = (\n  canvas: RefObject<HTMLCanvasElement>,\n  hue: nu"
  },
  {
    "path": "src/index.ts",
    "chars": 275,
    "preview": "import { ColorPicker } from './components/index.js'\nexport { useColorPicker } from './hooks/useColorPicker.js'\n\nexport t"
  },
  {
    "path": "src/shared/types.ts",
    "chars": 2657,
    "preview": "export type ColorPickerProps = {\n  idSuffix?: string\n  value?: string\n  onChange: (value: string) => void\n  hideControls"
  },
  {
    "path": "src/styles/darkStyles.ts",
    "chars": 929,
    "preview": "export const darkStyles: Record<string, Record<string, string | number>> = {\n    body: {\n      background: 'rgb(32, 32, "
  },
  {
    "path": "src/styles/styles.ts",
    "chars": 5919,
    "preview": "import { darkStyles } from './darkStyles.js';\nimport { Styles } from '../shared/types.js';\n\nconst styles: Styles = {\n  b"
  },
  {
    "path": "src/utils/converters.ts",
    "chars": 1590,
    "preview": "export function rgb2cmyk(r: number, g: number, b: number) {\n  let computedC = 0\n  let computedM = 0\n  let computedY = 0\n"
  },
  {
    "path": "src/utils/formatters.ts",
    "chars": 1012,
    "preview": "import { ColorsProps } from '../shared/types.js'\nimport { gradientParser } from './gradientParser.js'\n\nexport const low "
  },
  {
    "path": "src/utils/gradientParser.ts",
    "chars": 10183,
    "preview": "import { high, low } from './formatters.js'\nimport { isUpperCase } from './utils.js'\nimport tinycolor from 'tinycolor2'\n"
  },
  {
    "path": "src/utils/utils.ts",
    "chars": 4351,
    "preview": "import { formatInputValues } from './formatters.js'\nimport { ColorsProps } from '../shared/types.js'\n\nexport const safeB"
  },
  {
    "path": "test/gradientParser.spec.js",
    "chars": 2209,
    "preview": "import { gradientParser } from '../src/utils/gradientParser'\nimport { describe, expect, it } from 'vitest'\n\ndescribe('gr"
  },
  {
    "path": "test/utils.spec.js",
    "chars": 4622,
    "preview": "import {\n  isUpperCase,\n  getNewHsl,\n  getGradientType,\n  getDegrees,\n} from '../src/utils/utils'\nimport { describe, exp"
  },
  {
    "path": "tsconfig.build.json",
    "chars": 205,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": false,\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\""
  },
  {
    "path": "tsconfig.json",
    "chars": 416,
    "preview": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"declaration\": true,\n    \"esModuleInterop\": true,\n    \"isolatedModules"
  }
]

About this extraction

This page contains the full source code of the hxf31891/react-gradient-color-picker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (149.8 KB), approximately 44.8k tokens, and a symbol index with 55 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!