All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.page_editor.common.components.SpacingBox.js Maven / Gradle / Ivy

The newest version!
/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

import ClayButton, {ClayButtonWithIcon} from '@clayui/button';
import {Text} from '@clayui/core';
import ClayDropDown from '@clayui/drop-down';
import Layout from '@clayui/layout';
import ClayTooltip from '@clayui/tooltip';
import {ReactPortal} from '@liferay/frontend-js-react-web';
import {
	LengthInput,
	isValidStyleValue,
} from '@liferay/layout-js-components-web';
import classNames from 'classnames';
import {useId} from 'frontend-js-components-web';
import {sub} from 'frontend-js-web';
import React, {useEffect, useMemo, useRef, useState} from 'react';

import {useGlobalContext} from '../../app/contexts/GlobalContext';
import {useSelector} from '../../app/contexts/StoreContext';
import {getResetLabelByViewport} from '../../app/utils/getResetLabelByViewport';
import {useStyleBook} from '../../plugins/page_design_options/hooks/useStyleBook';

/**
 * These elements must be sorted from the most outer circle to the most inner
 * circle to facilitate keyboard navigation.
 * @type {string[]}
 */
const SPACING_TYPES = ['margin', 'padding'];

/**
 * We want to show spacing options in a clockwise order, according
 * to the standard of most CSS rules (top, right, bottom, left).
 * @type {string[]}
 */
const SPACING_POSITIONS = ['top', 'right', 'bottom', 'left'];

const ARROW_TO_POSITION = {
	ArrowDown: 'bottom',
	ArrowLeft: 'left',
	ArrowRight: 'right',
	ArrowUp: 'top',
};

const REVERSED_POSITION = {
	bottom: 'top',
	left: 'right',
	right: 'left',
	top: 'bottom',
};

const BUTTON_CLASSNAME = 'page-editor__spacing-selector__button';
const DROPDOWN_CLASSNAME = 'page-editor__spacing-selector__dropdown';

const TOOLTIP_SHOW_DELAY = 600;

export default function SpacingBox({
	canSetCustomValue,
	fields,
	onChange,
	value,
}) {
	const ref = useRef();

	const focusButton = (type, position) => {
		const button = document.querySelector(
			`.${BUTTON_CLASSNAME}[data-type=${type}][data-position=${position}]`
		);

		button?.focus();
	};

	const handleKeyDown = (event) => {
		if (
			(event.key === 'Enter' || event.key === ' ') &&
			document.activeElement === ref.current
		) {
			event.preventDefault();
			focusButton('margin', 'top');

			return;
		}

		if (
			!document.activeElement?.classList.contains(BUTTON_CLASSNAME) ||
			document.activeElement?.getAttribute('aria-expanded') === 'true' ||
			!event.key.startsWith('Arrow')
		) {
			return;
		}

		event.preventDefault();

		const {position: currentPosition, type: currentType} =
			document.activeElement.dataset;

		let nextPosition = ARROW_TO_POSITION[event.key];
		let nextType = currentType;

		if (nextPosition === currentPosition) {

			// Move to the outer type.
			// We try to update the type so we can move to the outer circle,
			// or stay in position if it is not possible.

			const currentTypeIndex = SPACING_TYPES.indexOf(currentType);
			nextType = SPACING_TYPES[Math.max(0, currentTypeIndex - 1)];
		}
		else if (nextPosition === REVERSED_POSITION[currentPosition]) {

			// Move to the inner type.
			// We try to update the type so we can move to the inner circle,
			// and keep currentPosition if it succeeds.

			const currentTypeIndex = SPACING_TYPES.indexOf(currentType);

			if (currentTypeIndex < SPACING_TYPES.length - 1) {
				nextType = SPACING_TYPES[currentTypeIndex + 1];
				nextPosition = currentPosition;
			}
		}

		focusButton(nextType, nextPosition);
	};

	return (
		
{SPACING_TYPES.map((type) => ( {SPACING_POSITIONS.map((position) => { const key = `${type}${capitalize(position)}`; return ( ); })} ))}
); } function SpacingSelectorButton({ canSetCustomValue, field, onChange, position, type, value, }) { const [active, setActive] = useState(false); const disabled = !field || field.disabled; const itemListRef = useRef(); const [labelElement, setLabelElement] = useState(null); const {tokenValues} = useStyleBook(); const tooltipId = useId(); const triggerId = useId(); const headerId = useId(); const [triggerElement, setTriggerElement] = useState(null); const selectedViewportSize = useSelector( (state) => state.selectedViewportSize ); const resetButtonLabel = useMemo( () => getResetLabelByViewport(selectedViewportSize), [selectedViewportSize] ); useEffect(() => { if (active && itemListRef.current) { setTimeout( () => itemListRef.current ?.querySelector( `button[data-value="${ value || field?.defaultValue }"]` ) ?.focus(), 10 ); } }, [active, field, value]); return ( setActive(!active)} ref={setTriggerElement} type="button" > {field ? ( {field.label} -{' '} } positionElement={labelElement} /> ) : null} } >
{active && canSetCustomValue ? ( <>
  • ) : null} {field?.typeOptions?.validValues?.map((option) => ( { onChange(field.name, option.value); setActive(false); triggerElement?.focus(); }} > {tokenValues[ `spacer${option.value}` ]?.label || option.label} ))}
    ); } function SpacingOptionValue({ position, removeValueUnit = false, tokenValues, type, value: optionValue, }) { const globalContext = useGlobalContext(); const [value, setValue] = useState(optionValue); useEffect(() => { if (tokenValues[`spacer${optionValue}`]) { setValue(tokenValues[`spacer${optionValue}`].value); return; } if (isValidStyleValue(`${type}-${position}`, optionValue)) { setValue(optionValue); return; } const element = globalContext.document.createElement('div'); element.style.display = 'none'; element.classList.add(`${type[0]}${position[0]}-${optionValue}`); globalContext.document.body.appendChild(element); const nextValue = globalContext.window .getComputedStyle(element) .getPropertyValue(`${type}-${position}`); setValue(nextValue); globalContext.document.body.removeChild(element); }, [ globalContext, optionValue, position, removeValueUnit, type, tokenValues, ]); return value === undefined ? '' : value; } function Tooltip({hoverElement, id: tooltipId, label, positionElement}) { const [tooltipStyle, setTooltipStyle] = useState(null); useEffect(() => { if (!hoverElement || !positionElement) { return; } let showTimeoutId; const handleMouseLeave = () => { clearTimeout(showTimeoutId); setTooltipStyle(null); }; const handleMouseOver = () => { clearTimeout(showTimeoutId); showTimeoutId = setTimeout(() => { const rect = positionElement.getBoundingClientRect(); setTooltipStyle({ left: rect.left + rect.width / 2, top: rect.top, }); }, TOOLTIP_SHOW_DELAY); }; hoverElement.addEventListener('mouseleave', handleMouseLeave); hoverElement.addEventListener('mouseover', handleMouseOver); return () => { clearTimeout(showTimeoutId); hoverElement.removeEventListener('mouseleave', handleMouseLeave); hoverElement.removeEventListener('mouseover', handleMouseOver); }; }, [hoverElement, positionElement]); return tooltipStyle ? ( {label} ) : null; } function capitalize(str) { return `${str.substring(0, 1).toUpperCase()}${str.substring(1)}`; } function SpacingSelectorBackground() { return ( ); }




    © 2015 - 2025 Weber Informatics LLC | Privacy Policy