diff --git a/dev-test/config.yml b/dev-test/config.yml
index 04cf85e8..46ae0230 100644
--- a/dev-test/config.yml
+++ b/dev-test/config.yml
@@ -119,6 +119,14 @@ collections: # A list of collections the CMS should be able to edit
- { label: 'Markdown', name: 'markdown', widget: 'markdown' }
- { label: 'Datetime', name: 'datetime', widget: 'datetime' }
- { label: 'Date', name: 'date', widget: 'date' }
+ - { label: 'Color', name: 'color', widget: 'color' }
+ - {
+ label: 'Color string editable and alpha enabled',
+ name: 'colorEditable',
+ widget: 'color',
+ enableAlpha: true,
+ allowInput: true,
+ }
- { label: 'Image', name: 'image', widget: 'image' }
- { label: 'File', name: 'file', widget: 'file' }
- { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] }
diff --git a/packages/netlify-cms-app/package.json b/packages/netlify-cms-app/package.json
index d72105a1..828a17b7 100644
--- a/packages/netlify-cms-app/package.json
+++ b/packages/netlify-cms-app/package.json
@@ -45,6 +45,7 @@
"netlify-cms-ui-default": "^2.11.6",
"netlify-cms-widget-boolean": "^2.3.4",
"netlify-cms-widget-code": "^1.2.4",
+ "netlify-cms-widget-color": "^1.0.0",
"netlify-cms-widget-date": "^2.5.5",
"netlify-cms-widget-datetime": "^2.6.5",
"netlify-cms-widget-file": "^2.7.4",
diff --git a/packages/netlify-cms-app/src/extensions.js b/packages/netlify-cms-app/src/extensions.js
index 7fec7d82..bd115d8c 100644
--- a/packages/netlify-cms-app/src/extensions.js
+++ b/packages/netlify-cms-app/src/extensions.js
@@ -25,6 +25,7 @@ import NetlifyCmsWidgetMap from 'netlify-cms-widget-map';
import NetlifyCmsWidgetDate from 'netlify-cms-widget-date';
import NetlifyCmsWidgetDatetime from 'netlify-cms-widget-datetime';
import NetlifyCmsWidgetCode from 'netlify-cms-widget-code';
+import NetlifyCmsWidgetColor from 'netlify-cms-widget-color';
// Editor Components
import image from 'netlify-cms-editor-component-image';
@@ -55,6 +56,7 @@ CMS.registerWidget([
NetlifyCmsWidgetDate.Widget(),
NetlifyCmsWidgetDatetime.Widget(),
NetlifyCmsWidgetCode.Widget(),
+ NetlifyCmsWidgetColor.Widget(),
]);
CMS.registerEditorComponent(image);
CMS.registerEditorComponent({
diff --git a/packages/netlify-cms-widget-color/CHANGELOG.md b/packages/netlify-cms-widget-color/CHANGELOG.md
new file mode 100644
index 00000000..e4d87c4d
--- /dev/null
+++ b/packages/netlify-cms-widget-color/CHANGELOG.md
@@ -0,0 +1,4 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
diff --git a/packages/netlify-cms-widget-color/README.md b/packages/netlify-cms-widget-color/README.md
new file mode 100644
index 00000000..c4f6f5ae
--- /dev/null
+++ b/packages/netlify-cms-widget-color/README.md
@@ -0,0 +1,11 @@
+# Docs coming soon!
+
+Netlify CMS was recently converted from a single npm package to a "monorepo" of over 20 packages.
+That's over 20 Readme's! We haven't created one for this package yet, but we will soon.
+
+In the meantime, you can:
+
+1. Check out the [main readme](https://github.com/netlify/netlify-cms/#readme) or the [documentation
+ site](https://www.netlifycms.org) for more info.
+2. Reach out to the [community chat](https://netlifycms.org/chat/) if you need help.
+3. Help out and [write the readme yourself](https://github.com/netlify/netlify-cms/edit/master/packages/netlify-cms-widget-string/README.md)!
diff --git a/packages/netlify-cms-widget-color/package.json b/packages/netlify-cms-widget-color/package.json
new file mode 100644
index 00000000..533f6428
--- /dev/null
+++ b/packages/netlify-cms-widget-color/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "netlify-cms-widget-color",
+ "description": "Widget for editing color strings in Netlify CMS.",
+ "version": "1.0.0",
+ "homepage": "https://www.netlifycms.org/docs/widgets/#color",
+ "repository": "https://github.com/netlify/netlify-cms/tree/master/packages/netlify-cms-widget-color",
+ "bugs": "https://github.com/netlify/netlify-cms/issues",
+ "module": "dist/esm/index.js",
+ "main": "dist/netlify-cms-widget-color.js",
+ "license": "MIT",
+ "keywords": [
+ "netlify",
+ "netlify-cms",
+ "widget",
+ "color"
+ ],
+ "sideEffects": false,
+ "scripts": {
+ "develop": "yarn build:esm --watch",
+ "build": "cross-env NODE_ENV=production webpack",
+ "build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward"
+ },
+ "dependencies": {
+ "react-color": "^2.18.1",
+ "validate-color": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@emotion/core": "^10.0.35",
+ "@emotion/styled": "^10.0.27",
+ "netlify-cms-ui-default": "^2.6.0",
+ "prop-types": "^15.7.2",
+ "react": "^16.8.4"
+ }
+}
diff --git a/packages/netlify-cms-widget-color/src/ColorControl.js b/packages/netlify-cms-widget-color/src/ColorControl.js
new file mode 100644
index 00000000..9f5ee416
--- /dev/null
+++ b/packages/netlify-cms-widget-color/src/ColorControl.js
@@ -0,0 +1,176 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled from '@emotion/styled';
+import ChromePicker from 'react-color';
+import validateColor from 'validate-color';
+import { zIndex } from 'netlify-cms-ui-default';
+
+const ClearIcon = () => (
+
+);
+
+const ClearButton = styled.div`
+ position: absolute;
+ right: 6px;
+ z-index: ${zIndex.zIndex1000};
+ padding: 8px;
+ margin-top: 11px;
+`;
+
+const ClearButtonWrapper = styled.div`
+ position: relative;
+ width: 100%;
+`;
+
+// color swatch background with checkerboard to display behind transparent colors
+const ColorSwatchBackground = styled.div`
+ position: absolute;
+ z-index: ${zIndex.zIndex1};
+ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==');
+ height: 38px;
+ width: 48px;
+ margin-top: 10px;
+ margin-left: 10px;
+ border-radius: 5px;
+`;
+
+const ColorSwatch = styled.div`
+ position: absolute;
+ z-index: ${zIndex.zIndex2};
+ background: ${props => props.background};
+ cursor: pointer;
+ height: 38px;
+ width: 48px;
+ margin-top: 10px;
+ margin-left: 10px;
+ border-radius: 5px;
+ border: 2px solid rgb(223, 223, 227);
+ text-align: center;
+ font-size: 27px;
+ line-height: 1;
+ padding-top: 4px;
+ user-select: none;
+ color: ${props => props.color};
+`;
+
+const ColorPickerContainer = styled.div`
+ position: absolute;
+ z-index: ${zIndex.zIndex1000};
+ margin-top: 48px;
+ margin-left: 12px;
+`;
+
+// fullscreen div to close color picker when clicking outside of picker
+const ClickOutsideDiv = styled.div`
+ position: fixed;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ left: 0px;
+`;
+
+export default class ColorControl extends React.Component {
+ static propTypes = {
+ onChange: PropTypes.func.isRequired,
+ forID: PropTypes.string,
+ value: PropTypes.node,
+ classNameWrapper: PropTypes.string.isRequired,
+ setActiveStyle: PropTypes.func.isRequired,
+ setInactiveStyle: PropTypes.func.isRequired,
+ };
+
+ static defaultProps = {
+ value: '',
+ };
+
+ state = {
+ showColorPicker: false,
+ };
+ // show/hide color picker
+ handleClick = () => {
+ this.setState({ showColorPicker: !this.state.showColorPicker });
+ };
+ handleClear = () => {
+ this.props.onChange('');
+ };
+ handleClose = () => {
+ this.setState({ showColorPicker: false });
+ };
+ handleChange = color => {
+ const formattedColor =
+ color.rgb.a < 1
+ ? `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`
+ : color.hex;
+ this.props.onChange(formattedColor);
+ };
+ render() {
+ const {
+ forID,
+ value,
+ field,
+ onChange,
+ classNameWrapper,
+ setActiveStyle,
+ setInactiveStyle,
+ } = this.props;
+
+ const allowInput = field.get('allowInput', false);
+
+ // clear button is not displayed if allowInput: true
+ const showClearButton = !allowInput && value;
+
+ return (
+ <>
+ {' '}
+ {showClearButton && (
+
+
+
+
+
+ )}
+
+
+ ?
+
+ {this.state.showColorPicker && (
+
+
+
+
+ )}
+ onChange(e.target.value)}
+ onFocus={setActiveStyle}
+ onBlur={setInactiveStyle}
+ style={{
+ paddingLeft: '75px',
+ paddingRight: '70px',
+ color: !allowInput && '#bbb',
+ }}
+ // make readonly and open color picker on click if set to allowInput: false
+ onClick={!allowInput ? this.handleClick : undefined}
+ readOnly={!allowInput}
+ />
+ >
+ );
+ }
+}
diff --git a/packages/netlify-cms-widget-color/src/ColorPreview.js b/packages/netlify-cms-widget-color/src/ColorPreview.js
new file mode 100644
index 00000000..bff43a4b
--- /dev/null
+++ b/packages/netlify-cms-widget-color/src/ColorPreview.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { WidgetPreviewContainer } from 'netlify-cms-ui-default';
+
+const ColorPreview = ({ value }) => {value};
+
+ColorPreview.propTypes = {
+ value: PropTypes.node,
+};
+
+export default ColorPreview;
diff --git a/packages/netlify-cms-widget-color/src/index.js b/packages/netlify-cms-widget-color/src/index.js
new file mode 100644
index 00000000..497fc932
--- /dev/null
+++ b/packages/netlify-cms-widget-color/src/index.js
@@ -0,0 +1,12 @@
+import controlComponent from './ColorControl';
+import previewComponent from './ColorPreview';
+
+const Widget = (opts = {}) => ({
+ name: 'color',
+ controlComponent,
+ previewComponent,
+ ...opts,
+});
+
+export const NetlifyCmsWidgetColor = { Widget, controlComponent, previewComponent };
+export default NetlifyCmsWidgetColor;
diff --git a/packages/netlify-cms-widget-color/webpack.config.js b/packages/netlify-cms-widget-color/webpack.config.js
new file mode 100644
index 00000000..42edd361
--- /dev/null
+++ b/packages/netlify-cms-widget-color/webpack.config.js
@@ -0,0 +1,3 @@
+const { getConfig } = require('../../scripts/webpack.js');
+
+module.exports = getConfig();
diff --git a/website/content/docs/widgets/color.md b/website/content/docs/widgets/color.md
new file mode 100644
index 00000000..97495c79
--- /dev/null
+++ b/website/content/docs/widgets/color.md
@@ -0,0 +1,22 @@
+---
+label: 'Color'
+title: color
+---
+
+The color widget translates a color picker to a color string.
+
+- **Name:** `color`
+- **UI:** color picker
+- **Data type:** string
+- **Options:**
+ - `default`: accepts a string; defaults to an empty string. Sets the default value
+ - `allowInput`: accepts a boolean, defaults to `false`. Allows manual editing of the color input value
+ - `enableAlpha`: accepts a boolean, defaults to `false`. Enables Alpha editing
+- **Example:**
+ ```yaml
+ - { label: 'Color', name: 'color', widget: 'color' }
+ ```
+- **Example:**
+ ```yaml
+ - { label: 'Color', name: 'color', widget: 'color', enableAlpha: true, allowInput: true }
+ ```
diff --git a/yarn.lock b/yarn.lock
index d1e76d31..d7358ef7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1527,6 +1527,11 @@
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
+"@icons/material@^0.2.4":
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
+ integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==
+
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -11706,7 +11711,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-lodash@^4.1.1, lodash@^4.11.2, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.1, lodash@~4.17.10:
+lodash@^4.0.1, lodash@^4.1.1, lodash@^4.11.2, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.1, lodash@~4.17.10:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
@@ -11939,6 +11944,11 @@ markdown-to-jsx@^6.11.4:
prop-types "^15.6.2"
unquote "^1.1.0"
+material-colors@^1.2.1:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
+ integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
+
mathml-tag-names@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
@@ -14335,6 +14345,18 @@ react-codemirror2@^7.0.0:
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-7.2.1.tgz#38dab492fcbe5fb8ebf5630e5bb7922db8d3a10c"
integrity sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==
+react-color@^2.18.1:
+ version "2.18.1"
+ resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.18.1.tgz#2cda8cc8e06a9e2c52ad391a30ddad31972472f4"
+ integrity sha512-X5XpyJS6ncplZs74ak0JJoqPi+33Nzpv5RYWWxn17bslih+X7OlgmfpmGC1fNvdkK7/SGWYf1JJdn7D2n5gSuQ==
+ dependencies:
+ "@icons/material" "^0.2.4"
+ lodash "^4.17.11"
+ material-colors "^1.2.1"
+ prop-types "^15.5.10"
+ reactcss "^1.2.0"
+ tinycolor2 "^1.4.1"
+
react-datetime@^2.16.3:
version "2.16.3"
resolved "https://registry.yarnpkg.com/react-datetime/-/react-datetime-2.16.3.tgz#7f9ac7d4014a939c11c761d0c22d1fb506cb505e"
@@ -14762,6 +14784,13 @@ react@^16.12.0, react@^16.8.3, react@^16.8.4:
object-assign "^4.1.1"
prop-types "^15.6.2"
+reactcss@^1.2.0:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
+ integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==
+ dependencies:
+ lodash "^4.0.1"
+
read-cmd-shim@^1.0.1:
version "1.0.5"
resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16"
@@ -17156,6 +17185,11 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3:
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
+tinycolor2@^1.4.1:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
+ integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
+
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -18052,6 +18086,11 @@ v8-to-istanbul@^6.0.1:
convert-source-map "^1.6.0"
source-map "^0.7.3"
+validate-color@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/validate-color/-/validate-color-2.1.0.tgz#ba32664db859a197c19520b50316b8b8d5ffa0bc"
+ integrity sha512-/78x9MDsanOisNfa4UxombQkPCE2A/hmFUtgunwGutpAgTRnhKvOhzJ4KeSDunbKEla6T+u0OlHd1hWCLVhSdA==
+
validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"