Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pre-publish sidebar #44331

Merged
merged 18 commits into from Feb 7, 2024
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Add prepublish panel #44331
16 changes: 16 additions & 0 deletions packages/js/product-editor/src/components/editor/editor.tsx
Expand Up @@ -11,6 +11,7 @@ import {
LayoutContextProvider,
useExtendLayout,
} from '@woocommerce/admin-layout';
import { useSelect } from '@wordpress/data';
import { Popover } from '@wordpress/components';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
Expand All @@ -32,6 +33,8 @@ import { Header } from '../header';
import { BlockEditor } from '../block-editor';
import { ValidationProvider } from '../../contexts/validation-context';
import { EditorProps } from './types';
import { store as productEditorUiStore } from '../../store/product-editor-ui';
import { PrepublishPanel } from '../prepublish-panel/prepublish-panel';

export function Editor( {
product,
Expand All @@ -42,6 +45,11 @@ export function Editor( {

const updatedLayoutContext = useExtendLayout( 'product-block-editor' );

// Check if the prepublish sidebar is open from the store.
const isPrepublishPanelOpen = useSelect( ( select ) => {
return select( productEditorUiStore ).isPrepublishPanelOpen();
}, [] );

return (
<LayoutContextProvider value={ updatedLayoutContext }>
<StrictMode>
Expand Down Expand Up @@ -73,6 +81,14 @@ export function Editor( {
/>
</>
}
actions={
isPrepublishPanelOpen && (
<PrepublishPanel
productType={ productType }
productId={ product.id }
/>
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that sidebar is used for the actual collapsable sidebar, we may want to use the actions input here, similar to GB. And if we do add an actual expandable/collapsable sidebar we can add it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! I did it in the commit 9ae966b

/>
<Popover.Slot />
</ValidationProvider>
Expand Down
21 changes: 17 additions & 4 deletions packages/js/product-editor/src/components/header/header.tsx
Expand Up @@ -23,6 +23,7 @@ import { MoreMenu } from './more-menu';
import { PreviewButton } from './preview-button';
import { SaveDraftButton } from './save-draft-button';
import { PublishButton } from './publish-button';
import { PrepublishButton } from '../prepublish-panel';
import { Tabs } from '../tabs';
import { HEADER_PINNED_ITEMS_SCOPE, TRACKS_SOURCE } from '../../constants';

Expand Down Expand Up @@ -79,6 +80,10 @@ export function Header( {
}

const isVariation = lastPersistedProduct?.parent_id > 0;
const isPublished =
productType === 'product'
? lastPersistedProduct?.status === 'publish'
: true;

return (
<div
Expand Down Expand Up @@ -154,10 +159,18 @@ export function Header( {
productStatus={ lastPersistedProduct?.status }
/>

<PublishButton
productType={ productType }
productStatus={ lastPersistedProduct?.status }
/>
{ ! isPublished &&
window.wcAdminFeatures[ 'product-pre-publish-modal' ] ? (
<PrepublishButton
productId={ productId }
productType={ productType }
/>
) : (
<PublishButton
productType={ productType }
productStatus={ lastPersistedProduct?.status }
/>
) }

<WooHeaderItem.Slot name="product" />
<PinnedItems.Slot scope={ HEADER_PINNED_ITEMS_SCOPE } />
Expand Down
@@ -0,0 +1,3 @@
export * from './prepublish-button';
export * from './prepublish-panel';
export * from './types';
@@ -0,0 +1,65 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { createElement } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import type { Product } from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';

/**
* Internal dependencies
*/
import { store as productEditorUiStore } from '../../store/product-editor-ui';
import { PrepublishButtonProps } from './types';
import { useValidations } from '../../contexts/validation-context';
import { TRACKS_SOURCE } from '../../constants';

export function PrepublishButton( {
productId,
productType = 'product',
}: PrepublishButtonProps ) {
const { openPrepublishPanel } = useDispatch( productEditorUiStore );
const { isValidating } = useValidations< Product >();
const { isSaving, isDirty } = useSelect(
( select ) => {
const {
// @ts-expect-error There are no types for this.
isSavingEntityRecord,
// @ts-expect-error There are no types for this.
hasEditsForEntityRecord,
} = select( 'core' );

return {
isSaving: isSavingEntityRecord< boolean >(
'postType',
productType,
productId
),
isDirty: hasEditsForEntityRecord(
'postType',
productType,
productId
),
};
},
[ productId, productType ]
);

const isBusy = isSaving || isValidating;
const isDisabled = isBusy || ! isDirty;

return (
<Button
onClick={ () => {
recordEvent( 'product_prepublish', { source: TRACKS_SOURCE } );
openPrepublishPanel();
} }
isBusy={ isBusy }
aria-disabled={ isDisabled }
children={ __( 'Add', 'woocommerce' ) }
variant={ 'primary' }
/>
);
}
@@ -0,0 +1,62 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { createElement } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { recordEvent } from '@woocommerce/tracks';

/**
* Internal dependencies
*/
import { PublishButton } from '../header/publish-button';
import { PrepublishPanelProps } from './types';
import { store as productEditorUiStore } from '../../store/product-editor-ui';
import { TRACKS_SOURCE } from '../../constants';

export function PrepublishPanel( {
productId,
productType = 'product',
title = __( 'Are you ready to add this product?', 'woocommerce' ),
description = __(
'Double-check your settings before sharing this product with customers.',
'woocommerce'
),
}: PrepublishPanelProps ) {
const lastPersistedProduct = useSelect(
( select ) => {
const { getEntityRecord } = select( 'core' );
return getEntityRecord( 'postType', productType, productId );
},
[ productType, productId ]
);

const { closePrepublishPanel } = useDispatch( productEditorUiStore );

return (
<div className="woocommerce-product-publish-panel">
<div className="woocommerce-product-publish-panel__header">
<PublishButton
productType={ productType }
productStatus={ lastPersistedProduct?.status }
/>
<Button
variant={ 'secondary' }
onClick={ () => {
recordEvent( 'product_prepublish_cancel', {
source: TRACKS_SOURCE,
} );
closePrepublishPanel();
} }
>
{ __( 'Cancel', 'woocommerce' ) }
</Button>
</div>
<div className="woocommerce-product-publish-panel__title">
<h4>{ title }</h4>
<span>{ description }</span>
</div>
</div>
);
}
@@ -0,0 +1,56 @@
.woocommerce-product-publish-panel {
right: 0;
left: 0;
overflow: auto;
position: fixed;

@include break-medium() {
left: auto;
width: $sidebar-width + $border-width + 6px;
transform: translateX(+100%);
animation: product-publish-panel__slide-in-animation 0.1s forwards;
@include reduce-motion("animation");

body.is-fullscreen-mode & {
top: 0;
}

// Keep it open on focus to avoid conflict with navigate-regions animation.
[role="region"]:focus & {
transform: translateX(0%);
}
}

&__header {
background: $white;
height: $header-height + $border-width;
display: flex;
align-items: center;
gap: 8px;
padding: 0 $grid-unit-20;

> button {
flex: 1;
}

.components-button {
width: 100%;
justify-content: center;
}
}

&__title {
padding: $grid-unit-20;
h4 {
font-size: 14px;
line-height: 20px;
margin: 8px 0;
}
}
}

@keyframes product-publish-panel__slide-in-animation {
100% {
transform: translateX(0%);
}
}
@@ -0,0 +1,11 @@
export type PrepublishPanelProps = {
productId: number;
productType: string;
title?: string;
description?: string;
};

export type PrepublishButtonProps = {
productId: number;
productType: string;
};
19 changes: 12 additions & 7 deletions packages/js/product-editor/src/store/product-editor-ui/Readme.md
Expand Up @@ -5,25 +5,30 @@ This module provides a @wordpress/data store for managing the UI state of a WooC
## Structure

Defines action types for the UI state:
- `ACTION_MODAL_EDITOR_OPEN`
- `ACTION_MODAL_EDITOR_CLOSE`

- `ACTION_MODAL_EDITOR_OPEN`
- `ACTION_MODAL_EDITOR_CLOSE`
- `ACTION_PANEL_PREPUBLISH_OPEN`
- `ACTION_PANEL_PREPUBLISH_CLOSE`

### Actions

- `openModalEditor`
- `closeModalEditor`
- `openModalEditor`
- `closeModalEditor`
- `openPrepublishPanel`
- `closePrepublishPanel`

### Selectors

Selector function:

- `isModalEditorOpen`

- `isModalEditorOpen`
- `isPrepublishPanelOpen`

### Store

Registers the WooCommerce Product Editor UI store with the following:

- Store Name: `woo/product-editor-ui`
- Store Name: `woo/product-editor-ui`

## Usage
13 changes: 13 additions & 0 deletions packages/js/product-editor/src/store/product-editor-ui/actions.ts
Expand Up @@ -11,6 +11,8 @@ import {
ACTION_MODAL_EDITOR_OPEN,
ACTION_MODAL_EDITOR_SET_BLOCKS,
ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
ACTION_PANEL_PREPUBLISH_OPEN,
ACTION_PANEL_PREPUBLISH_CLOSE,
} from './constants';

const modalEditorActions = {
Expand All @@ -33,6 +35,17 @@ const modalEditorActions = {
} ),
};

const prepublishPanelActions = {
openPrepublishPanel: () => ( {
type: ACTION_PANEL_PREPUBLISH_OPEN,
} ),

closePrepublishPanel: () => ( {
type: ACTION_PANEL_PREPUBLISH_CLOSE,
} ),
};

export default {
...modalEditorActions,
...prepublishPanelActions,
};
Expand Up @@ -6,3 +6,5 @@ export const ACTION_MODAL_EDITOR_CLOSE = 'MODAL_EDITOR_CLOSE';
export const ACTION_MODAL_EDITOR_SET_BLOCKS = 'MODAL_EDITOR_SET_BLOCKS';
export const ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED =
'MODAL_EDITOR_CONTENT_HAS_CHANGED';
export const ACTION_PANEL_PREPUBLISH_OPEN = 'PANEL_PREPUBLISH_OPEN';
export const ACTION_PANEL_PREPUBLISH_CLOSE = 'PANEL_PREPUBLISH_CLOSE';