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"