/** * WordPress dependencies */ import { useDispatch, useSelect } from '@safe-wordpress/data'; import { useCallback, useEffect } from '@safe-wordpress/element'; import { _x } from '@safe-wordpress/i18n'; /** * External dependencies */ import clsx from 'clsx'; import { CodeEditor } from '@nab/components'; import { store as NAB_DATA } from '@nab/data'; import { store as NAB_EDITOR } from '@nab/editor'; import { getValue, omitUndefineds, setValue as setValueInLocalStore, } from '@nab/utils'; import type { AlternativeId, CssEditorState } from '@nab/types'; /** * Internal dependencies */ import { VisualEditor } from '../visual-editor'; import { ContentEditor } from '../content-editor'; import type { AlternativeAttributes } from '../../../../../../../../../packages/experiment-library/css/types'; export type BodyProps = { readonly alternativeId: AlternativeId; }; export function Body( { alternativeId }: BodyProps ): JSX.Element { const activeSelector = useActiveSelector(); const [ css, setCss ] = useCssValue( alternativeId ); const [ editor, setEditor ] = useEditor( alternativeId, getPreferredEditor() ); const markAsActive = useCssEditorActivation(); const highlightSelectedItems = useSelectorHighlighter(); return ( { ( tab: Tab ): JSX.Element => { switch ( tab.name ) { case 'css-editor': return ( markAsActive( true ) } onBlur={ () => markAsActive( false ) } /> ); case 'visual-editor': return ; case 'content-editor': return ; } } } ); } // ============ // HELPER VIEWS // ============ type TabPanelProps = { readonly className: string; readonly activeClass: string; readonly orientation: 'horizontal'; readonly onSelect: ( tab: EditorMode ) => void; readonly children: ( tab: Tab ) => JSX.Element; readonly activeTab: EditorMode; readonly tabs: Readonly< [ Tab, ...Tab[] ] >; }; // NOTICE. This is a workaround until “TabPanel” can be controlled/uncontrolled. const TabPanel = ( { className, activeClass, orientation, onSelect, children, activeTab, tabs, }: TabPanelProps ) => (
{ tabs.map( ( tab ) => ( ) ) }
{ children( tabs.find( ( t ) => t.name === activeTab ) || tabs[ 0 ] ) }
); // ===== // HOOKS // ===== const useCssValue = ( alternativeId: AlternativeId ) => { const alternative = useSelect( ( select ) => select( NAB_EDITOR ).getAlternative< AlternativeAttributes >( alternativeId ), [ alternativeId ] ); const value = alternative?.attributes?.css || ''; const { setAlternative } = useDispatch( NAB_EDITOR ); const setValue = ( css: string ) => { if ( ! alternative ) { return; } void setAlternative( alternative.id, { ...alternative, attributes: omitUndefineds( { ...alternative.attributes, css, } ), } ); }; return [ value, setValue ] as const; }; const useActiveSelector = () => useSelect( ( select ) => select( NAB_DATA ).getPageAttribute( 'css-editor/cssEditorState' ) ?.activeSelector, [] ); function getPreferredEditor(): EditorMode { const value = getValue( 'cssPreferredEditor' ); const isValid = ( v: unknown | EditorMode ): v is EditorMode => TABS.some( ( t ) => t.name === v ); return isValid( value ) ? value : 'visual-editor'; } function useEditor( alternativeId: AlternativeId, defaultEditorMode: EditorMode ): [ EditorMode, ( editor: EditorMode ) => void ] { const editor = useSelect( ( select ) => select( NAB_DATA ).getPageAttribute( 'css-editor/cssEditorState' ), [] ); const { setPageAttribute } = useDispatch( NAB_DATA ); const setEditor = useCallback( ( mode: EditorMode ): void => { setValueInLocalStore( 'cssPreferredEditor', mode ); if ( mode === editor?.mode ) { return; } void setPageAttribute( 'css-editor/cssEditorState', { isEditorActive: false, areChangesActive: true, activeSelector: '', activeElement: undefined, contentValues: [], isPreviewInScope: false, ...editor, alternativeId, mode, details: undefined, } ); }, [ alternativeId, editor, setPageAttribute ] ); useEffect( () => { if ( editor ) { return; } setEditor( defaultEditorMode ); }, [ editor, defaultEditorMode, setEditor ] ); return [ editor?.mode ?? defaultEditorMode, setEditor ]; } function useCssEditorActivation() { const editor = useSelect( ( select ) => select( NAB_DATA ).getPageAttribute( 'css-editor/cssEditorState' ), [] ); const { setPageAttribute } = useDispatch( NAB_DATA ); return ( isEditorActive: boolean ) => void ( 'css-editor' === editor?.mode && setPageAttribute( 'css-editor/cssEditorState', { ...editor, isEditorActive, } ) ); } const useSelectorHighlighter = () => { const editor = useSelect( ( select ) => select( NAB_DATA ).getPageAttribute( 'css-editor/cssEditorState' ), [] ); const { setPageAttribute } = useDispatch( NAB_DATA ); if ( ! editor ) { return () => void null; } return ( selectors: ReadonlyArray< string > = [] ) => { const activeSelector = selectors.join( ', ' ); if ( activeSelector === editor.activeSelector ) { return; } void setPageAttribute( 'css-editor/cssEditorState', { ...editor, activeSelector, activeElement: undefined, } ); }; }; // ==== // DATA // ==== type EditorMode = CssEditorState[ 'mode' ]; type Tab = { readonly name: EditorMode; readonly title: string; readonly className: string; }; const TABS: Readonly< [ Tab, Tab, Tab ] > = [ { name: 'visual-editor', title: _x( 'Appearance', 'text', 'nelio-ab-testing' ), className: 'nab-css-editor-sidebar__tab', }, { name: 'css-editor', title: _x( 'CSS', 'text', 'nelio-ab-testing' ), className: 'nab-css-editor-sidebar__tab', }, { name: 'content-editor', title: _x( 'Content', 'text', 'nelio-ab-testing' ), className: 'nab-css-editor-sidebar__tab', }, ];