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(
-
+
+
+
);
};