├── .babelrc ├── .env ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── jsconfig.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── rollup.config.js ├── src ├── App.js ├── index.js └── lib │ ├── assets │ └── images │ │ └── alpha-background.svg │ ├── components │ ├── ColorPicker │ │ ├── Area │ │ │ ├── Alpha │ │ │ │ └── index.jsx │ │ │ ├── GradientPoints │ │ │ │ ├── GradientPoint │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ │ ├── Hue │ │ │ │ └── index.jsx │ │ │ ├── Picking │ │ │ │ └── index.jsx │ │ │ ├── Preview │ │ │ │ └── index.jsx │ │ │ └── index.jsx │ │ ├── Gradient │ │ │ ├── GradientControls │ │ │ │ └── index.jsx │ │ │ └── index.jsx │ │ ├── Preview │ │ │ ├── Hex │ │ │ │ └── index.jsx │ │ │ ├── RGB │ │ │ │ ├── index.jsx │ │ │ │ └── item │ │ │ │ │ └── index.jsx │ │ │ └── index.jsx │ │ ├── Solid │ │ │ └── index.jsx │ │ └── index.jsx │ └── UI │ │ ├── Input │ │ ├── index.jsx │ │ └── index.scss │ │ └── index.jsx │ ├── helpers │ ├── calculateDegree.js │ ├── changePicker.js │ ├── generateStyles.js │ ├── getAlpha.js │ ├── getHue.js │ ├── getRgbByHue.js │ ├── getRightValue.js │ ├── hexToRgb.js │ ├── hsvToRgb.js │ ├── index.js │ ├── rgbToHex.js │ ├── rgbToHsv.js │ ├── setRgba.js │ └── updateGradientActivePercent.js │ ├── hooks │ ├── index.js │ ├── mouseEvents.js │ └── useMount.js │ ├── index.js │ └── index.scss └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { "modules": false }], 4 | ["@babel/preset-react"] 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-proposal-object-rest-spread" 9 | ], 10 | [ 11 | "@babel/plugin-syntax-optional-catch-binding" 12 | ], 13 | [ 14 | "@babel/plugin-proposal-optional-catch-binding" 15 | ], 16 | [ 17 | "@babel/plugin-proposal-nullish-coalescing-operator" 18 | ] 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.test.tsx 2 | Test/ 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "plugin:react/recommended", 8 | "airbnb" 9 | ], 10 | "globals": { 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true, 17 | "modules": true 18 | }, 19 | "ecmaVersion": 2020, 20 | "sourceType": "module" 21 | }, 22 | "plugins": [ 23 | "react-hooks", 24 | "react", 25 | "babel" 26 | ], 27 | "rules": { 28 | // eslint 29 | "indent": ["error", 4, { "SwitchCase": 1 }], 30 | "semi": [2, "always"], 31 | "camelcase": 0, 32 | "max-len": [2, 200, 4], 33 | "arrow-parens": [2, "as-needed"], 34 | "consistent-return": 0, 35 | "import/extensions": 0, 36 | "import/no-unresolved": 0, 37 | "import/prefer-default-export": 0, 38 | "import/no-cycle": 0, 39 | "no-bitwise": 0, 40 | "no-plusplus": 0, 41 | "no-shadow": 0, 42 | "no-param-reassign": 0, 43 | "no-restricted-syntax": 0, 44 | "no-case-declarations": 0, 45 | "no-mixed-operators": 0, 46 | "no-prototype-builtins": 0, 47 | "no-restricted-properties": 0, 48 | "no-return-assign": 0, 49 | "no-nested-ternary": 0, 50 | "no-unused-expressions": 0, 51 | // babel plugin 52 | "babel/no-unused-expressions": 0, 53 | // react plugin 54 | "react/jsx-indent": ["error", 4], 55 | "react/jsx-indent-props": ["error", 4], 56 | "react/button-has-type": 0, 57 | "react/prop-types": 0, 58 | "react/jsx-props-no-spreading": 0, 59 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".tsx"] }], 60 | "react/jsx-first-prop-new-line": [2, "multiline"], 61 | "react-hooks/rules-of-hooks": 2, 62 | "react-hooks/exhaustive-deps": 2, 63 | // jsx-a11y 64 | "jsx-a11y/no-static-element-interactions": 0, 65 | "jsx-a11y/no-noninteractive-element-interactions": 0, 66 | "jsx-a11y/click-events-have-key-events": 0, 67 | "jsx-a11y/label-has-associated-control": 0, 68 | //eslint-import-plugin 69 | "import/order": [ 70 | "error", 71 | { 72 | "groups": ["builtin", "external", "internal"], 73 | "pathGroups": [ 74 | { 75 | "pattern": "react", 76 | "group": "external", 77 | "position": "before" 78 | } 79 | ], 80 | "pathGroupsExcludedImportTypes": ["react"], 81 | "newlines-between": "always" 82 | } 83 | ] 84 | }, 85 | 86 | "settings": { 87 | "import/resolver": { 88 | "node": { 89 | "paths": ["src"] 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /dist 14 | 15 | # misc 16 | .idea 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | package-lock.json 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | src 4 | public 5 | .babelrc 6 | .env 7 | .eslintignore 8 | .eslintrc 9 | jsconfig.json 10 | rollup.config.js 11 | yarn.lock 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Arthur Hayrapetyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Color Gradient Picker 2 | 3 | ## Table of Contents 4 | 5 | * [Installation](#installation) 6 | * [Examples](#examples) 7 | * [Demos](#demo) 8 | 9 | ## Installation 10 | 11 | To install, you can use [npm](https://npmjs.org/) or [yarn](https://yarnpkg.com): 12 | 13 | 14 | $ npm install react-color-gradient-picker 15 | $ yarn add react-color-gradient-picker 16 | 17 | ## Examples 18 | 19 | Here is a simple examples of react-color-gradient-picker being used in an app: 20 | 21 | ### Color Picker 22 | ```jsx 23 | import React, { useState } from 'react'; 24 | import ReactDOM from 'react-dom'; 25 | import { ColorPicker } from 'react-color-gradient-picker'; 26 | import 'react-color-gradient-picker/dist/index.css'; 27 | 28 | const color = { 29 | red: 255, 30 | green: 0, 31 | blue: 0, 32 | alpha: 1, 33 | }; 34 | 35 | function App() { 36 | const [colorAttrs, setColorAttrs] = useState(color); 37 | 38 | const onChange = (colorAttrs) => { 39 | setColorAttrs(colorAttrs); 40 | }; 41 | 42 | return ( 43 | 49 | 50 | ); 51 | } 52 | 53 | ReactDOM.render(, document.getElementById('app-id')); 54 | ``` 55 | 56 | ### Gradient Color Picker 57 | ```jsx 58 | import React, { useState } from 'react'; 59 | import ReactDOM from 'react-dom'; 60 | import { ColorPicker } from 'react-color-gradient-picker'; 61 | import 'react-color-gradient-picker/dist/index.css'; 62 | 63 | const gradient = { 64 | points: [ 65 | { 66 | left: 0, 67 | red: 0, 68 | green: 0, 69 | blue: 0, 70 | alpha: 1, 71 | }, 72 | { 73 | left: 100, 74 | red: 255, 75 | green: 0, 76 | blue: 0, 77 | alpha: 1, 78 | }, 79 | ], 80 | degree: 0, 81 | type: 'linear', 82 | }; 83 | 84 | function App() { 85 | const [gradientAttrs, setGradientAttrs] = useState(gradient); 86 | 87 | const onChange = (gradientAttrs) => { 88 | setGradientAttrs(gradientAttrs); 89 | }; 90 | 91 | return ( 92 | 99 | 100 | ); 101 | } 102 | 103 | ReactDOM.render(, document.getElementById('app-id')); 104 | ``` 105 | ## Demo 106 | 107 | * [Solid and gradient pickers live demo](https://arthay.github.io/react-color-gradient-picker/) 108 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "paths": { 5 | "~/*": ["*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-color-gradient-picker", 3 | "description": "Color picker for react", 4 | "version": "0.1.2", 5 | "main": "./dist/index-cjs.js", 6 | "module": "./dist/index-es.js", 7 | "browser": "./dist/index-cjs.js", 8 | "style": "./dist/index.css", 9 | "author": "Arthur Hayrapetyan", 10 | "license": "MIT", 11 | "homepage": "https://arthay.github.io/react-color-gradient-picker/", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/arthay/react-color-gradient-picker.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/arthay/react-color-gradient-picker/issues" 18 | }, 19 | "peerDependencies": { 20 | "react": "^16.13.1", 21 | "react-dom": "^16.13.1" 22 | }, 23 | "devDependencies": { 24 | "@babel/cli": "^7.8.4", 25 | "@babel/core": "^7.9.6", 26 | "@babel/plugin-proposal-class-properties": "^7.8.3", 27 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", 28 | "@babel/plugin-proposal-object-rest-spread": "^7.9.6", 29 | "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", 30 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 31 | "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", 32 | "@babel/polyfill": "^7.8.7", 33 | "@babel/preset-env": "^7.9.6", 34 | "@babel/preset-react": "^7.9.4", 35 | "@rollup/plugin-buble": "^0.21.3", 36 | "@rollup/plugin-commonjs": "^12.0.0", 37 | "@rollup/plugin-json": "^4.0.3", 38 | "@rollup/plugin-multi-entry": "^3.0.1", 39 | "@rollup/plugin-node-resolve": "^8.0.0", 40 | "@testing-library/jest-dom": "^4.2.4", 41 | "@testing-library/react": "^9.3.2", 42 | "@testing-library/user-event": "^7.1.2", 43 | "babel-loader": "^8.1.0", 44 | "babel-plugin-webpack-alias": "^2.1.2", 45 | "eslint": "^7.0.0", 46 | "eslint-config-airbnb": "^18.1.0", 47 | "eslint-plugin-babel": "^5.3.0", 48 | "eslint-plugin-import": "^2.20.2", 49 | "eslint-plugin-json": "^2.1.1", 50 | "eslint-plugin-jsx-a11y": "^6.2.3", 51 | "eslint-plugin-react": "^7.20.0", 52 | "eslint-plugin-react-hooks": "^4.0.2", 53 | "gh-pages": "^2.2.0", 54 | "node-sass": "^4.14.1", 55 | "react": "^16.13.1", 56 | "react-dom": "^16.13.1", 57 | "react-scripts": "3.4.1", 58 | "rollup": "^2.10.5", 59 | "rollup-plugin-copy-assets": "^2.0.1", 60 | "rollup-plugin-includepaths": "^0.2.3", 61 | "rollup-plugin-scss": "^2.5.0", 62 | "rollup-plugin-terser": "^5.3.0" 63 | }, 64 | "scripts": { 65 | "start": "react-scripts start", 66 | "build": "rimraf dist && rollup -c", 67 | "build:demo": "react-scripts build", 68 | "lint": "eslint 'src/**/*.{js,ts,tsx}' --quiet --fix" 69 | }, 70 | "tags": [ 71 | "react", 72 | "color-picker", 73 | "gradient" 74 | ], 75 | "keywords": [ 76 | "react", 77 | "react-hook", 78 | "react-functional-component", 79 | "color-picker", 80 | "picker", 81 | "gradient", 82 | "rgb", 83 | "hsv" 84 | ], 85 | "browserslist": { 86 | "production": [ 87 | ">0.2%", 88 | "not dead", 89 | "not op_mini all" 90 | ], 91 | "development": [ 92 | "last 1 chrome version", 93 | "last 1 firefox version", 94 | "last 1 safari version" 95 | ] 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arthay/react-color-gradient-picker/2b6a37105935dcbb17a8f3640434353fb955daac/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Color Picker React 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arthay/react-color-gradient-picker/2b6a37105935dcbb17a8f3640434353fb955daac/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arthay/react-color-gradient-picker/2b6a37105935dcbb17a8f3640434353fb955daac/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from '@rollup/plugin-node-resolve'; 2 | import babel from '@rollup/plugin-buble'; 3 | import json from '@rollup/plugin-json'; 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | import scss from 'rollup-plugin-scss'; 6 | import copy from 'rollup-plugin-copy-assets'; 7 | import includePaths from 'rollup-plugin-includepaths'; 8 | 9 | import pkg from './package.json'; 10 | 11 | const extensions = ['.js', '.jsx', '.ts', '.tsx']; 12 | const config = { 13 | input: 'src/lib/index.js', 14 | external: Object.keys(Object.assign(pkg.peerDependencies, pkg.dependencies)), 15 | output: [{ 16 | format: 'cjs', 17 | name: 'react-color-gradient-picker', // umd and iife for var name = (function(){})() 18 | entryFileNames: 'index-[format].js', 19 | dir: 'dist', 20 | sourcemap: true, 21 | globals: { // umd and iife for for import externals 22 | 'react-dom': 'reactDOM', 23 | }, 24 | }, { 25 | format: 'es', 26 | dir: 'dist', 27 | entryFileNames: 'index-[format].js', 28 | sourcemap: true, 29 | name: 'react-color-gradient-picker', 30 | }], 31 | plugins: [ 32 | scss({ 33 | output: 'dist/index.css', 34 | }), 35 | nodeResolve({ 36 | mainFields: ['module', 'main', 'jsnext:main', 'browser'], 37 | extensions, 38 | }), 39 | commonjs(), 40 | babel({ 41 | exclude: './node_modules/**', 42 | extensions, 43 | objectAssign: 'Object.assign', 44 | }), 45 | json(), 46 | copy({ 47 | assets: [ 48 | // You can include directories 49 | 'src/lib/assets', 50 | ], 51 | }), 52 | includePaths({ 53 | paths: ['src'], 54 | extensions: ['.js', '.jsx'], 55 | 56 | }), 57 | ], 58 | }; 59 | 60 | export default config; 61 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { ColorPicker } from './lib'; 4 | 5 | function App() { 6 | const onChange = (attrs, name) => { 7 | console.log(attrs, name); 8 | }; 9 | 10 | return ( 11 |
12 |
13 |

Solid

14 | onChange(color, 'start')} 16 | onChange={color => onChange(color, 'change')} 17 | onEndChange={color => onChange(color, 'end')} 18 | /> 19 |
20 |
21 |

Gradient

22 | onChange(color, 'start')} 24 | onChange={color => onChange(color, 'change')} 25 | onEndChange={color => onChange(color, 'end')} 26 | isGradient 27 | /> 28 |
29 |
30 | ); 31 | } 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | 5 | import App from './App'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root'), 12 | ); 13 | -------------------------------------------------------------------------------- /src/lib/assets/images/alpha-background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Area/Alpha/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useCallback, useEffect, useRef, useState, 3 | } from 'react'; 4 | 5 | import { useMouseEvents } from 'lib/hooks'; 6 | import { getAlpha } from 'lib/helpers'; 7 | 8 | function Alpha({ 9 | red, green, blue, alpha, updateRgb, 10 | }) { 11 | const alphaMaskRef = useRef(); 12 | const [width, setWidth] = useState(0); 13 | 14 | useEffect(() => { 15 | if (alphaMaskRef.current) { 16 | setWidth(alphaMaskRef.current.clientWidth); 17 | } 18 | }, [setWidth]); 19 | 20 | const mouseDownHandler = useCallback(event => { 21 | const elementX = event.currentTarget.getBoundingClientRect().x; 22 | const startX = event.pageX; 23 | const positionX = startX - elementX; 24 | 25 | updateRgb({ alpha: getAlpha(positionX, width) }, 'onStartChange'); 26 | return { 27 | startX, 28 | positionX, 29 | 30 | }; 31 | }, [width, updateRgb]); 32 | 33 | const changeObjectPositions = useCallback((event, { startX, positionX }) => { 34 | const moveX = event.pageX - startX; 35 | positionX += moveX; 36 | 37 | const alpha = getAlpha(positionX, width); 38 | 39 | return { 40 | positions: { 41 | positionX, 42 | startX: event.pageX, 43 | }, 44 | alpha, 45 | }; 46 | }, [width]); 47 | 48 | const mouseMoveHandler = useCallback((event, { startX, positionX }) => { 49 | const { positions, alpha } = changeObjectPositions(event, { startX, positionX }); 50 | 51 | updateRgb({ alpha }, 'onChange'); 52 | 53 | return positions; 54 | }, [changeObjectPositions, updateRgb]); 55 | 56 | const mouseUpHandler = useCallback((event, { startX, positionX }) => { 57 | const { positions, alpha } = changeObjectPositions(event, { startX, positionX }); 58 | 59 | updateRgb({ alpha }, 'onEndChange'); 60 | 61 | return positions; 62 | }, [changeObjectPositions, updateRgb]); 63 | 64 | const mouseEvents = useMouseEvents(mouseDownHandler, mouseMoveHandler, mouseUpHandler); 65 | 66 | const onMouseDown = event => { 67 | mouseEvents(event); 68 | }; 69 | 70 | const style = { 71 | background: `linear-gradient(to right, rgba(0, 0, 0, 0), rgb(${red}, ${green}, ${blue}))`, 72 | }; 73 | 74 | const offsetLeft = ((alpha * width) | 0) - 6; 75 | 76 | const pointerStyle = { 77 | left: `${offsetLeft}px`, 78 | }; 79 | 80 | return ( 81 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | ); 93 | } 94 | 95 | export default Alpha; 96 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Area/GradientPoints/GradientPoint/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | 3 | import { updateGradientActivePercent } from 'lib/helpers'; 4 | import { useMouseEvents } from 'lib/hooks'; 5 | 6 | function GradientPoint({ 7 | point, 8 | activePointIndex, 9 | index, 10 | width, 11 | positions, 12 | changeActivePointIndex, 13 | updateGradientLeft, 14 | removePoint, 15 | }) { 16 | const activeClassName = activePointIndex === index ? ' active' : ''; 17 | 18 | const pointStyle = { 19 | left: `${(point.left * (width / 100)) - 6}px`, 20 | }; 21 | 22 | const mouseDownHandler = useCallback(event => { 23 | changeActivePointIndex(index); 24 | 25 | const startX = event.pageX; 26 | const startY = event.pageY; 27 | const offsetX = startX - positions.x; 28 | 29 | updateGradientLeft(point.left, index, 'onStartChange'); 30 | 31 | return { 32 | startX, 33 | startY, 34 | offsetX, 35 | 36 | }; 37 | }, [point.left, index, positions, changeActivePointIndex, updateGradientLeft]); 38 | 39 | const changeObjectPositions = useCallback((event, { startX, offsetX }) => { 40 | const moveX = event.pageX - startX; 41 | offsetX += moveX; 42 | // update point percent 43 | const left = updateGradientActivePercent(offsetX, width); 44 | 45 | return { 46 | positions: { 47 | offsetX, 48 | startX: event.pageX, 49 | }, 50 | left, 51 | }; 52 | }, [width]); 53 | 54 | const mouseMoveHandler = useCallback((event, { startX, offsetX }) => { 55 | const { positions, left } = changeObjectPositions(event, { startX, offsetX }); 56 | 57 | updateGradientLeft(left, index, 'onChange'); 58 | 59 | return positions; 60 | }, [index, changeObjectPositions, updateGradientLeft]); 61 | 62 | const mouseUpHandler = useCallback((event, { startX, offsetX }) => { 63 | const { positions, left } = changeObjectPositions(event, { startX, offsetX }); 64 | 65 | updateGradientLeft(left, index, 'onEndChange'); 66 | 67 | return positions; 68 | }, [index, changeObjectPositions, updateGradientLeft]); 69 | 70 | const mouseEvents = useMouseEvents(mouseDownHandler, mouseMoveHandler, mouseUpHandler); 71 | 72 | const onMouseDown = event => { 73 | changeActivePointIndex(index); 74 | mouseEvents(event); 75 | }; 76 | 77 | const pointerClickHandler = event => { 78 | event.stopPropagation(); 79 | }; 80 | 81 | return ( 82 |
removePoint(index)} 88 | > 89 | 90 |
91 | ); 92 | } 93 | 94 | export default GradientPoint; 95 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Area/GradientPoints/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useEffect, useState, useRef, useCallback, 3 | } from 'react'; 4 | 5 | import { generateGradientStyle, updateGradientActivePercent } from 'lib/helpers'; 6 | 7 | import GradientPoint from './GradientPoint'; 8 | 9 | function GradientPoints({ 10 | points, activePointIndex, changeActivePointIndex, updateGradientLeft, addPoint, removePoint, 11 | }) { 12 | const [pointsStyle, setpointsStyle] = useState({}); 13 | const [width, setWidth] = useState(0); 14 | const [positions, setPositions] = useState({}); 15 | 16 | const pointsContainerRef = useRef(); 17 | 18 | 19 | useEffect(() => { 20 | if (pointsContainerRef.current) { 21 | setWidth(pointsContainerRef.current.clientWidth); 22 | 23 | const pointerPos = pointsContainerRef.current.getBoundingClientRect(); 24 | setPositions({ x: pointerPos.x, y: pointerPos.y }); 25 | } 26 | }, []); 27 | useEffect(() => { 28 | const style = generateGradientStyle(points, 'linear', 90); 29 | 30 | setpointsStyle({ background: style }); 31 | }, [points]); 32 | 33 | 34 | const pointsContainerClick = useCallback(event => { 35 | const left = updateGradientActivePercent(event.pageX - positions.x, width); 36 | 37 | addPoint(left); 38 | }, [addPoint, positions.x, width]); 39 | 40 | const pointsContainer = () => ( 41 |
45 | { 46 | points && points.map((point, index) => ( 47 | 58 | )) 59 | } 60 |
61 | ); 62 | 63 | return ( 64 |
69 | {pointsContainer()} 70 |
71 | 72 | 73 | ); 74 | } 75 | 76 | export default GradientPoints; 77 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Area/Hue/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useCallback, useEffect, useRef, useState, 3 | } from 'react'; 4 | 5 | import { useMouseEvents } from 'lib/hooks'; 6 | import { getHue } from 'lib/helpers'; 7 | 8 | function Hue({ 9 | hue, saturation, value, updateRgb, 10 | }) { 11 | const hueRef = useRef(); 12 | const [width, setWidth] = useState(0); 13 | 14 | useEffect(() => { 15 | if (hueRef.current) { 16 | setWidth(hueRef.current.clientWidth); 17 | } 18 | }, [setWidth]); 19 | 20 | const mouseDownHandler = useCallback(event => { 21 | const elementX = event.currentTarget.getBoundingClientRect().x; 22 | const startX = event.pageX; 23 | const positionX = startX - elementX; 24 | 25 | const color = getHue(positionX, width, saturation, value); 26 | 27 | updateRgb(color, 'onStartChange'); 28 | 29 | return { 30 | startX, 31 | positionX, 32 | }; 33 | }, [width, saturation, value, updateRgb]); 34 | 35 | const changeObjectPositions = useCallback((event, { startX, positionX }) => { 36 | const moveX = event.pageX - startX; 37 | positionX += moveX; 38 | 39 | // update value and saturation 40 | const offsetX = positionX > width ? width : positionX <= 0 ? 0 : positionX; 41 | const color = getHue(offsetX, width, saturation, value); 42 | 43 | return { 44 | positions: { 45 | positionX, 46 | startX: event.pageX, 47 | }, 48 | color, 49 | }; 50 | }, [width, saturation, value]); 51 | 52 | const mouseMoveHandler = useCallback((event, { startX, positionX }) => { 53 | const { positions, color } = changeObjectPositions(event, { startX, positionX }); 54 | 55 | updateRgb(color, 'onChange'); 56 | 57 | return positions; 58 | }, [changeObjectPositions, updateRgb]); 59 | 60 | const mouseUpHandler = useCallback((event, { startX, positionX }) => { 61 | const { positions, color } = changeObjectPositions(event, { startX, positionX }); 62 | 63 | updateRgb(color, 'onEndChange'); 64 | 65 | return positions; 66 | }, [changeObjectPositions, updateRgb]); 67 | 68 | const mouseEvents = useMouseEvents(mouseDownHandler, mouseMoveHandler, mouseUpHandler); 69 | 70 | const onMouseDown = event => { 71 | mouseEvents(event); 72 | }; 73 | 74 | const offsetLeft = ((hue * width / 360) | 0) - 6; 75 | 76 | const pointerStyle = { 77 | left: `${offsetLeft}px`, 78 | }; 79 | 80 | return ( 81 |
85 |
86 |
90 |
91 |
92 | ); 93 | } 94 | 95 | export default Hue; 96 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Area/Picking/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useEffect, useCallback, useRef, useState, 3 | } from 'react'; 4 | 5 | import { changePicker, getRgbByHue } from 'lib/helpers'; 6 | import { useMouseEvents } from 'lib/hooks'; 7 | 8 | function Picking({ 9 | red, 10 | green, 11 | blue, 12 | hue, 13 | saturation, 14 | value, 15 | updateRgb, 16 | }) { 17 | const pickingAreaRef = useRef(); 18 | const [width, setWidth] = useState(0); 19 | const [height, setHeight] = useState(0); 20 | 21 | useEffect(() => { 22 | if (pickingAreaRef.current) { 23 | setWidth(pickingAreaRef.current.clientWidth); 24 | setHeight(pickingAreaRef.current.clientHeight); 25 | } 26 | }, [pickingAreaRef, setWidth, setHeight]); 27 | 28 | useEffect(() => { 29 | const { red, green, blue } = getRgbByHue(hue); 30 | 31 | pickingAreaRef.current.style.backgroundColor = `rgb(${red}, ${green}, ${blue})`; 32 | }, [hue]); 33 | 34 | // generate offsetLeft by saturation 35 | const offsetLeft = ((saturation * width / 100) | 0) - 6; 36 | 37 | // generate offsetTop by value 38 | const offsetTop = (height - (value * height / 100) | 0) - 6; 39 | 40 | const getPointerStyle = { 41 | backgroundColor: `rgb(${red}, ${green}, ${blue})`, 42 | left: `${offsetLeft}px`, 43 | top: `${offsetTop}px`, 44 | }; 45 | 46 | const mouseDownHandler = useCallback(event => { 47 | const elementX = event.currentTarget.getBoundingClientRect().x; 48 | const elementY = event.currentTarget.getBoundingClientRect().y; 49 | const startX = event.pageX; 50 | const startY = event.pageY; 51 | const positionX = startX - elementX; 52 | const positionY = startY - elementY; 53 | 54 | const color = changePicker(positionX, positionY, height, width, hue); 55 | 56 | updateRgb(color, 'onStartChange'); 57 | return { 58 | startX, 59 | startY, 60 | positionX, 61 | positionY, 62 | 63 | }; 64 | }, [height, width, hue, updateRgb]); 65 | 66 | const changeObjectPositions = useCallback((event, { 67 | startX, startY, positionX, positionY, 68 | }) => { 69 | const moveX = event.pageX - startX; 70 | const moveY = event.pageY - startY; 71 | positionX += moveX; 72 | positionY += moveY; 73 | 74 | const color = changePicker(positionX, positionY, height, width, hue); 75 | 76 | return { 77 | positions: { 78 | positionX, 79 | positionY, 80 | startX: event.pageX, 81 | startY: event.pageY, 82 | }, 83 | color, 84 | }; 85 | }, [height, hue, width]); 86 | 87 | const mouseMoveHandler = useCallback((event, { 88 | startX, startY, positionX, positionY, 89 | }) => { 90 | const { positions, color } = changeObjectPositions(event, { 91 | startX, startY, positionX, positionY, 92 | }); 93 | 94 | updateRgb(color, 'onChange'); 95 | 96 | return positions; 97 | }, [updateRgb, changeObjectPositions]); 98 | 99 | const mouseUpHandler = useCallback((event, { 100 | startX, startY, positionX, positionY, 101 | }) => { 102 | const { positions, color } = changeObjectPositions(event, { 103 | startX, startY, positionX, positionY, 104 | }); 105 | 106 | updateRgb(color, 'onEndChange'); 107 | 108 | return positions; 109 | }, [updateRgb, changeObjectPositions]); 110 | 111 | const mouseEvents = useMouseEvents(mouseDownHandler, mouseMoveHandler, mouseUpHandler); 112 | 113 | const onMouseDown = event => { 114 | mouseEvents(event); 115 | }; 116 | 117 | return ( 118 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | ); 130 | } 131 | 132 | export default Picking; 133 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Area/Preview/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | import { generateSolidStyle, generateGradientStyle } from 'lib/helpers'; 4 | 5 | function Preview({ 6 | red, 7 | green, 8 | blue, 9 | alpha, 10 | isGradient, 11 | points, 12 | gradientType, 13 | gradientDegree, 14 | }) { 15 | const [style, setStyle] = useState({}); 16 | 17 | useEffect(() => { 18 | if (isGradient) { 19 | const style = generateGradientStyle(points, gradientType, gradientDegree); 20 | 21 | setStyle({ background: style }); 22 | 23 | return; 24 | } 25 | 26 | const style = generateSolidStyle(red, green, blue, alpha); 27 | 28 | setStyle({ backgroundColor: style }); 29 | }, [points, gradientDegree, gradientType, isGradient, red, green, blue, alpha]); 30 | 31 | return ( 32 |
33 |
34 |
35 | ); 36 | } 37 | 38 | export default Preview; 39 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Area/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Picking from './Picking'; 4 | import Preview from './Preview'; 5 | import Hue from './Hue'; 6 | import Alpha from './Alpha'; 7 | import GradientPoints from './GradientPoints'; 8 | 9 | function Area({ 10 | red, 11 | green, 12 | blue, 13 | alpha, 14 | hue, 15 | saturation, 16 | value, 17 | isGradient, 18 | type, 19 | degree, 20 | points, 21 | activePointIndex, 22 | updateRgb, 23 | changeActivePointIndex, 24 | updateGradientLeft, 25 | addPoint, 26 | removePoint, 27 | }) { 28 | return ( 29 |
30 | 39 | 40 | { 41 | isGradient 42 | && ( 43 | 53 | ) 54 | } 55 | 56 |
57 | 67 | 68 |
69 | 75 | 82 |
83 |
84 |
85 | ); 86 | } 87 | 88 | export default Area; 89 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Gradient/GradientControls/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | 3 | import { calculateDegree } from 'lib/helpers'; 4 | import { useMouseEvents } from 'lib/hooks'; 5 | 6 | function GradientControls({ type, degree, changeGradientControl }) { 7 | const [disableClick, setDisableClick] = useState(false); 8 | 9 | const onClickGradientDegree = useCallback(() => { 10 | if (disableClick) { 11 | setDisableClick(false); 12 | return; 13 | } 14 | 15 | let gradientDegree = degree + 45; 16 | 17 | if (gradientDegree >= 360) { 18 | gradientDegree = 0; 19 | } 20 | 21 | changeGradientControl({ degree: parseInt(gradientDegree, 10) }); 22 | }, [disableClick, degree, changeGradientControl]); 23 | 24 | const mouseDownHandler = useCallback(event => { 25 | const pointer = event.target; 26 | const pointerBox = pointer.getBoundingClientRect(); 27 | const centerY = pointerBox.top + parseInt(8 - window.pageYOffset, 10); 28 | const centerX = pointerBox.left + parseInt(8 - window.pageXOffset, 10); 29 | 30 | return { 31 | centerY, 32 | centerX, 33 | 34 | }; 35 | }, []); 36 | 37 | const mouseMoveHandler = useCallback((event, { centerX, centerY }) => { 38 | setDisableClick(true); 39 | 40 | const newDegree = calculateDegree(event.clientX, event.clientY, centerX, centerY); 41 | 42 | changeGradientControl({ degree: parseInt(newDegree, 10) }); 43 | 44 | return { centerX, centerY }; 45 | }, [changeGradientControl]); 46 | 47 | const mouseUpHandler = event => { 48 | const targetClasses = event.target.classList; 49 | 50 | if (targetClasses.contains('gradient-degrees') || targetClasses.contains('icon-rotate')) { 51 | return; 52 | } 53 | 54 | setDisableClick(false); 55 | }; 56 | 57 | const mouseEvents = useMouseEvents(mouseDownHandler, mouseMoveHandler, mouseUpHandler); 58 | 59 | const onMouseDown = event => { 60 | mouseEvents(event); 61 | }; 62 | 63 | const degreesStyle = { 64 | transform: `rotate(${degree}deg)`, 65 | }; 66 | 67 | return ( 68 |
69 |
70 |
changeGradientControl({ type: 'linear' })} 73 | /> 74 |
changeGradientControl({ type: 'radial' })} 77 | /> 78 |
79 | { 80 | type === 'linear' 81 | && ( 82 |
83 |
88 |
89 |
90 |
91 |
92 |
93 |

94 | {degree} 95 | ° 96 |

97 |
98 |
99 | ) 100 | } 101 |
102 | ); 103 | } 104 | 105 | export default GradientControls; 106 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Gradient/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react'; 2 | 3 | import { useMount } from 'lib/hooks'; 4 | import { rgbToHsv, getRightValue, generateGradientStyle } from 'lib/helpers'; 5 | 6 | import Area from '../Area'; 7 | import Preview from '../Preview'; 8 | import GradientControls from './GradientControls'; 9 | 10 | function Gradient({ 11 | points, type, degree, onChange, onStartChange, onEndChange, 12 | }) { 13 | const [activePointIndex, setActivePointIndex] = useState(0); 14 | const [gradientPoints, setGradientPoints] = useState(points); 15 | const [activePoint, setActivePoint] = useState(gradientPoints[0]); 16 | const [colorRed, setColorRed] = useState(activePoint.red); 17 | const [colorGreen, setColorGreen] = useState(activePoint.green); 18 | const [colorBlue, setColorBlue] = useState(activePoint.blue); 19 | const [colorAlpha, setColorAlpha] = useState(activePoint.alpha); 20 | const [colorHue, setColorHue] = useState(0); 21 | const [colorSaturation, setColorSaturation] = useState(100); 22 | const [colorValue, setColorValue] = useState(100); 23 | const [gradientType, setGradientType] = useState(type); 24 | const [gradientDegree, setGradientDegree] = useState(degree); 25 | 26 | const actions = { 27 | onChange, 28 | onStartChange, 29 | onEndChange, 30 | }; 31 | 32 | useMount(() => { 33 | const { hue, saturation, value } = rgbToHsv({ red: colorRed, green: colorGreen, blue: colorBlue }); 34 | 35 | setColorHue(hue); 36 | setColorSaturation(saturation); 37 | setColorValue(value); 38 | }); 39 | 40 | const removePoint = useCallback((index = activePointIndex) => { 41 | if (gradientPoints.length <= 2) { 42 | return; 43 | } 44 | 45 | const localGradientPoints = gradientPoints.slice(); 46 | localGradientPoints.splice(index, 1); 47 | 48 | setGradientPoints(localGradientPoints); 49 | 50 | if (index > 0) { 51 | setActivePointIndex(index - 1); 52 | } 53 | 54 | onChange && onChange({ 55 | points: localGradientPoints, 56 | type: gradientType, 57 | degree: gradientDegree, 58 | style: generateGradientStyle(localGradientPoints, gradientType, gradientDegree), 59 | }); 60 | }, [gradientPoints, activePointIndex, gradientType, gradientDegree, onChange]); 61 | 62 | const keyUpHandler = useCallback(event => { 63 | if ((event.keyCode === 46 || event.keyCode === 8)) { 64 | removePoint(activePointIndex); 65 | } 66 | }, [activePointIndex, removePoint]); 67 | 68 | useEffect(() => { 69 | document.addEventListener('keyup', keyUpHandler); 70 | 71 | return () => { 72 | document.removeEventListener('keyup', keyUpHandler); 73 | }; 74 | }); 75 | 76 | const changeGradientControl = useCallback(({ type, degree }, actionName = 'onChange') => { 77 | type = getRightValue(type, gradientType); 78 | degree = getRightValue(degree, gradientDegree); 79 | 80 | setGradientType(type); 81 | setGradientDegree(degree); 82 | 83 | const action = actions[actionName]; 84 | 85 | action && action({ 86 | points: gradientPoints, 87 | type, 88 | degree, 89 | style: generateGradientStyle(gradientPoints, type, degree), 90 | }); 91 | }, [actions, gradientPoints, gradientDegree, gradientType]); 92 | 93 | const changeActivePointIndex = useCallback(index => { 94 | setActivePointIndex(index); 95 | 96 | const localGradientPoint = gradientPoints[index]; 97 | const { 98 | red, green, blue, alpha, 99 | } = localGradientPoint; 100 | setActivePoint(localGradientPoint); 101 | 102 | setColorRed(red); 103 | setColorGreen(green); 104 | setColorBlue(blue); 105 | setColorAlpha(alpha); 106 | 107 | const { hue, saturation, value } = rgbToHsv({ red, green, blue }); 108 | 109 | setColorHue(hue); 110 | setColorSaturation(saturation); 111 | setColorValue(value); 112 | }, [gradientPoints]); 113 | 114 | const updateColor = useCallback(({ 115 | red, green, blue, alpha, hue, saturation, value, 116 | }, actionName = 'onChange') => { 117 | red = getRightValue(red, colorRed); 118 | green = getRightValue(green, colorGreen); 119 | blue = getRightValue(blue, colorBlue); 120 | alpha = getRightValue(alpha, colorAlpha); 121 | hue = getRightValue(hue, colorHue); 122 | saturation = getRightValue(saturation, colorSaturation); 123 | value = getRightValue(value, colorValue); 124 | 125 | const localGradientPoints = gradientPoints.slice(); 126 | 127 | localGradientPoints[activePointIndex] = { 128 | ...localGradientPoints[activePointIndex], 129 | red, 130 | green, 131 | blue, 132 | alpha, 133 | }; 134 | 135 | setColorRed(red); 136 | setColorGreen(green); 137 | setColorBlue(blue); 138 | setColorAlpha(alpha); 139 | setColorHue(hue); 140 | setColorSaturation(saturation); 141 | setColorValue(value); 142 | setGradientPoints(localGradientPoints); 143 | 144 | const action = actions[actionName]; 145 | 146 | action && action({ 147 | points: localGradientPoints, 148 | type: gradientType, 149 | degree: gradientDegree, 150 | style: generateGradientStyle(localGradientPoints, gradientType, gradientDegree), 151 | }); 152 | }, [ 153 | colorRed, colorGreen, colorBlue, colorAlpha, 154 | colorHue, colorSaturation, colorValue, 155 | activePointIndex, gradientPoints, actions, gradientType, gradientDegree, 156 | ]); 157 | 158 | const updateGradientLeft = useCallback((left, index, actionName = 'onChange') => { 159 | const localGradientPoints = gradientPoints.slice(); 160 | localGradientPoints[index].left = left; 161 | 162 | setGradientPoints(localGradientPoints); 163 | 164 | const action = actions[actionName]; 165 | 166 | action && action({ 167 | points: localGradientPoints, 168 | type: gradientType, 169 | degree: gradientDegree, 170 | style: generateGradientStyle(localGradientPoints, gradientType, gradientDegree), 171 | }); 172 | }, [actions, gradientPoints, gradientDegree, gradientType]); 173 | 174 | const addPoint = useCallback(left => { 175 | const localGradientPoints = gradientPoints.slice(); 176 | 177 | localGradientPoints.push({ 178 | ...localGradientPoints[activePointIndex], 179 | left, 180 | }); 181 | 182 | setGradientPoints(localGradientPoints); 183 | setActivePointIndex(localGradientPoints.length - 1); 184 | 185 | onChange && onChange({ 186 | points: localGradientPoints, 187 | type: gradientType, 188 | degree: gradientDegree, 189 | style: generateGradientStyle(localGradientPoints, gradientType, gradientDegree), 190 | }); 191 | }, [onChange, gradientPoints, activePointIndex, gradientType, gradientDegree]); 192 | 193 | return ( 194 | <> 195 | 200 | 220 | 227 | 228 | ); 229 | } 230 | 231 | export default Gradient; 232 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Preview/Hex/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react'; 2 | 3 | import { rgbToHex, hexToRgb } from 'lib/helpers'; 4 | import { Input } from 'lib/components/UI'; 5 | 6 | function Hex({ 7 | red, green, blue, updateRgb, 8 | }) { 9 | const [hexValue, setHexValue] = useState(''); 10 | const [progress, setProgress] = useState(false); 11 | 12 | useEffect(() => { 13 | if (progress) { 14 | return; 15 | } 16 | const hex = rgbToHex(red, green, blue); 17 | 18 | setHexValue(hex); 19 | }, [red, green, blue, progress]); 20 | 21 | const changeHex = useCallback(event => { 22 | setHexValue(event.target.value); 23 | const color = hexToRgb(event.target.value); 24 | 25 | if (color) { 26 | updateRgb(color); 27 | } 28 | }, [setHexValue, updateRgb]); 29 | 30 | return ( 31 | setProgress(true)} 36 | onBlur={() => setProgress(false)} 37 | classes="hex" 38 | /> 39 | ); 40 | } 41 | 42 | export default Hex; 43 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Preview/RGB/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | 3 | import { rgbToHsv } from 'lib/helpers'; 4 | 5 | import RGBItem from './item'; 6 | 7 | function RGB({ 8 | red, green, blue, alpha, updateRgb, 9 | }) { 10 | const changeValue = useCallback((field, value) => { 11 | if (field === 'alpha') { 12 | updateRgb({ alpha: value / 100 }); 13 | 14 | return; 15 | } 16 | 17 | const color = rgbToHsv({ 18 | red, green, blue, [field]: value, 19 | }); 20 | 21 | updateRgb({ ...color, [field]: value }); 22 | }, [red, green, blue, updateRgb]); 23 | 24 | return ( 25 | <> 26 | changeValue('red', value)} 31 | /> 32 | 33 | changeValue('green', value)} 38 | /> 39 | 40 | changeValue('blue', value)} 45 | /> 46 | 47 | changeValue('alpha', value)} 52 | /> 53 | 54 | ); 55 | } 56 | 57 | export default RGB; 58 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Preview/RGB/item/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react'; 2 | 3 | import { Input } from 'lib/components/UI'; 4 | 5 | export default function RGBItem({ 6 | value, type, label, onChange, 7 | }) { 8 | const [inputValue, setInputValue] = useState(value); 9 | 10 | useEffect(() => { 11 | if (value !== +inputValue && inputValue !== '') { 12 | setInputValue(value); 13 | } 14 | }, [inputValue, value]); 15 | 16 | const onChangeHandler = useCallback(event => { 17 | const value = +event.target.value; 18 | 19 | if (Number.isNaN(value) || value.length > 3 || value < 0 || value > 255) { 20 | return; 21 | } 22 | setInputValue(event.target.value); 23 | 24 | onChange(value); 25 | }, [onChange]); 26 | 27 | const onBlur = useCallback(() => { 28 | !inputValue && inputValue !== 0 && setInputValue(value); 29 | }, [inputValue, setInputValue, value]); 30 | 31 | return ( 32 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Preview/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Hex from './Hex'; 4 | import RGB from './RGB'; 5 | 6 | function Preview({ 7 | red, green, blue, alpha, updateRgb, 8 | }) { 9 | return ( 10 |
11 |
12 | 18 | 19 | 26 |
27 |
28 | ); 29 | } 30 | 31 | export default Preview; 32 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/Solid/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | 3 | import { useMount } from 'lib/hooks'; 4 | import { rgbToHsv, getRightValue, generateSolidStyle } from 'lib/helpers'; 5 | 6 | import Area from '../Area'; 7 | import Preview from '../Preview'; 8 | 9 | function Solid({ 10 | red, 11 | green, 12 | blue, 13 | alpha, 14 | onChange, 15 | onStartChange, 16 | onEndChange, 17 | }) { 18 | const [colorRed, setColorRed] = useState(red); 19 | const [colorGreen, setColorGreen] = useState(green); 20 | const [colorBlue, setColorBlue] = useState(blue); 21 | const [colorAlpha, setColorAlpha] = useState(alpha); 22 | const [colorHue, setColorHue] = useState(0); 23 | const [colorSaturation, setColorSaturation] = useState(100); 24 | const [colorValue, setColorValue] = useState(100); 25 | 26 | const actions = { 27 | onChange, 28 | onStartChange, 29 | onEndChange, 30 | }; 31 | 32 | useMount(() => { 33 | const { hue, saturation, value } = rgbToHsv({ red: colorRed, green: colorGreen, blue: colorBlue }); 34 | 35 | setColorHue(hue); 36 | setColorSaturation(saturation); 37 | setColorValue(value); 38 | }); 39 | 40 | const updateColor = useCallback(({ 41 | red, green, blue, alpha, hue, saturation, value, 42 | }, actionName = 'onChange') => { 43 | red = getRightValue(red, colorRed); 44 | green = getRightValue(green, colorGreen); 45 | blue = getRightValue(blue, colorBlue); 46 | alpha = getRightValue(alpha, colorAlpha); 47 | hue = getRightValue(hue, colorHue); 48 | saturation = getRightValue(saturation, colorSaturation); 49 | value = getRightValue(value, colorValue); 50 | 51 | setColorRed(red); 52 | setColorGreen(green); 53 | setColorBlue(blue); 54 | setColorAlpha(alpha); 55 | setColorHue(hue); 56 | setColorSaturation(saturation); 57 | setColorValue(value); 58 | 59 | const action = actions[actionName]; 60 | 61 | action && action({ 62 | red, 63 | green, 64 | blue, 65 | alpha, 66 | hue, 67 | saturation, 68 | value, 69 | style: generateSolidStyle(red, green, blue, alpha), 70 | }); 71 | }, [ 72 | colorRed, colorGreen, colorBlue, colorAlpha, 73 | colorHue, colorSaturation, colorValue, 74 | actions, 75 | ]); 76 | 77 | return ( 78 | <> 79 | 89 | 96 | 97 | ); 98 | } 99 | 100 | Solid.defaultProps = { 101 | red: 255, 102 | green: 0, 103 | blue: 0, 104 | alpha: 1, 105 | }; 106 | 107 | export default Solid; 108 | -------------------------------------------------------------------------------- /src/lib/components/ColorPicker/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Solid from './Solid'; 4 | import Gradient from './Gradient'; 5 | 6 | function ColorPicker({ 7 | color, 8 | isGradient, 9 | gradient, 10 | onStartChange, 11 | onChange, 12 | onEndChange, 13 | }) { 14 | return ( 15 |
16 | { 17 | isGradient 18 | ? ( 19 | 28 | ) 29 | : ( 30 | 39 | ) 40 | } 41 |
42 | ); 43 | } 44 | 45 | ColorPicker.defaultProps = { 46 | isGradient: false, 47 | onChange: () => {}, 48 | color: { 49 | red: 255, 50 | green: 0, 51 | blue: 0, 52 | alpha: 1, 53 | }, 54 | gradient: { 55 | points: [ 56 | { 57 | left: 0, 58 | red: 0, 59 | green: 0, 60 | blue: 0, 61 | alpha: 1, 62 | }, 63 | { 64 | left: 100, 65 | red: 255, 66 | green: 0, 67 | blue: 0, 68 | alpha: 1, 69 | }, 70 | ], 71 | degree: 0, 72 | type: 'linear', 73 | }, 74 | }; 75 | 76 | export default ColorPicker; 77 | -------------------------------------------------------------------------------- /src/lib/components/UI/Input/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.scss'; 3 | 4 | function Input({ 5 | value, label, type = 'text', onChange, onFocus, onBlur, classes, 6 | }) { 7 | return ( 8 |
9 |
10 | 17 |
18 |
19 | {label} 20 |
21 |
22 | ); 23 | } 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /src/lib/components/UI/Input/index.scss: -------------------------------------------------------------------------------- 1 | .input-field { 2 | display: flex; 3 | flex-shrink: 0; 4 | align-items: center; 5 | flex-direction: column; 6 | 7 | .label { 8 | font-size: 12px; 9 | line-height: 15px; 10 | font-weight: 600; 11 | margin-top: 6px; 12 | margin-bottom: 0; 13 | color: #1F2667; 14 | } 15 | 16 | .input-container { 17 | display: flex; 18 | align-items: center; 19 | position: relative; 20 | width: 100%; 21 | border-radius: 6px; 22 | color: #28314d; 23 | 24 | .input { 25 | width: 100%; 26 | outline: 0; 27 | color: #1F2667; 28 | border-radius: inherit; 29 | border: 1px solid #bbbfc5; 30 | height: 24px; 31 | font-size: 12px; 32 | font-weight: 600; 33 | padding: 0 6px; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/components/UI/index.jsx: -------------------------------------------------------------------------------- 1 | export * from './Input'; 2 | -------------------------------------------------------------------------------- /src/lib/helpers/calculateDegree.js: -------------------------------------------------------------------------------- 1 | export default function calculateDegree(x, y, centerX, centerY) { 2 | const radians = Math.atan2(x - centerX, y - centerY); 3 | return (radians * (180 / Math.PI) * -1) + 180; 4 | } 5 | -------------------------------------------------------------------------------- /src/lib/helpers/changePicker.js: -------------------------------------------------------------------------------- 1 | import hsvToRgb from './hsvToRgb'; 2 | 3 | export default function changePicker(x, y, height, width, hue) { 4 | if (x > width) x = width; 5 | if (y > height) y = height; 6 | if (x < 0) x = 0; 7 | if (y < 0) y = 0; 8 | const value = 100 - (y * 100 / height) | 0; 9 | const saturation = x * 100 / width | 0; 10 | return { 11 | ...hsvToRgb(hue, saturation, value), 12 | saturation, 13 | value, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/helpers/generateStyles.js: -------------------------------------------------------------------------------- 1 | export function generateSolidStyle(red, green, blue, alpha) { 2 | return `rgba(${red}, ${green}, ${blue}, ${alpha})`; 3 | } 4 | 5 | export function generateGradientStyle(points, type, degree) { 6 | let style = ''; 7 | const sortedPoints = points.slice(); 8 | 9 | sortedPoints.sort((a, b) => a.left - b.left); 10 | 11 | if (type === 'linear') { 12 | style = `linear-gradient(${degree}deg,`; 13 | } else { 14 | style = 'radial-gradient('; 15 | } 16 | 17 | sortedPoints.forEach((point, index) => { 18 | style += `rgba(${point.red}, ${point.green}, ${point.blue}, ${point.alpha}) ${point.left}%`; 19 | 20 | if (index !== sortedPoints.length - 1) { 21 | style += ','; 22 | } 23 | }); 24 | 25 | style += ')'; 26 | return style; 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/helpers/getAlpha.js: -------------------------------------------------------------------------------- 1 | export default function getAlpha(value, width) { 2 | value = Number((value / width).toFixed(2)); 3 | 4 | return value > 1 ? 1 : value < 0 ? 0 : value; 5 | } 6 | -------------------------------------------------------------------------------- /src/lib/helpers/getHue.js: -------------------------------------------------------------------------------- 1 | import { hsvToRgb } from './index'; 2 | 3 | export default function getHue(offsetX, width, saturation, value) { 4 | let hue = ((360 * offsetX) / width) | 0; 5 | 6 | hue = hue < 0 ? 0 : hue > 360 ? 360 : hue; 7 | 8 | return { 9 | ...hsvToRgb(hue, saturation, value), 10 | hue, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/helpers/getRgbByHue.js: -------------------------------------------------------------------------------- 1 | export default function getRgbByHue(hue) { 2 | let C = 1; 3 | const H = hue / 60; 4 | let X = C * (1 - Math.abs(H % 2 - 1)); 5 | const m = 0; 6 | const precision = 255; 7 | let r = 0; 8 | let g = 0; 9 | let b = 0; 10 | 11 | C = (C + m) * precision | 0; 12 | X = (X + m) * precision | 0; 13 | 14 | if (H >= 0 && H < 1) { 15 | r = C | 0; 16 | g = X | 0; 17 | b = m | 0; 18 | } 19 | if (H >= 1 && H < 2) { 20 | r = X | 0; 21 | g = C | 0; 22 | b = m | 0; 23 | } 24 | if (H >= 2 && H < 3) { 25 | r = m | 0; 26 | g = C | 0; 27 | b = X | 0; 28 | } 29 | if (H >= 3 && H < 4) { 30 | r = m | 0; 31 | g = X | 0; 32 | b = C | 0; 33 | } 34 | if (H >= 4 && H < 5) { 35 | r = X | 0; 36 | g = m | 0; 37 | b = C | 0; 38 | } 39 | if (H >= 5 && H <= 6) { 40 | r = C | 0; 41 | g = m | 0; 42 | b = X | 0; 43 | } 44 | 45 | return { 46 | red: r, 47 | green: g, 48 | blue: b, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/helpers/getRightValue.js: -------------------------------------------------------------------------------- 1 | export default function getRightValue(newValue, oldValue) { 2 | return (!newValue && newValue !== 0) ? oldValue : newValue; 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/helpers/hexToRgb.js: -------------------------------------------------------------------------------- 1 | import { rgbToHsv, setRgba } from './index'; 2 | 3 | const hexRegexp = /(^#{0,1}[0-9A-F]{6}$)|(^#{0,1}[0-9A-F]{3}$)|(^#{0,1}[0-9A-F]{8}$)/i; 4 | 5 | const regexp = /([0-9A-F])([0-9A-F])([0-9A-F])/i; 6 | 7 | export default function hexToRgb(value) { 8 | const valid = hexRegexp.test(value); 9 | 10 | if (valid) { 11 | if (value[0] === '#') value = value.slice(1, value.length); 12 | 13 | if (value.length === 3) value = value.replace(regexp, '$1$1$2$2$3$3'); 14 | 15 | const red = parseInt(value.substr(0, 2), 16); 16 | const green = parseInt(value.substr(2, 2), 16); 17 | const blue = parseInt(value.substr(4, 2), 16); 18 | const alpha = parseInt(value.substr(6, 2), 16) / 255; 19 | 20 | const color = setRgba(red, green, blue, alpha); 21 | const hsv = rgbToHsv({ ...color }); 22 | 23 | return { 24 | ...color, 25 | ...hsv, 26 | }; 27 | } 28 | 29 | return false; 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/helpers/hsvToRgb.js: -------------------------------------------------------------------------------- 1 | import setRGBA from './setRgba'; 2 | 3 | export default function hsvToRgb(hue, saturation, value) { 4 | value /= 100; 5 | const sat = saturation / 100; 6 | let C = sat * value; 7 | const H = hue / 60; 8 | let X = C * (1 - Math.abs(H % 2 - 1)); 9 | let m = value - C; 10 | const precision = 255; 11 | 12 | C = (C + m) * precision | 0; 13 | X = (X + m) * precision | 0; 14 | m = m * precision | 0; 15 | 16 | if (H >= 1 && H < 2) { 17 | return setRGBA(X, C, m); 18 | } 19 | if (H >= 2 && H < 3) { 20 | return setRGBA(m, C, X); 21 | } 22 | if (H >= 3 && H < 4) { 23 | return setRGBA(m, X, C); 24 | } 25 | if (H >= 4 && H < 5) { 26 | return setRGBA(X, m, C); 27 | } 28 | if (H >= 5 && H <= 6) { 29 | return setRGBA(C, m, X); 30 | } 31 | 32 | return setRGBA(C, X, m); 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as rgbToHsv } from './rgbToHsv'; 2 | export { default as getRgbByHue } from './getRgbByHue'; 3 | export { default as changePicker } from './changePicker'; 4 | export { default as hsvToRgb } from './hsvToRgb'; 5 | export { default as getHue } from './getHue'; 6 | export { default as getAlpha } from './getAlpha'; 7 | export { default as rgbToHex } from './rgbToHex'; 8 | export { default as hexToRgb } from './hexToRgb'; 9 | export { default as setRgba } from './setRgba'; 10 | export { default as updateGradientActivePercent } from './updateGradientActivePercent'; 11 | export { default as calculateDegree } from './calculateDegree'; 12 | export { default as getRightValue } from './getRightValue'; 13 | export * from './generateStyles'; 14 | -------------------------------------------------------------------------------- /src/lib/helpers/rgbToHex.js: -------------------------------------------------------------------------------- 1 | export default function rgbToHex(red, green, blue) { 2 | let r16 = red.toString(16); 3 | let g16 = green.toString(16); 4 | let b16 = blue.toString(16); 5 | 6 | if (red < 16) r16 = `0${r16}`; 7 | if (green < 16) g16 = `0${g16}`; 8 | if (blue < 16) b16 = `0${b16}`; 9 | 10 | return r16 + g16 + b16; 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/helpers/rgbToHsv.js: -------------------------------------------------------------------------------- 1 | export default function rgbToHSv({ red, green, blue }) { 2 | let rr; 3 | let gg; 4 | let bb; 5 | let h; 6 | let s; 7 | 8 | const rabs = red / 255; 9 | const gabs = green / 255; 10 | const babs = blue / 255; 11 | const v = Math.max(rabs, gabs, babs); 12 | const diff = v - Math.min(rabs, gabs, babs); 13 | const diffc = c => (v - c) / 6 / diff + 1 / 2; 14 | if (diff === 0) { 15 | h = 0; 16 | s = 0; 17 | } else { 18 | s = diff / v; 19 | rr = diffc(rabs); 20 | gg = diffc(gabs); 21 | bb = diffc(babs); 22 | 23 | if (rabs === v) { 24 | h = bb - gg; 25 | } else if (gabs === v) { 26 | h = (1 / 3) + rr - bb; 27 | } else if (babs === v) { 28 | h = (2 / 3) + gg - rr; 29 | } 30 | if (h < 0) { 31 | h += 1; 32 | } else if (h > 1) { 33 | h -= 1; 34 | } 35 | } 36 | 37 | return { 38 | hue: Math.round(h * 360), 39 | saturation: Math.round(s * 100), 40 | value: Math.round(v * 100), 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/helpers/setRgba.js: -------------------------------------------------------------------------------- 1 | function isValidRGBValue(value) { 2 | return (typeof (value) === 'number' && Number.isNaN(value) === false && value >= 0 && value <= 255); 3 | } 4 | 5 | export default function setRGBA(red, green, blue, alpha) { 6 | if (isValidRGBValue(red) && isValidRGBValue(green) && isValidRGBValue(blue)) { 7 | const color = { 8 | red: red | 0, 9 | green: green | 0, 10 | blue: blue | 0, 11 | }; 12 | 13 | if (isValidRGBValue(alpha) === true) { 14 | color.alpha = alpha | 0; 15 | } 16 | 17 | // RGBToHSL(color.r, color.g, color.b); 18 | 19 | return color; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/helpers/updateGradientActivePercent.js: -------------------------------------------------------------------------------- 1 | export default function updateGradientActivePercent(offsetX, width) { 2 | const leftPercent = (offsetX * 100) / width; 3 | return leftPercent < 0 ? 0 : leftPercent > 100 ? 100 : leftPercent; 4 | } 5 | -------------------------------------------------------------------------------- /src/lib/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useMouseEvents } from './mouseEvents'; 2 | export { default as useMount } from './useMount'; 3 | -------------------------------------------------------------------------------- /src/lib/hooks/mouseEvents.js: -------------------------------------------------------------------------------- 1 | export default function useMouseEvents(mouseDownHandler, mouseMoveHandler, mouseUpHandler) { 2 | return function mouseEventsHandler(event) { 3 | let positions = mouseDownHandler(event); 4 | 5 | function onMouseMove(event) { 6 | positions = mouseMoveHandler(event, positions) || positions; 7 | } 8 | 9 | window.addEventListener('mousemove', onMouseMove); 10 | 11 | window.addEventListener('mouseup', event => { 12 | window.removeEventListener('mousemove', onMouseMove); 13 | 14 | mouseUpHandler && mouseUpHandler(event, positions); 15 | }, { once: true }); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/hooks/useMount.js: -------------------------------------------------------------------------------- 1 | /* eslint react-hooks/exhaustive-deps: 0 */ 2 | 3 | import { useEffect } from 'react'; 4 | 5 | const useMount = effect => useEffect(effect, []); 6 | 7 | export default useMount; 8 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | 3 | export { default as ColorPicker } from './components/ColorPicker'; 4 | -------------------------------------------------------------------------------- /src/lib/index.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .ui-color-picker { 6 | margin: 8px; 7 | background-color: #FFFFFF; 8 | display: flex; 9 | flex-direction: column; 10 | width: 280px; 11 | user-select: none; 12 | 13 | .gradient-controls { 14 | display: flex; 15 | flex-direction: row; 16 | align-items: center; 17 | width: 100%; 18 | margin-bottom: 8px; 19 | margin-top: 5px; 20 | height: 24px; 21 | padding: 0 16px; 22 | 23 | .gradient-type { 24 | flex: 1; 25 | display: flex; 26 | 27 | .gradient-type-item { 28 | height: 28px; 29 | width: 28px; 30 | border-radius: 50%; 31 | position: relative; 32 | cursor: pointer; 33 | 34 | &.active { 35 | &::after { 36 | content: ''; 37 | display: block; 38 | position: absolute; 39 | top: -3px; 40 | bottom: -3px; 41 | left: -3px; 42 | right: -3px; 43 | border: 2px solid #1F2667; 44 | border-radius: 50%; 45 | } 46 | } 47 | 48 | &.liner-gradient { 49 | background: linear-gradient(270deg, #FFFFFF 0%, #1F2667 100%); 50 | } 51 | 52 | &.radial-gradient { 53 | margin-left: 8px; 54 | background: radial-gradient(circle, #FFFFFF 0%, #1F2667 100%); 55 | } 56 | } 57 | 58 | } 59 | 60 | .gradient-degrees-options { 61 | position: relative; 62 | 63 | .gradient-degrees { 64 | display: -ms-flexbox; 65 | display: flex; 66 | -webkit-box-pack: center; 67 | -ms-flex-pack: center; 68 | justify-content: center; 69 | -webkit-box-align: center; 70 | -ms-flex-align: center; 71 | align-items: center; 72 | position: relative; 73 | width: 28px; 74 | height: 28px; 75 | border: 3px solid #1F2667; 76 | border-radius: 18px; 77 | margin-right: 54px; 78 | 79 | .gradient-degree-center { 80 | position: relative; 81 | width: 6px; 82 | height: 22px; 83 | pointer-events: none; 84 | 85 | .gradient-degree-pointer { 86 | position: absolute; 87 | width: 6px; 88 | height: 6px; 89 | top: 2px; 90 | border-radius: 3px; 91 | background: #1F2667; 92 | } 93 | } 94 | } 95 | 96 | .gradient-degree-value { 97 | position: absolute; 98 | top: 0; 99 | right: 0; 100 | width: 48px; 101 | height: 28px; 102 | display: flex; 103 | align-items: center; 104 | justify-content: center; 105 | border: 1px solid #bbbfc5; 106 | border-radius: 6px; 107 | 108 | p { 109 | text-align: center; 110 | padding: 0 6px; 111 | } 112 | } 113 | } 114 | } 115 | 116 | .picker-area { 117 | display: flex; 118 | flex-direction: column; 119 | padding: 0 16px; 120 | 121 | &.gradient-tab { 122 | .picking-area { 123 | margin-bottom: 10px; 124 | } 125 | } 126 | 127 | .picking-area { 128 | width: 100%; 129 | height: 160px; 130 | margin-bottom: 16px; 131 | position: relative; 132 | border-radius: 8px; 133 | 134 | &:hover { 135 | cursor: default; 136 | } 137 | 138 | .picking-area-overlay1 { 139 | height: 100%; 140 | width: 100%; 141 | background: linear-gradient(to right, white 0%, rgba(255, 255, 255, 0) 100%); 142 | border-radius: 3px; 143 | 144 | .picking-area-overlay2 { 145 | height: 100%; 146 | width: 100%; 147 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, black 100%); 148 | border-radius: 8px; 149 | } 150 | } 151 | } 152 | 153 | .preview { 154 | display: flex; 155 | flex-direction: row; 156 | margin-bottom: 16px; 157 | 158 | .preview-box { 159 | box-sizing: border-box; 160 | height: 36px; 161 | width: 36px; 162 | border-radius: 8px; 163 | border: 1px solid #EBEDF5; 164 | } 165 | 166 | .color-hue-alpha { 167 | display: flex; 168 | flex-direction: column; 169 | flex: 1; 170 | margin-left: 6px; 171 | 172 | .hue { 173 | width: 100%; 174 | position: relative; 175 | border-radius: 10px; 176 | margin-bottom: 8px; 177 | padding: 0 7px; 178 | background-color: red; 179 | 180 | .hue-area { 181 | position: relative; 182 | height: 14px; 183 | background: -webkit-linear-gradient(left, #ff0000, #ff0080, #ff00ff, #8000ff, #0000ff, #0080ff, #00ffff, #00ff80, #00ff00, #80ff00, #ffff00, #ff8000, #ff0000); 184 | background: -o-linear-gradient(left, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080, #ff0000); 185 | background: -ms-linear-gradient(left, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080, #ff0000); 186 | background: -moz-linear-gradient(left, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080, #ff0000); 187 | background: linear-gradient(to right, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080, #ff0000); 188 | } 189 | } 190 | 191 | .alpha { 192 | position: relative; 193 | width: 100%; 194 | overflow: hidden; 195 | border-radius: 10px; 196 | height: 14px; 197 | cursor: pointer; 198 | 199 | .gradient { 200 | position: absolute; 201 | top: 0; 202 | left: 0; 203 | right: 0; 204 | bottom: 0; 205 | } 206 | 207 | .alpha-area { 208 | width: 100%; 209 | height: 100%; 210 | background: url("assets/images/alpha-background.svg"); 211 | background-size: auto; 212 | background-position: 50% 50%; 213 | border-radius: 10px; 214 | padding: 0 7px; 215 | 216 | .alpha-mask { 217 | width: 100%; 218 | height: 100%; 219 | position: relative; 220 | } 221 | } 222 | } 223 | } 224 | } 225 | 226 | .gradient { 227 | width: 100%; 228 | height: 14px; 229 | position: relative; 230 | cursor: pointer; 231 | border-radius: 10px; 232 | margin-bottom: 8px; 233 | padding: 0 7px; 234 | 235 | .gradient-slider-container { 236 | height: 100%; 237 | width: 100%; 238 | position: relative; 239 | } 240 | } 241 | 242 | .picker-pointer { 243 | position: absolute; 244 | top: 1px; 245 | height: 12px; 246 | width: 12px; 247 | border: 1px solid #FFFFFF; 248 | background: transparent; 249 | border-radius: 50%; 250 | box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.3); 251 | 252 | .child-point { 253 | position: absolute; 254 | top: 50%; 255 | left: 50%; 256 | transform: translate(-50%, -50%); 257 | height: 3px; 258 | width: 3px; 259 | background: #FFFFFF; 260 | border-radius: 50%; 261 | opacity: 0; 262 | transition: opacity 0.33s; 263 | 264 | &.active { 265 | opacity: 1; 266 | } 267 | } 268 | } 269 | } 270 | 271 | .color-preview-area { 272 | margin-bottom: 8px; 273 | padding: 0 16px; 274 | 275 | .input-group { 276 | width: 100%; 277 | display: flex; 278 | flex-direction: row; 279 | justify-content: space-between; 280 | 281 | .uc-field-group:not(:last-child) { 282 | margin-right: 7px; 283 | } 284 | } 285 | 286 | .hex { 287 | width: 65px; 288 | } 289 | 290 | .rgb { 291 | width: 40px; 292 | } 293 | } 294 | 295 | .colors { 296 | padding: 3px 16px 0; 297 | 298 | .colors-label { 299 | display: flex; 300 | align-items: center; 301 | margin-bottom: 4px; 302 | cursor: pointer; 303 | 304 | .uc-icon { 305 | margin-right: 8px; 306 | transition: transform 0.3s; 307 | } 308 | 309 | .tp-text { 310 | text-transform: uppercase; 311 | } 312 | 313 | &.show { 314 | & + .colors-item-container { 315 | max-height: 80px; 316 | padding-bottom: 16px; 317 | } 318 | 319 | .uc-icon { 320 | transform: rotate(-90deg); 321 | } 322 | } 323 | } 324 | 325 | .template { 326 | display: flex; 327 | flex-direction: column; 328 | } 329 | 330 | .global { 331 | display: flex; 332 | flex-direction: column; 333 | align-items: flex-start; 334 | } 335 | 336 | .colors-item-container { 337 | display: flex; 338 | flex-wrap: wrap; 339 | width: 100%; 340 | transition: max-height 0.3s, padding-bottom 0.3s; 341 | max-height: 0; 342 | overflow: hidden; 343 | 344 | .colors-item { 345 | height: 24px; 346 | width: 24px; 347 | border-radius: 50%; 348 | background-color: #EBEDF5; 349 | cursor: pointer; 350 | position: relative; 351 | margin-top: 4px; 352 | flex-shrink: 0; 353 | 354 | &:not(.plus) { 355 | border: 1px solid #EBEDF5; 356 | } 357 | 358 | &.empty { 359 | display: flex; 360 | align-items: center; 361 | justify-content: center; 362 | 363 | .line { 364 | width: 2px; 365 | height: 16px; 366 | background-color: #8892B3; 367 | border-radius: 1px; 368 | transform: rotate(45deg); 369 | } 370 | } 371 | 372 | &.plus { 373 | margin-bottom: 4px; 374 | 375 | .uc-icon { 376 | position: absolute; 377 | top: 50%; 378 | left: 50%; 379 | transform: translate(-50%, -50%); 380 | opacity: 1; 381 | } 382 | } 383 | 384 | &:not(:first-child):not(:nth-child(9)) { 385 | margin-left: 8px; 386 | } 387 | 388 | .uc-icon { 389 | position: absolute; 390 | right: -8px; 391 | top: -3px; 392 | opacity: 0; 393 | transition: opacity 0.3s; 394 | } 395 | 396 | &:hover { 397 | .uc-icon { 398 | opacity: 1; 399 | } 400 | } 401 | 402 | &.active { 403 | &::after { 404 | content: ''; 405 | display: block; 406 | position: absolute; 407 | top: -3px; 408 | bottom: -3px; 409 | left: -3px; 410 | right: -3px; 411 | border: 2px solid #8892B3; 412 | border-radius: 50%; 413 | } 414 | } 415 | } 416 | } 417 | } 418 | } 419 | --------------------------------------------------------------------------------