diff --git a/src/components/publish-site-button.tsx b/src/components/publish-site-button.tsx new file mode 100644 index 000000000..08398ea93 --- /dev/null +++ b/src/components/publish-site-button.tsx @@ -0,0 +1,43 @@ +import { globe, cloudUpload } from '@wordpress/icons'; +import { useI18n } from '@wordpress/react-i18n'; +import Button from 'src/components/button'; +import { useSyncSites } from 'src/hooks/sync-sites'; +import { useContentTabs } from 'src/hooks/use-content-tabs'; +import { useAppDispatch } from 'src/stores'; +import { connectedSitesActions, useConnectedSitesData } from 'src/stores/sync'; +import { Tooltip } from './tooltip'; + +export const PublishSiteButton = () => { + const { __ } = useI18n(); + const dispatch = useAppDispatch(); + const { setSelectedTab } = useContentTabs(); + const { connectedSites } = useConnectedSitesData(); + const { isAnySitePulling, isAnySitePushing } = useSyncSites(); + const isAnySiteSyncing = isAnySitePulling || isAnySitePushing; + const handlePublishClick = () => { + setSelectedTab( 'sync' ); + dispatch( connectedSitesActions.openModal( 'push' ) ); + }; + + if ( connectedSites.length !== 0 ) return null; + + return ( + + + + ); +}; diff --git a/src/components/site-content-tabs.tsx b/src/components/site-content-tabs.tsx index 58ad9f3f9..a38fa6ed3 100644 --- a/src/components/site-content-tabs.tsx +++ b/src/components/site-content-tabs.tsx @@ -25,19 +25,34 @@ export function SiteContentTabs() { // Remount: Avoid focus loss on user tab changes (no remount), // but remount on programmatic changes and site switches so initial tab/content state resets. const [ keyCounter, setKeyCounter ] = useState( 0 ); + const [ programmaticTab, setProgrammaticTab ] = useState( selectedTab ); const lastChangeWasUser = useRef( false ); const isFirstRender = useRef( true ); + const prevSelectedTab = useRef( selectedTab ); useEffect( () => { if ( isFirstRender.current ) { isFirstRender.current = false; + prevSelectedTab.current = selectedTab; return; } + + // If tab didn't actually change, skip + if ( prevSelectedTab.current === selectedTab ) { + return; + } + + // Check if this was a user action by seeing if the flag was set BEFORE selectedTab changed if ( lastChangeWasUser.current ) { lastChangeWasUser.current = false; + prevSelectedTab.current = selectedTab; return; } + + // Programmatic change - update both counter and tab to force remount setKeyCounter( ( k ) => k + 1 ); + setProgrammaticTab( selectedTab ); + prevSelectedTab.current = selectedTab; }, [ selectedTab ] ); if ( ! loadingSites && ! localSites.length ) { @@ -71,12 +86,15 @@ export function SiteContentTabs() { tabs={ tabs } orientation="horizontal" onSelect={ ( tabName ) => { - // Mark this as a user-initiated change so we don't remount - lastChangeWasUser.current = true; + // Mark this as a user-initiated change BEFORE calling setSelectedTab + // so the useEffect can detect it was user-initiated + if ( tabName !== selectedTab ) { + lastChangeWasUser.current = true; + } setSelectedTab( tabName as TabName ); } } initialTabName={ selectedTab } - key={ `${ selectedSite.id }-${ keyCounter }` } + key={ `${ selectedSite.id }-${ keyCounter }-${ programmaticTab }` } > { ( { name } ) => (
- { - if ( selectedSite.running ) { - void onStop( selectedSite.id ); - } else { - void onStart( selectedSite.id ); - } - } } - disabled={ disabled } - buttonLabelOnDisabled={ buttonLabelOnDisabled } - /> - +
+ + + { + if ( selectedSite.running ) { + void onStop( selectedSite.id ); + } else { + void onStart( selectedSite.id ); + } + } } + disabled={ disabled } + buttonLabelOnDisabled={ buttonLabelOnDisabled } + /> + +
); }; diff --git a/src/components/tests/content-tab-overview-shortcuts-section.test.tsx b/src/components/tests/content-tab-overview-shortcuts-section.test.tsx index e40e62365..7eecd472c 100644 --- a/src/components/tests/content-tab-overview-shortcuts-section.test.tsx +++ b/src/components/tests/content-tab-overview-shortcuts-section.test.tsx @@ -2,6 +2,7 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import { Provider } from 'react-redux'; import { ContentTabOverview } from 'src/components/content-tab-overview'; +import { ContentTabsProvider } from 'src/hooks/use-content-tabs'; import { useThemeDetails } from 'src/hooks/use-theme-details'; import { isMac } from 'src/lib/app-globals'; import { getIpcApi } from 'src/lib/get-ipc-api'; @@ -26,12 +27,23 @@ const selectedSite: StartedSiteDetails = { const mockGetIpcApi = getIpcApi as jest.Mock; jest.mock( 'src/lib/get-ipc-api' ); jest.mock( 'src/hooks/use-theme-details' ); +jest.mock( 'src/stores/sync', () => ( { + ...jest.requireActual( 'src/stores/sync' ), + useConnectedSitesData: jest.fn().mockReturnValue( { + connectedSites: [], + localSiteId: 'site-id', + } ), +} ) ); // Replace the store's reducer with our test reducer store.replaceReducer( testReducer ); function renderWithProvider( component: React.ReactElement ) { - return render( { component } ); + return render( + + { component } + + ); } describe( 'ShortcutsSection', () => { diff --git a/src/components/tests/content-tab-overview.test.tsx b/src/components/tests/content-tab-overview.test.tsx index 2129b63ec..09f86b4ae 100644 --- a/src/components/tests/content-tab-overview.test.tsx +++ b/src/components/tests/content-tab-overview.test.tsx @@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'; import { Provider } from 'react-redux'; import { ContentTabOverview } from 'src/components/content-tab-overview'; +import { ContentTabsProvider } from 'src/hooks/use-content-tabs'; import { useSiteDetails } from 'src/hooks/use-site-details'; import { useThemeDetails } from 'src/hooks/use-theme-details'; import { store } from 'src/stores'; @@ -77,7 +78,9 @@ describe( 'ContentTabOverview', () => { } ); render( - + + + ); };