UNPKG

@salesforce/design-system-react

Version:

Salesforce Lightning Design System for React

625 lines (547 loc) 27.2 kB
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /* eslint-disable max-lines */ /* eslint-disable react/sort-comp */ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import assign from 'lodash.assign'; import checkProps from './check-props'; import CustomColor from './private/custom-color'; import Swatch from './private/swatch'; import SwatchPicker from './private/swatch-picker'; import Button from '../button'; import Input from '../input'; import Tabs from '../tabs'; import TabsPanel from '../tabs/panel'; import Popover from '../popover'; import ColorUtils from '../../utilities/color'; import { COLOR_PICKER } from '../../utilities/constants'; import generateId from '../../utilities/generate-id'; import componentDoc from './component.json'; var propTypes = { /** * **Assistive text for accessibility** * * `label`: Visually hidden label but read out loud by screen readers. * * `hueSlider`: Instructions for hue selection input * * `saturationValueGrid`: Instructions for using the grid for saturation * and value selection */ assistiveText: PropTypes.shape({ label: PropTypes.string, hueSlider: PropTypes.string, saturationValueGrid: PropTypes.string }), /** * CSS classes to be added to tag with `.slds-color-picker`. Uses `classNames` [API](https://github.com/JedWatson/classnames). _Tested with snapshot testing._ */ className: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]), /** * CSS classes to be added to tag with `.slds-popover`. Uses `classNames` [API](https://github.com/JedWatson/classnames). _Tested with snapshot testing._ */ classNameMenu: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]), /** * Unique ID for component. */ id: PropTypes.string, /** * Disables the input and button. */ disabled: PropTypes.bool, /** * Message to display when the outer input is in an error state. When this is present, also visually highlights the component as in error. */ errorText: PropTypes.string, /** * Message to display when the custom tab input is in an error state. When this is present, also visually highlights the component as in error. */ errorTextWorkingColor: PropTypes.string, /** * Event Callbacks * * `onChange`: This function is triggered when done is clicked. This function returns `{event, { color: [string] }}`, which is a hex representation of the color. * * `onClose`: This function is triggered when the menu is closed. This function returns `{event, { trigger, componentWillUnmount }}`. Trigger can have the values `cancel`, `clickOutside`, or `newPopover`. * * `onOpen`: This function is triggered when the color-picker menu is mounted and added to the DOM. The parameters are `event, { portal: }`. `portal` can be used as a React tree root node. * * `onRequestClose`: This function is triggered when the user clicks outside the menu or clicks the close button. You will want to define this if color-picker is to be a controlled component. Most of the time you will want to set `isOpen` to `false` when this is triggered unless you need to validate something. * This function returns `{event, {trigger: [string]}}` where `trigger` is either `cancel` or `clickOutside`. * * `onRequestOpen`: Function called when the color-picker menu would like show. * * `onValidateColor`: Function that overwrites default color validator and called when validating HEX color on outer input change. If callback returns false, errorText is shown if set. * * `onValidateWorkingColor`: Function that overwrites default color validator and called when validating HEX color on custom tab inner input change. If callback returns false, errorTextWorkingColor is shown if set. * * `onWorkingColorChange`: This function is triggered when working color changes (color inside the custom tab). This function returns `{event, { color: [string] }}`, which is a hex representation of the color. * _Tested with Mocha framework._ */ events: PropTypes.shape({ onChange: PropTypes.func, onClose: PropTypes.func, onOpen: PropTypes.func, onRequestClose: PropTypes.func, onRequestOpen: PropTypes.func, onValidateColor: PropTypes.func, onValidateWorkingColor: PropTypes.func, onWorkingColorChange: PropTypes.func }), /** * By default, dialogs will flip their alignment (such as bottom to top) if they extend beyond a boundary element such as a scrolling parent or a window/viewpoint. `hasStaticAlignment` disables this behavior and allows this component to extend beyond boundary elements. _Not tested._ */ hasStaticAlignment: PropTypes.bool, /** * Hides the text input */ hideInput: PropTypes.bool, /** * Popover open state */ isOpen: PropTypes.bool, /** * **Text labels for internationalization** * * `blueAbbreviated`: One letter abbreviation of blue color component * * `cancelButton`: Text for cancel button on popover * * `customTab`: Text for custom tab of popover * * `customTabActiveWorkingColorSwatch`: Label for custom tab active working color swatch * * `customTabTransparentSwatch`: Label for custom tab active transparent swatch * * `greenAbbreviated`: One letter abbreviation of green color component * * `hexLabel`: Label for input of hexadecimal color * * `invalidColor`: Error message when hex color input is invalid * * `invalidComponent`: Error message when a component input is invalid * * `label`: An `input` label as for a `form` * * `redAbbreviated`: One letter abbreviation of red color component * * `swatchTab`: Label for swatch tab of popover * * `submitButton`: Text for submit/done button of popover */ labels: PropTypes.shape({ blueAbbreviated: PropTypes.string, cancelButton: PropTypes.string, customTab: PropTypes.string, customTabActiveWorkingColorSwatch: PropTypes.string, customTabTransparentSwatch: PropTypes.string, greenAbbreviated: PropTypes.string, hexLabel: PropTypes.string, invalidColor: PropTypes.string, invalidComponent: PropTypes.string, label: PropTypes.string, redAbbreviated: PropTypes.string, swatchTab: PropTypes.string, swatchTabTransparentSwatch: PropTypes.string, submitButton: PropTypes.string }), /** * Please select one of the following: * * `absolute` - (default) The dialog will use `position: absolute` and style attributes to position itself. This allows inverted placement or flipping of the dialog. * * `overflowBoundaryElement` - The dialog will overflow scrolling parents. Use on elements that are aligned to the left or right of their target and don't care about the target being within a scrolling parent. Typically this is a popover or tooltip. Dropdown menus can usually open up and down if no room exists. In order to achieve this a portal element will be created and attached to `body`. This element will render into that detached render tree. * * `relative` - No styling or portals will be used. Menus will be positioned relative to their triggers. This is a great choice for HTML snapshot testing. */ menuPosition: PropTypes.oneOf(['absolute', 'overflowBoundaryElement', 'relative']), /** * An array of hex color values which is used to set the options of the * swatch tab of the colorpicker popover. * To specify transparent, use empty string as a value. */ swatchColors: PropTypes.arrayOf(PropTypes.string), /** * Determines which tab is visible when dialog opens. Use this prop with `base` variant only. * Defaults to `swatch` tab. */ defaultSelectedTab: PropTypes.oneOf(['swatches', 'custom']), /** * Selects which tabs are present for the colorpicker. * * `base`: both swatches and custom tabs are present * * `swatches`: only swatch tab is present * * `custom`: only custom tab is present * _Tested with snapshot testing._ */ variant: PropTypes.oneOf(['base', 'swatches', 'custom']), /** * Current color in hexadecimal string, including # sign (eg: "#000000") */ value: PropTypes.string, /** * Current working color in hexadecimal string, including # sign (eg: "#000000") */ valueWorking: PropTypes.string }; var defaultProps = { assistiveText: { saturationValueGrid: 'Use arrow keys to select a saturation and brightness, on an x and y axis.', hueSlider: 'Select Hue' }, events: {}, labels: { blueAbbreviated: 'B', cancelButton: 'Cancel', customTab: 'Custom', customTabActiveWorkingColorSwatch: 'Working Color', customTabTransparentSwatch: 'Transparent Swatch', greenAbbreviated: 'G', hexLabel: 'Hex', invalidColor: 'The color entered is invalid', invalidComponent: 'The value needs to be an integer from 0-255', redAbbreviated: 'R', submitButton: 'Done', swatchTab: 'Default', swatchTabTransparentSwatch: 'Transparent Swatch' }, menuPosition: 'absolute', swatchColors: ['#e3abec', '#c2dbf7', '#9fd6ff', '#9de7da', '#9df0c0', '#fff099', '#fed49a', '#d073e0', '#86baf3', '#5ebbff', '#44d8be', '#3be282', '#ffe654', '#ffb758', '#bd35bd', '#5779c1', '#5679c0', '#00aea9', '#3cba4c', '#f5bc25', '#f99221', '#580d8c', '#001970', '#0a2399', '#0b7477', '#0b6b50', '#b67e11', '#b85d0d', ''], defaultSelectedTab: 'swatches', variant: 'base' }; /** * The Unified Color Picker component allows for a fully accessible and configurable color picker, allowing the user to pick from a set of predefined colors (swatches), or to pick a custom color using a HSB selection interface. It can be configured to show one or both of those color selection interfaces. View [component blueprint guidelines](https://lightningdesignsystem.com/components/color-picker/). */ var ColorPicker = /*#__PURE__*/function (_React$Component) { _inherits(ColorPicker, _React$Component); var _super = _createSuper(ColorPicker); function ColorPicker(props) { var _this; _classCallCheck(this, ColorPicker); _this = _super.call(this, props); _defineProperty(_assertThisInitialized(_this), "handleSwatchChange", function (event) { _this.setWorkingColor(event, { hex: event.target.value }); }); _defineProperty(_assertThisInitialized(_this), "handleOnRequestClose", function (event, _ref) { var trigger = _ref.trigger; if (trigger === 'clickOutside' || trigger === 'cancel') { _this.handleCancelState(); } if (_this.props.onRequestClose) { _this.props.onRequestClose(event, { trigger: trigger }); } }); _defineProperty(_assertThisInitialized(_this), "handleClickOutside", function (event) { _this.handleCancelButtonClick(event); }); _defineProperty(_assertThisInitialized(_this), "handleCancel", function (event) { _this.handleCancelState(); if (_this.props.onRequestClose) { _this.props.onRequestClose(event, { trigger: 'cancel' }); } }); _defineProperty(_assertThisInitialized(_this), "handleCancelState", function () { var workingColor = ColorUtils.getNewColor({ // eslint-disable-next-line react/no-access-state-in-setstate hex: _this.state.currentColor }, _this.props.events.onValidateWorkingColor); _this.setState({ isOpen: false, workingColor: workingColor, previousWorkingColor: workingColor }); }); _defineProperty(_assertThisInitialized(_this), "handleHexInputChange", function (event, _ref2) { var labels = _ref2.labels; var currentColor = event.target.value; var namedColorHex = ColorUtils.getHexFromNamedColor(currentColor); var isValid = false; if (_this.props.events.onValidateColor) { isValid = _this.props.events.onValidateColor(currentColor); } else { isValid = namedColorHex ? true : ColorUtils.isValidHex(currentColor); } _this.setState({ currentColor: currentColor, workingColor: ColorUtils.getNewColor({ hex: namedColorHex || currentColor, name: namedColorHex ? currentColor.toLowerCase() : null }, _this.props.events.onValidateWorkingColor), colorErrorMessage: isValid ? '' : labels.invalidColor }); if (_this.props.events.onChange) { _this.props.events.onChange(event, { color: currentColor, isValid: isValid }); } }); _defineProperty(_assertThisInitialized(_this), "handleSaturationValueChange", function (event, _ref3) { var saturation = _ref3.saturation, value = _ref3.value; _this.setWorkingColor(event, { saturation: saturation, value: value }); }); _defineProperty(_assertThisInitialized(_this), "handleSubmitButtonClick", function (event) { _this.setState({ isOpen: false, // eslint-disable-next-line react/no-access-state-in-setstate currentColor: _this.state.workingColor.hex, colorErrorMessage: '' }); if (_this.props.events.onChange) { _this.props.events.onChange(event, { color: _this.state.workingColor.hex, isValid: true }); } }); _defineProperty(_assertThisInitialized(_this), "handleSwatchButtonClick", function () { var workingColor = ColorUtils.getNewColor({ // eslint-disable-next-line react/no-access-state-in-setstate hex: _this.state.workingColor.hex }, _this.props.events.onValidateWorkingColor); _this.setState({ // eslint-disable-next-line react/no-access-state-in-setstate isOpen: !_this.state.isOpen, workingColor: workingColor, // eslint-disable-next-line react/no-access-state-in-setstate previousWorkingColor: _this.state.previousWorkingColor }); if (_this.props.onRequestOpen) { _this.props.onRequestOpen(); } }); _defineProperty(_assertThisInitialized(_this), "handleSwatchSelect", function (event, _ref4) { var hex = _ref4.hex; _this.setWorkingColor(event, { hex: hex }); }); _this.generatedId = props.id || generateId(); var _workingColor = ColorUtils.getNewColor({ hex: props.valueWorking || props.value }, props.events.onValidateWorkingColor); _this.state = { currentColor: props.value != null ? props.value : '', disabled: props.disabled, isOpen: props.isOpen, workingColor: _workingColor, previousWorkingColor: _workingColor, colorErrorMessage: props.errorText }; checkProps(COLOR_PICKER, props, componentDoc); return _this; } _createClass(ColorPicker, [{ key: "componentDidUpdate", value: function componentDidUpdate(prevProps) { // The following are only present to allow props to update the state if they get out of sync (for instance, the external store is updated). var nextState = {}; if (this.props.value !== prevProps.value) { nextState.currentColor = this.props.value; } if (this.props.valueWorking !== prevProps.valueWorking) { nextState.workingColor = ColorUtils.getNewColor({ hex: this.props.valueWorking }, this.props.events.onValidateWorkingColor); } if (this.props.disabled !== prevProps.disabled) { nextState.disabled = this.props.disabled; } if (Object.entries(nextState).length !== 0) { // eslint-disable-next-line react/no-did-update-set-state this.setState(nextState); } } }, { key: "getInput", value: function getInput(_ref5) { var _this2 = this; var labels = _ref5.labels; return this.props.hideInput ? null : /*#__PURE__*/React.createElement(Input, { "aria-describedby": !this.state.isOpen && this.state.colorErrorMessage ? "color-picker-summary-error-".concat(this.generatedId) : undefined, className: classNames('slds-color-picker__summary-input', 'slds-align-top', { 'slds-has-error': !!this.state.colorErrorMessage }), disabled: this.props.disabled, id: "color-picker-summary-input-".concat(this.generatedId), onChange: function onChange(event) { _this2.handleHexInputChange(event, { labels: labels }); }, value: this.state.currentColor }); } }, { key: "getDefaultTab", value: function getDefaultTab(_ref6) { var labels = _ref6.labels; return (this.props.variant === 'base' || this.props.variant === 'swatches') && /*#__PURE__*/React.createElement(TabsPanel, { label: labels.swatchTab }, /*#__PURE__*/React.createElement(SwatchPicker, { color: this.state.workingColor, labels: labels, onSelect: this.handleSwatchSelect, swatchColors: this.props.swatchColors })); } }, { key: "getCustomTab", value: function getCustomTab(_ref7) { var labels = _ref7.labels; return (this.props.variant === 'base' || this.props.variant === 'custom') && /*#__PURE__*/React.createElement(TabsPanel, { label: labels.customTab }, /*#__PURE__*/React.createElement(CustomColor, { assistiveText: this.props.assistiveText, id: this.generatedId, color: this.state.workingColor, errorTextWorkingColor: this.props.errorTextWorkingColor, previousColor: this.state.previousWorkingColor, labels: labels, onBlueChange: this.handleColorChange('blue'), onGreenChange: this.handleColorChange('green'), onHexChange: this.handleColorChange('hex'), onHueChange: this.handleColorChange('hue'), onRedChange: this.handleColorChange('red'), onSwatchChange: this.handleSwatchChange, onSaturationValueChange: this.handleSaturationValueChange, onSaturationNavigate: this.handleNavigate('saturation'), onValueNavigate: this.handleNavigate('value') })); } }, { key: "getPopover", value: function getPopover(_ref8) { var labels = _ref8.labels; var popoverBody = /*#__PURE__*/React.createElement(Tabs, { id: "color-picker-tabs-".concat(this.generatedId), defaultSelectedIndex: this.props.defaultSelectedTab === 'custom' ? 1 : 0 }, this.getDefaultTab({ labels: labels }), this.getCustomTab({ labels: labels })); var popoverFooter = /*#__PURE__*/React.createElement("div", { className: "slds-color-picker__selector-footer" }, /*#__PURE__*/React.createElement(Button, { className: "slds-color-picker__selector-cancel", id: "color-picker-footer-cancel-".concat(this.generatedId), label: labels.cancelButton, onClick: this.handleCancel, variant: "neutral" }), /*#__PURE__*/React.createElement(Button, { className: "slds-color-picker__selector-submit", disabled: Object.keys(this.state.workingColor.errors || {}).length > 0, id: "color-picker-footer-submit-".concat(this.generatedId), label: labels.submitButton, onClick: this.handleSubmitButtonClick, variant: "brand" })); return /*#__PURE__*/React.createElement(Popover, { ariaLabelledby: "color-picker-label-".concat(this.generatedId), align: "bottom left", body: popoverBody, className: classNames('slds-color-picker__selector', this.props.classNameMenu), footer: popoverFooter, hasNoCloseButton: true, hasNoNubbin: true, hasStaticAlignment: this.props.hasStaticAlignment, id: "slds-color-picker__selector-".concat(this.generatedId), isOpen: this.state.isOpen, onClose: this.props.onClose, onOpen: this.props.onOpen, onRequestClose: this.handleOnRequestClose, position: this.props.menuPosition }, /*#__PURE__*/React.createElement(Button, { className: "slds-color-picker__summary-button", disabled: this.props.disabled, iconClassName: "slds-m-left_xx-small", iconPosition: "right", iconVariant: "more", id: "slds-color-picker__summary-button-".concat(this.generatedId), label: /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("span", { className: "slds-assistive-text" }, this.props.assistiveText.label ? this.props.assistiveText.label : labels.label), /*#__PURE__*/React.createElement(Swatch, { color: this.state.currentColor, labels: labels })), onClick: this.handleSwatchButtonClick, variant: "icon" })); } }, { key: "setWorkingColor", value: function setWorkingColor(event, color) { var newColor = ColorUtils.getNewColor(color, this.props.events.onValidateWorkingColor, // eslint-disable-next-line react/no-access-state-in-setstate this.state.workingColor); this.setState({ workingColor: newColor, // eslint-disable-next-line react/no-access-state-in-setstate previousWorkingColor: this.state.workingColor }); if (this.props.events.onWorkingColorChange) { this.props.events.onWorkingColorChange(event, { color: newColor }); } } }, { key: "handleColorChange", value: function handleColorChange(property) { var _this3 = this; return function (event) { var colorProperties = {}; colorProperties[property] = event.target.value; _this3.setWorkingColor(event, colorProperties); }; } }, { key: "handleNavigate", value: function handleNavigate(property) { var _this4 = this; return function (event, _ref9) { var delta = _ref9.delta; var colorProperties = {}; colorProperties[property] = delta; var newColor = ColorUtils.getDeltaColor(colorProperties, _this4.props.events.onValidateWorkingColor, // eslint-disable-next-line react/no-access-state-in-setstate _this4.state.workingColor); _this4.setState({ workingColor: newColor, // eslint-disable-next-line react/no-access-state-in-setstate previousWorkingColor: _this4.state.workingColor }); if (_this4.props.events.onWorkingColorChange) { _this4.props.events.onWorkingColorChange(event, { color: newColor }); } }; } }, { key: "render", value: function render() { var _this5 = this; var labels = assign({}, defaultProps.labels, this.props.labels); return /*#__PURE__*/React.createElement("div", { className: classNames('slds-color-picker', this.props.className), ref: function ref(node) { _this5.wrapper = node; } }, /*#__PURE__*/React.createElement("div", { className: "slds-color-picker__summary" }, /*#__PURE__*/React.createElement("label", { className: classNames('slds-color-picker__summary-label', this.props.assistiveText.label ? 'slds-assistive-text' : ''), htmlFor: !this.props.hideInput ? "color-picker-summary-input-".concat(this.generatedId) : undefined, id: "color-picker-label-".concat(this.generatedId) }, this.props.assistiveText.label ? this.props.assistiveText.label : labels.label), this.getPopover({ labels: labels }), this.getInput({ labels: labels }), !this.state.isOpen && this.state.colorErrorMessage ? /*#__PURE__*/React.createElement("p", { className: "slds-form-error", id: "color-picker-summary-error-".concat(this.generatedId) }, this.state.colorErrorMessage) : '')); } }]); return ColorPicker; }(React.Component); _defineProperty(ColorPicker, "displayName", COLOR_PICKER); _defineProperty(ColorPicker, "propTypes", propTypes); _defineProperty(ColorPicker, "defaultProps", defaultProps); export default ColorPicker; //# sourceMappingURL=index.js.map