@wordpress/components
Version:
UI components for WordPress.
459 lines (451 loc) • 16.8 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PaletteEdit = PaletteEdit;
exports.deduplicateElementSlugs = deduplicateElementSlugs;
exports.default = void 0;
exports.getNameAndSlugForPosition = getNameAndSlugForPosition;
var _clsx = _interopRequireDefault(require("clsx"));
var _element = require("@wordpress/element");
var _i18n = require("@wordpress/i18n");
var _icons = require("@wordpress/icons");
var _compose = require("@wordpress/compose");
var _button = _interopRequireDefault(require("../button"));
var _colorPicker = require("../color-picker");
var _flex = require("../flex");
var _hStack = require("../h-stack");
var _itemGroup = require("../item-group");
var _vStack = require("../v-stack");
var _gradientPicker = _interopRequireDefault(require("../gradient-picker"));
var _colorPalette = _interopRequireDefault(require("../color-palette"));
var _dropdownMenu = _interopRequireDefault(require("../dropdown-menu"));
var _popover = _interopRequireDefault(require("../popover"));
var _styles = require("./styles");
var _navigableContainer = require("../navigable-container");
var _constants = require("../custom-gradient-picker/constants");
var _customGradientPicker = _interopRequireDefault(require("../custom-gradient-picker"));
var _strings = require("../utils/strings");
var _jsxRuntime = require("react/jsx-runtime");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const DEFAULT_COLOR = '#000';
function NameInput({
value,
onChange,
label
}) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.NameInputControl, {
size: "compact",
label: label,
hideLabelFromVision: true,
value: value,
onChange: onChange
});
}
/*
* Deduplicates the slugs of the provided elements.
*/
function deduplicateElementSlugs(elements) {
const slugCounts = {};
return elements.map(element => {
var _newSlug;
let newSlug;
const {
slug
} = element;
slugCounts[slug] = (slugCounts[slug] || 0) + 1;
if (slugCounts[slug] > 1) {
newSlug = `${slug}-${slugCounts[slug] - 1}`;
}
return {
...element,
slug: (_newSlug = newSlug) !== null && _newSlug !== void 0 ? _newSlug : slug
};
});
}
/**
* Returns a name and slug for a palette item. The name takes the format "Color + id".
* To ensure there are no duplicate ids, this function checks all slugs.
* It expects slugs to be in the format: slugPrefix + color- + number.
* It then sets the id component of the new name based on the incremented id of the highest existing slug id.
*
* @param elements An array of color palette items.
* @param slugPrefix The slug prefix used to match the element slug.
*
* @return A name and slug for the new palette item.
*/
function getNameAndSlugForPosition(elements, slugPrefix) {
const nameRegex = new RegExp(`^${slugPrefix}color-([\\d]+)$`);
const position = elements.reduce((previousValue, currentValue) => {
if (typeof currentValue?.slug === 'string') {
const matches = currentValue?.slug.match(nameRegex);
if (matches) {
const id = parseInt(matches[1], 10);
if (id >= previousValue) {
return id + 1;
}
}
}
return previousValue;
}, 1);
return {
name: (0, _i18n.sprintf)(/* translators: %d: is an id for a custom color */
(0, _i18n.__)('Color %d'), position),
slug: `${slugPrefix}color-${position}`
};
}
function ColorPickerPopover({
isGradient,
element,
onChange,
popoverProps: receivedPopoverProps,
onClose = () => {}
}) {
const popoverProps = (0, _element.useMemo)(() => ({
shift: true,
offset: 20,
// Disabling resize as it would otherwise cause the popover to show
// scrollbars while dragging the color picker's handle close to the
// popover edge.
resize: false,
placement: 'left-start',
...receivedPopoverProps,
className: (0, _clsx.default)('components-palette-edit__popover', receivedPopoverProps?.className)
}), [receivedPopoverProps]);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_popover.default, {
...popoverProps,
onClose: onClose,
children: [!isGradient && /*#__PURE__*/(0, _jsxRuntime.jsx)(_colorPicker.ColorPicker, {
color: element.color,
enableAlpha: true,
onChange: newColor => {
onChange({
...element,
color: newColor
});
}
}), isGradient && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
className: "components-palette-edit__popover-gradient-picker",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_customGradientPicker.default, {
__experimentalIsRenderedInSidebar: true,
value: element.gradient,
onChange: newGradient => {
onChange({
...element,
gradient: newGradient
});
}
})
})]
});
}
function Option({
canOnlyChangeValues,
element,
onChange,
onRemove,
popoverProps: receivedPopoverProps,
slugPrefix,
isGradient
}) {
const value = isGradient ? element.gradient : element.color;
const [isEditingColor, setIsEditingColor] = (0, _element.useState)(false);
// Use internal state instead of a ref to make sure that the component
// re-renders when the popover's anchor updates.
const [popoverAnchor, setPopoverAnchor] = (0, _element.useState)(null);
const popoverProps = (0, _element.useMemo)(() => ({
...receivedPopoverProps,
// Use the custom palette color item as the popover anchor.
anchor: popoverAnchor
}), [popoverAnchor, receivedPopoverProps]);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_itemGroup.Item, {
ref: setPopoverAnchor,
size: "small",
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_hStack.HStack, {
justify: "flex-start",
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
size: "small",
onClick: () => {
setIsEditingColor(true);
},
"aria-label": (0, _i18n.sprintf)(
// translators: %s is a color or gradient name, e.g. "Red".
(0, _i18n.__)('Edit: %s'), element.name.trim().length ? element.name : value || ''),
style: {
padding: 0
},
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.IndicatorStyled, {
colorValue: value
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_flex.FlexItem, {
children: !canOnlyChangeValues ? /*#__PURE__*/(0, _jsxRuntime.jsx)(NameInput, {
label: isGradient ? (0, _i18n.__)('Gradient name') : (0, _i18n.__)('Color name'),
value: element.name,
onChange: nextName => onChange({
...element,
name: nextName,
slug: slugPrefix + (0, _strings.kebabCase)(nextName !== null && nextName !== void 0 ? nextName : '')
})
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.NameContainer, {
children: element.name.trim().length ? element.name : /* Fall back to non-breaking space to maintain height */
'\u00A0'
})
}), !canOnlyChangeValues && /*#__PURE__*/(0, _jsxRuntime.jsx)(_flex.FlexItem, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.RemoveButton, {
size: "small",
icon: _icons.lineSolid,
label: (0, _i18n.sprintf)(
// translators: %s is a color or gradient name, e.g. "Red".
(0, _i18n.__)('Remove color: %s'), element.name.trim().length ? element.name : value || ''),
onClick: onRemove
})
})]
}), isEditingColor && /*#__PURE__*/(0, _jsxRuntime.jsx)(ColorPickerPopover, {
isGradient: isGradient,
onChange: onChange,
element: element,
popoverProps: popoverProps,
onClose: () => setIsEditingColor(false)
})]
});
}
function PaletteEditListView({
elements,
onChange,
canOnlyChangeValues,
slugPrefix,
isGradient,
popoverProps,
addColorRef
}) {
// When unmounting the component if there are empty elements (the user did not complete the insertion) clean them.
const elementsReferenceRef = (0, _element.useRef)();
(0, _element.useEffect)(() => {
elementsReferenceRef.current = elements;
}, [elements]);
const debounceOnChange = (0, _compose.useDebounce)(updatedElements => onChange(deduplicateElementSlugs(updatedElements)), 100);
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_vStack.VStack, {
spacing: 3,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_itemGroup.ItemGroup, {
isRounded: true,
isBordered: true,
isSeparated: true,
children: elements.map((element, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(Option, {
isGradient: isGradient,
canOnlyChangeValues: canOnlyChangeValues,
element: element,
onChange: newElement => {
debounceOnChange(elements.map((currentElement, currentIndex) => {
if (currentIndex === index) {
return newElement;
}
return currentElement;
}));
},
onRemove: () => {
const newElements = elements.filter((_currentElement, currentIndex) => {
if (currentIndex === index) {
return false;
}
return true;
});
onChange(newElements.length ? newElements : undefined);
addColorRef.current?.focus();
},
slugPrefix: slugPrefix,
popoverProps: popoverProps
}, index))
})
});
}
const EMPTY_ARRAY = [];
/**
* Allows editing a palette of colors or gradients.
*
* ```jsx
* import { PaletteEdit } from '@wordpress/components';
* const MyPaletteEdit = () => {
* const [ controlledColors, setControlledColors ] = useState( colors );
*
* return (
* <PaletteEdit
* colors={ controlledColors }
* onChange={ ( newColors?: Color[] ) => {
* setControlledColors( newColors );
* } }
* paletteLabel="Here is a label"
* />
* );
* };
* ```
*/
function PaletteEdit({
gradients,
colors = EMPTY_ARRAY,
onChange,
paletteLabel,
paletteLabelHeadingLevel = 2,
emptyMessage,
canOnlyChangeValues,
canReset,
slugPrefix = '',
popoverProps
}) {
const isGradient = !!gradients;
const elements = isGradient ? gradients : colors;
const [isEditing, setIsEditing] = (0, _element.useState)(false);
const [editingElement, setEditingElement] = (0, _element.useState)(null);
const isAdding = isEditing && !!editingElement && elements[editingElement] && !elements[editingElement].slug;
const elementsLength = elements.length;
const hasElements = elementsLength > 0;
const debounceOnChange = (0, _compose.useDebounce)(onChange, 100);
const onSelectPaletteItem = (0, _element.useCallback)((value, newEditingElementIndex) => {
const selectedElement = newEditingElementIndex === undefined ? undefined : elements[newEditingElementIndex];
const key = isGradient ? 'gradient' : 'color';
// Ensures that the index returned matches a known element value.
if (!!selectedElement && selectedElement[key] === value) {
setEditingElement(newEditingElementIndex);
} else {
setIsEditing(true);
}
}, [isGradient, elements]);
const addColorRef = (0, _element.useRef)(null);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_styles.PaletteEditStyles, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_hStack.HStack, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.PaletteHeading, {
level: paletteLabelHeadingLevel,
children: paletteLabel
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_styles.PaletteActionsContainer, {
children: [hasElements && isEditing && /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.DoneButton, {
size: "small",
onClick: () => {
setIsEditing(false);
setEditingElement(null);
},
children: (0, _i18n.__)('Done')
}), !canOnlyChangeValues && /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
ref: addColorRef,
size: "small",
isPressed: isAdding,
icon: _icons.plus,
label: isGradient ? (0, _i18n.__)('Add gradient') : (0, _i18n.__)('Add color'),
onClick: () => {
const {
name,
slug
} = getNameAndSlugForPosition(elements, slugPrefix);
if (!!gradients) {
onChange([...gradients, {
gradient: _constants.DEFAULT_GRADIENT,
name,
slug
}]);
} else {
onChange([...colors, {
color: DEFAULT_COLOR,
name,
slug
}]);
}
setIsEditing(true);
setEditingElement(elements.length);
}
}), hasElements && (!isEditing || !canOnlyChangeValues || canReset) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_dropdownMenu.default, {
icon: _icons.moreVertical,
label: isGradient ? (0, _i18n.__)('Gradient options') : (0, _i18n.__)('Color options'),
toggleProps: {
size: 'small'
},
children: ({
onClose
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_navigableContainer.NavigableMenu, {
role: "menu",
children: [!isEditing && /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
__next40pxDefaultSize: true,
variant: "tertiary",
onClick: () => {
setIsEditing(true);
onClose();
},
className: "components-palette-edit__menu-button",
children: (0, _i18n.__)('Show details')
}), !canOnlyChangeValues && /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
__next40pxDefaultSize: true,
variant: "tertiary",
onClick: () => {
setEditingElement(null);
setIsEditing(false);
onChange();
onClose();
},
className: "components-palette-edit__menu-button",
children: isGradient ? (0, _i18n.__)('Remove all gradients') : (0, _i18n.__)('Remove all colors')
}), canReset && /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
__next40pxDefaultSize: true,
className: "components-palette-edit__menu-button",
variant: "tertiary",
onClick: () => {
setEditingElement(null);
onChange();
onClose();
},
children: isGradient ? (0, _i18n.__)('Reset gradient') : (0, _i18n.__)('Reset colors')
})]
})
})
})]
})]
}), hasElements && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_styles.PaletteEditContents, {
children: [isEditing && /*#__PURE__*/(0, _jsxRuntime.jsx)(PaletteEditListView, {
canOnlyChangeValues: canOnlyChangeValues,
elements: elements
// @ts-expect-error TODO: Don't know how to resolve
,
onChange: onChange,
slugPrefix: slugPrefix,
isGradient: isGradient,
popoverProps: popoverProps,
addColorRef: addColorRef
}), !isEditing && editingElement !== null && /*#__PURE__*/(0, _jsxRuntime.jsx)(ColorPickerPopover, {
isGradient: isGradient,
onClose: () => setEditingElement(null),
onChange: newElement => {
debounceOnChange(
// @ts-expect-error TODO: Don't know how to resolve
elements.map((currentElement, currentIndex) => {
if (currentIndex === editingElement) {
return newElement;
}
return currentElement;
}));
},
element: elements[editingElement !== null && editingElement !== void 0 ? editingElement : -1],
popoverProps: popoverProps
}), !isEditing && (isGradient ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_gradientPicker.default, {
gradients: gradients,
onChange: onSelectPaletteItem,
clearable: false,
disableCustomGradients: true
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_colorPalette.default, {
colors: colors,
onChange: onSelectPaletteItem,
clearable: false,
disableCustomColors: true
}))]
}), !hasElements && emptyMessage && /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.PaletteEditContents, {
children: emptyMessage
})]
});
}
var _default = exports.default = PaletteEdit;
//# sourceMappingURL=index.js.map