/** * External dependencies */ import clsx from 'clsx'; /** * WordPress dependencies */ import { __experimentalHStack as HStack, __experimentalVStack as VStack, __experimentalSpacer as Spacer, __experimentalItemGroup as ItemGroup, __experimentalInputControl as InputControl, __experimentalUnitControl as UnitControl, __experimentalGrid as Grid, __experimentalDropdownContentWrapper as DropdownContentWrapper, useNavigator, __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, __experimentalConfirmDialog as ConfirmDialog, Dropdown, Button, Flex, FlexItem, ColorPalette, Modal, privateApis as componentsPrivateApis, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { plus, shadow as shadowIcon, reset, moreVertical, } from '@wordpress/icons'; import { useState, useMemo, useEffect, useRef } from '@wordpress/element'; /** * Internal dependencies */ import { Subtitle } from './subtitle'; import { ScreenHeader } from './screen-header'; import { ScreenBody } from './screen-body'; import { defaultShadow } from './shadows-panel'; import { getShadowParts, shadowStringToObject, shadowObjectToString, } from './shadow-utils'; import { useSetting } from './hooks'; import { unlock } from './lock-unlock'; const { Menu } = unlock( componentsPrivateApis ); const customShadowMenuItems = [ { label: __( 'Rename' ), action: 'rename', }, { label: __( 'Delete' ), action: 'delete', }, ]; const presetShadowMenuItems = [ { label: __( 'Reset' ), action: 'reset', }, ]; export default function ShadowsEditPanel() { const { goBack, params } = useNavigator(); const { category, slug } = params; const [ shadows, setShadows ] = useSetting( `shadow.presets.${ category }` ); useEffect( () => { const hasCurrentShadow = shadows?.some( ( shadow: any ) => shadow.slug === slug ); // If the shadow being edited doesn't exist anymore in the global styles setting, navigate back // to prevent the user from editing a non-existent shadow entry. // This can happen, for example: // - when the user deletes the shadow // - when the user resets the styles while editing a custom shadow // // The check on the slug is necessary to prevent a double back navigation when the user triggers // a backward navigation by interacting with the screen's UI. if ( !! slug && ! hasCurrentShadow ) { goBack(); } }, [ shadows, slug, goBack ] ); const [ baseShadows ] = useSetting( `shadow.presets.${ category }`, undefined, 'base' ); const [ selectedShadow, setSelectedShadow ] = useState( () => ( shadows || [] ).find( ( shadow: any ) => shadow.slug === slug ) ); const baseSelectedShadow = useMemo( () => ( baseShadows || [] ).find( ( b: any ) => b.slug === slug ), [ baseShadows, slug ] ); const [ isConfirmDialogVisible, setIsConfirmDialogVisible ] = useState( false ); const [ isRenameModalVisible, setIsRenameModalVisible ] = useState( false ); const [ shadowName, setShadowName ] = useState< string | undefined >( selectedShadow?.name ); if ( ! category || ! slug ) { return null; } const onShadowChange = ( shadow: string ) => { setSelectedShadow( { ...selectedShadow, shadow } ); const updatedShadows = shadows.map( ( s: any ) => s.slug === slug ? { ...selectedShadow, shadow } : s ); setShadows( updatedShadows ); }; const onMenuClick = ( action: string ) => { if ( action === 'reset' ) { const updatedShadows = shadows.map( ( s: any ) => s.slug === slug ? baseSelectedShadow : s ); setSelectedShadow( baseSelectedShadow ); setShadows( updatedShadows ); } else if ( action === 'delete' ) { setIsConfirmDialogVisible( true ); } else if ( action === 'rename' ) { setIsRenameModalVisible( true ); } }; const handleShadowDelete = () => { setShadows( shadows.filter( ( s: any ) => s.slug !== slug ) ); }; const handleShadowRename = ( newName: string | undefined ) => { if ( ! newName ) { return; } const updatedShadows = shadows.map( ( s: any ) => s.slug === slug ? { ...selectedShadow, name: newName } : s ); setSelectedShadow( { ...selectedShadow, name: newName } ); setShadows( updatedShadows ); }; return ! selectedShadow ? ( ) : ( <> } /> { ( category === 'custom' ? customShadowMenuItems : presetShadowMenuItems ).map( ( item ) => ( onMenuClick( item.action ) } disabled={ item.action === 'reset' && selectedShadow.shadow === baseSelectedShadow?.shadow } > { item.label } ) ) } { isConfirmDialogVisible && ( { handleShadowDelete(); setIsConfirmDialogVisible( false ); } } onCancel={ () => { setIsConfirmDialogVisible( false ); } } confirmButtonText={ __( 'Delete' ) } size="medium" > { sprintf( /* translators: %s: Name of the shadow preset. */ __( 'Are you sure you want to delete "%s" shadow preset?' ), selectedShadow.name ) } ) } { isRenameModalVisible && ( setIsRenameModalVisible( false ) } size="small" >
{ event.preventDefault(); handleShadowRename( shadowName ); setIsRenameModalVisible( false ); } } >
) } ); } interface ShadowsPreviewProps { shadow: string; } function ShadowsPreview( { shadow }: ShadowsPreviewProps ) { const shadowStyle = { boxShadow: shadow, }; return (
); } interface ShadowEditorProps { shadow: string; onChange: ( shadow: string ) => void; } function ShadowEditor( { shadow, onChange }: ShadowEditorProps ) { const addShadowButtonRef = useRef< HTMLButtonElement >( null ); const shadowParts = useMemo( () => getShadowParts( shadow ), [ shadow ] ); const onChangeShadowPart = ( index: number, part: string ) => { const newShadowParts = [ ...shadowParts ]; newShadowParts[ index ] = part; onChange( newShadowParts.join( ', ' ) ); }; const onAddShadowPart = () => { onChange( [ ...shadowParts, defaultShadow ].join( ', ' ) ); }; const onRemoveShadowPart = ( index: number ) => { onChange( shadowParts.filter( ( p, i ) => i !== index ).join( ', ' ) ); addShadowButtonRef.current?.focus(); }; return ( <> { __( 'Shadows' ) } { canRemove && (