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)

## Install ``` npm install react-best-gradient-color-picker ``` ``` yarn add react-best-gradient-color-picker ``` ## Demo See the picker in action [here](https://gradient-package-demo.web.app/)
**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)
## 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 } ```
### 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| ### API | Name | Description | | ---------------- | ---------------------------------------------------------------- | | onChange | A function to update color value |
# useColorPicker - Take complete control of the picker - Get current state - Convert between color types ## 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(
) } ``` ### 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)| ### 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 | ## 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(
{gradientType === 'linear-gradient' && setDegrees(e.target.value)} />} setPointLeft(e.target.value)} />
) } ``` ### 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(
setR(e.target.value)} /> setG(e.target.value)} /> setB(e.target.value)} /> setA(e.target.value)} />
) } ``` ### 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(
) } ``` ### 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 } ``` 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(
) } ``` ### 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 (
) } ``` ### 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(
) } ``` ### 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(
) } ```
## 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.
| 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 |
## 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(
) } ``` 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(
handleDegrees(e.target.value)} />
) } ``` ## Roadmap 1. enhanced mobile support 2. cross browser eye dropper issue 3. enhanced gradient parsing to allow additional gradient types ## 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 ## 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(false) const [handleTop, setHandleTop] = useState(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 (
handleMove(e)} // className="rbgcp-advanced-bar-wrap" style={{ cursor: 'resize', position: 'relative' }} id={`rbgcp-advanced-bar-${label}-wrapper${pickerIdSuffix}`} >
handleMove(e)} onClick={(e) => handleClick(e)} tabIndex={0} role="button" onKeyDown={() => { return }} > {label}
handleClick(e)} // className="rbgcp-advanced-bar-canvas" style={{ position: 'relative', borderRadius: 14 }} id={`rbgcp-advanced-bar-${label}-canvas${pickerIdSuffix}`} />
) } 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 (
) } 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 (
Color Guide
Analogous
{analogous?.map((c: any, key: number) => (
handleClick(c)} /> ))}
Monochromatic
{monochromatic?.map((c: any, key: number) => (
handleClick(c)} /> ))}
Triad
{triad?.map((c: any, key: number) => (
handleClick(c)} /> ))}
Tetrad
{tetrad?.map((c: any, key: number) => (
handleClick(c)} /> ))}
) } export default ComparibleColors ================================================ 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
} else { return (
{locales?.CONTROLS?.SOLID}
{locales?.CONTROLS?.GRADIENT}
) } } const InputTypeDropdown = ({ openInputType, setOpenInputType, }: { openInputType?: boolean setOpenInputType: (arg0: boolean) => void }) => { const { inputType, setInputType, defaultStyles, pickerIdSuffix } = usePicker() const vTrans = openInputType ? 'visibility 0ms linear' : 'visibility 100ms linear 150ms' const zTrans = openInputType ? 'z-index 0ms linear' : 'z-index 100ms linear 150ms' const oTrans = openInputType ? 'opacity 120ms linear' : 'opacity 150ms linear 50ms' const handleInputType = (e: any, val: string) => { if (openInputType) { e.stopPropagation() setInputType(val) setOpenInputType(false) } } return (
handleInputType(e, 'rgb')} style={modalBtnStyles(inputType === 'rgb', defaultStyles)} > RGB
handleInputType(e, 'hsl')} style={modalBtnStyles(inputType === 'hsl', defaultStyles)} > HSL
handleInputType(e, 'hsv')} style={modalBtnStyles(inputType === 'hsv', defaultStyles)} > HSV
handleInputType(e, 'cmyk')} style={modalBtnStyles(inputType === 'cmyk', defaultStyles)} > CMYK
) } const Controls = ({ locales, hideEyeDrop = false, hideAdvancedSliders = false, hideColorGuide = false, hideInputType = false, hideColorTypeBtns = false, hideGradientControls = false, hideGradientType = false, hideGradientAngle = false, hideGradientStop = false, }: { locales?: LocalesProps hideEyeDrop?: boolean hideAdvancedSliders?: boolean hideColorGuide?: boolean hideInputType?: boolean hideColorTypeBtns?: boolean hideGradientControls?: boolean hideGradientType?: boolean hideGradientAngle?: boolean hideGradientStop?: boolean }) => { const { config, onChange, isGradient, handleChange, previous, defaultStyles, pickerIdSuffix } = usePicker() const { defaultColor, defaultGradient } = config const [openComparibles, setOpenComparibles] = useState(false) const [openInputType, setOpenInputType] = useState(false) const [openAdvanced, setOpenAdvanced] = useState(false) const noTools = hideEyeDrop && hideAdvancedSliders && hideColorGuide && hideInputType const solidColor = previous?.color ?? defaultColor const gradientColor = previous?.gradient ?? defaultGradient const setSolid = () => { onChange(solidColor) } const setGradient = () => { onChange(gradientColor) } const allRightControlsHidden = hideEyeDrop && hideAdvancedSliders && hideColorGuide && hideInputType const allControlsHidden = allRightControlsHidden && hideColorTypeBtns if (allControlsHidden) { if (isGradient && !hideGradientControls) { return ( ) } else { return null } } else { return (
{!allRightControlsHidden && (
{!hideEyeDrop && } {!hideAdvancedSliders && (
setOpenAdvanced(!openAdvanced)} // className="rbgcp-control-btn rbgcp-advanced-btn" style={controlBtnStyles(openAdvanced, defaultStyles)} >
)} {!hideColorGuide && (
setOpenComparibles(!openComparibles)} // className="rbgcp-control-btn rbgcp-comparibles-btn" id={`rbgcp-comparibles-btn${pickerIdSuffix}`} >
)} {!hideInputType && (
setOpenInputType(!openInputType)} // className="rbgcp-control-btn rbgcp-color-model-btn" style={controlBtnStyles(openInputType, defaultStyles)} >
)}
)}
{!hideAdvancedSliders && ( )} {!hideColorGuide && ( )} {isGradient && !hideGradientControls && ( )}
) } } export default Controls; ================================================ 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 ( ) } const Dropper = ({ onSelect }: { onSelect: (arg0: string) => void }) => { const { defaultStyles } = usePicker() const [pickerCanvas, setPickerCanvas] = useState(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 (
{coverUp && (
getColorLegacy(e)} style={defaultStyles.rbgcpEyedropperCover} /> )}
) } 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 (
handleDown(e)} id={`rbgcp-gradient-handle-${i}${pickerIdSuffix}`} // className="rbgcp-gradient-handle-wrap" style={{ ...defaultStyles.rbgcpGradientHandleWrap, left: (left ?? 0) * leftMultiplyer, }} >
{isSelected && (
)}
) } const GradientBar = () => { const { value, colors, config, squareWidth, currentColor, handleGradient, pickerIdSuffix, createGradientStr, } = usePicker() const { barSize } = config const [dragging, setDragging] = useState(false) // const [inFocus, setInFocus] = useState(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 (
handleDown(e)} onMouseMove={(e) => handleMove(e)} id={`rbgcp-gradient-bar-canvas${pickerIdSuffix}`} // className="rbgcp-gradient-bar-canvas" /> {colors?.map((c: any, i) => ( ))}
) } 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 (
{ return }} >
{ return }} >
) } const StopPicker = () => { const { currentLeft, currentColor, defaultStyles, handleGradient, pickerIdSuffix, } = usePicker() const handleMove = (newVal: string) => { handleGradient(currentColor, formatInputValues(parseInt(newVal), 0, 100)) } return (
handleMove(e.target.value)} style={{ ...defaultStyles.rbgcpControlInput, ...defaultStyles.rbgcpStopInput, }} // className="rbgcp-control-input rbgcp-stop-input" />
) } const DegreePicker = () => { const { degrees, onChange, value, defaultStyles, pickerIdSuffix } = usePicker() const handleDegrees = (e: any) => { const newValue = formatInputValues(e.target.value, 0, 360) const remaining = value.split(/,(.+)/)[1] onChange(`linear-gradient(${newValue ?? 0}deg, ${remaining}`) } return (
handleDegrees(e)} id={`rbgcp-degree-input${pickerIdSuffix}`} // className="rbgcp-control-input rbgcp-degree-input" style={{ ...defaultStyles.rbgcpControlInput, ...defaultStyles.rbgcpDegreeInput, }} />
99 ? 0 : degrees < 10 ? 7 : 3, top: 1, fontWeight: 400, fontSize: 13, }} > °
) } const DeleteBtn = () => { const { colors, selectedColor, createGradientStr, defaultStyles, pickerIdSuffix } = usePicker() const deletePoint = () => { if (colors?.length > 2) { const formatted = colors?.map((fc: any, i: number) => ({ ...fc, value: i === selectedColor - 1 ? high(fc) : low(fc), })) const remaining = formatted?.filter( (_: any, i: number) => i !== selectedColor ) createGradientStr(remaining) } } return (
{ return }} >
) } const GradientControls = ({ hideGradientType, hideGradientAngle, hideGradientStop, }: { hideGradientType?: boolean hideGradientAngle?: boolean hideGradientStop?: boolean }) => { const { gradientType, defaultStyles, pickerIdSuffix } = usePicker() return (
{!hideGradientType && }
{!hideGradientAngle && gradientType === 'linear-gradient' && ( )}
{!hideGradientStop && }
) } export default GradientControls ================================================ 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(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 (
handleMove(e)} id={`rbgcp-hue-wrap${pickerIdSuffix}`} // className="rbgcp-hue-wrap" >
handleClick(e)} id={`rbgcp-hue-bar${pickerIdSuffix}`} style={{ borderRadius: 14, position: 'relative', verticalAlign: 'top', }} />
) } 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 (
onChange(e)} style={{ ...defaultStyles.rbgcpInput }} id={`rbgcp-${label}-input${pickerIdSuffix}`} // className="rbgcp-input" />
{label}
) } 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 (
handleHex(e)} value={displayValue?.toUpperCase()} id={`rbgcp-hex-input${pickerIdSuffix}`} style={{ ...defaultStyles.rbgcpInput, ...defaultStyles.rbgcpHexInput }} />
{label}
) } 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 ( <> handleRgb({ r: newVal, g: hc?.g, b: hc?.b })} /> handleRgb({ r: hc?.r, g: newVal, b: hc?.b })} /> 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 ( <> handleH(newVal, s, l)} /> handleSl({ h: hc?.h, s: newVal, l: l })} /> 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 ( <> handleH(newVal, hc?.s, hc?.v)} /> handleSV({ h: hc?.h, s: newVal, v: hc?.v })} /> 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 ( <> handleCmyk({ c: newVal / 100, m: m, y: y, k: k })} /> handleCmyk({ c: c, m: newVal / 100, y: y, k: k })} /> handleCmyk({ c: c, m: m, y: newVal / 100, k: k })} /> handleCmyk({ c: c, m: m, y: y, k: newVal / 100 })} /> ) } const Inputs = () => { const { hc, setHc, inputType, tinyColor, hideOpacity, showHexAlpha, handleChange, defaultStyles, pickerIdSuffix, } = usePicker() return (
{inputType !== 'cmyk' && ( )} {inputType === 'hsl' && ( )} {inputType === 'rgb' && ( )} {inputType === 'hsv' && ( )} {inputType === 'cmyk' && ( )} {!hideOpacity && ( handleChange(`rgba(${hc?.r}, ${hc?.g}, ${hc?.b}, ${newVal / 100})`) } /> )}
) } 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 (
handleMove(e)} style={{ height: 14, marginTop: 17, marginBottom: 4, cursor: 'ew-resize', position: 'relative', }} id={`rbgcp-opacity-wrapper${pickerIdSuffix}`} // className="rbgcp-opacity-wrap" >
handleClick(e)} />
) } 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 (
{!hidePickerSquare && } {!hideControls && ( )} {isGradient && } {!hideHue && } {!hideOpacity && } {!hideInputs && } {!hidePresets && }
) } export default Picker type PickerProps = { hideControls?: boolean hideInputs?: boolean hidePresets?: boolean hideOpacity?: boolean hideHue?: boolean presets?: string[] hideEyeDrop?: boolean hideAdvancedSliders?: boolean hideColorGuide?: boolean hideInputType?: boolean hideColorTypeBtns?: boolean hideGradientType?: boolean hideGradientAngle?: boolean hideGradientStop?: boolean hideGradientControls?: boolean locales?: LocalesProps hidePickerSquare?: boolean } ================================================ 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 (