diff --git a/src/customizations/volto/components/manage/Widgets/UrlWidget.jsx b/src/customizations/volto/components/manage/Widgets/UrlWidget.jsx new file mode 100644 index 000000000..0d31ad3ce --- /dev/null +++ b/src/customizations/volto/components/manage/Widgets/UrlWidget.jsx @@ -0,0 +1,196 @@ +/** + * UrlWidget component. + * @module components/manage/Widgets/UrlWidget + * Volto 19.x.x + * - Recreated the linkInvalid variable to correctly identify whether a link is valid or not. + * - Added the necessary ARIA attributes to the input. + */ + +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { Input, Button } from 'semantic-ui-react'; +import Icon from '@plone/volto/components/theme/Icon/Icon'; +import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper'; +import { + addAppURL, + isInternalURL, + flattenToAppURL, + URLUtils, +} from '@plone/volto/helpers/Url/Url'; +import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser'; +import clearSVG from '@plone/volto/icons/clear.svg'; +import navTreeSVG from '@plone/volto/icons/nav.svg'; + +/** Widget to edit urls + * + * This is the default widget used for the `remoteUrl` field. You can also use + * it by declaring a field like: + * + * ```jsx + * { + * title: "URL", + * widget: 'url', + * } + * ``` + */ +export const UrlWidget = (props) => { + const { + id, + onChange, + onBlur, + onClick, + minLength, + maxLength, + placeholder, + isDisabled, + required, + } = props; + const inputId = `field-${id}`; + + const [value, setValue] = useState(flattenToAppURL(props.value)); + const [isInvalid, setIsInvalid] = useState(true); + /** + * Clear handler + * @method clear + * @param {Object} value Value + * @returns {undefined} + */ + const clear = () => { + setValue(''); + onChange(id, undefined); + }; + + const onChangeValue = (_value) => { + let newValue = _value; + + if (newValue?.length > 0) { + if (isInvalid && URLUtils.isUrl(URLUtils.normalizeUrl(newValue))) { + setIsInvalid(false); + } + + if (isInternalURL(newValue)) { + newValue = flattenToAppURL(newValue); + } + } + + setValue(newValue); + + newValue = isInternalURL(newValue) ? addAppURL(newValue) : newValue; + + const isValidInternal = isInternalURL(newValue); + const isValidExternal = + !isInternalURL(newValue) && + newValue.length > 0 && + URLUtils.checkAndNormalizeUrl(newValue).isValid; + + const invalid = !(isValidInternal || isValidExternal); + setIsInvalid(invalid); + + onChange(id, newValue === '' ? undefined : newValue); + }; + + return ( + +
+ onChangeValue(target.value)} + onBlur={({ target }) => + onBlur(id, target.value === '' ? undefined : target.value) + } + onClick={() => onClick()} + minLength={minLength || null} + maxLength={maxLength || null} + error={isInvalid} + required={required} + aria-required={required} + aria-invalid={isInvalid} + /> + {value?.length > 0 ? ( + + + + ) : ( + + + + )} +
+
+ ); +}; + +/** + * Property types + * @property {Object} propTypes Property types. + * @static + */ +UrlWidget.propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string, + required: PropTypes.bool, + error: PropTypes.arrayOf(PropTypes.string), + value: PropTypes.string, + onChange: PropTypes.func.isRequired, + onBlur: PropTypes.func, + onClick: PropTypes.func, + minLength: PropTypes.number, + maxLength: PropTypes.number, + openObjectBrowser: PropTypes.func.isRequired, + placeholder: PropTypes.string, +}; + +/** + * Default properties. + * @property {Object} defaultProps Default properties. + * @static + */ +UrlWidget.defaultProps = { + description: null, + required: false, + error: [], + value: null, + onChange: () => {}, + onBlur: () => {}, + onClick: () => {}, + minLength: null, + maxLength: null, +}; + +export default withObjectBrowser(UrlWidget);