Onboarding: Add WebPreview component for theme previewing (https://github.com/woocommerce/woocommerce-admin/pull/2681)
* Add WebPreview component * Add theme preview component * Add WebPreview example for devdocs * Update loading content prop name for WebPreview * Add selected class state for device buttons * Fix tabbing issue in stylesheet * Fix loadingContent prop changes * Add in translators note * Fix theme details height issue * Add theme demo track events (https://github.com/woocommerce/woocommerce-admin/pull/2715) * Add theme demo track events * Track theme chosen location * Track theme slug on device switch * Apply design feedback
This commit is contained in:
parent
dca1b07377
commit
d41ce76451
|
@ -23,6 +23,7 @@ import withSelect from 'wc-api/with-select';
|
|||
import './style.scss';
|
||||
import { recordEvent } from 'lib/tracks';
|
||||
import ThemeUploader from './uploader';
|
||||
import ThemePreview from './preview';
|
||||
|
||||
class Theme extends Component {
|
||||
constructor() {
|
||||
|
@ -30,19 +31,21 @@ class Theme extends Component {
|
|||
|
||||
this.state = {
|
||||
activeTab: 'all',
|
||||
demo: null,
|
||||
uploadedThemes: [],
|
||||
};
|
||||
|
||||
this.handleUploadComplete = this.handleUploadComplete.bind( this );
|
||||
this.onChoose = this.onChoose.bind( this );
|
||||
this.onClosePreview = this.onClosePreview.bind( this );
|
||||
this.onSelectTab = this.onSelectTab.bind( this );
|
||||
this.openDemo = this.openDemo.bind( this );
|
||||
}
|
||||
|
||||
async onChoose( theme ) {
|
||||
async onChoose( theme, location = '' ) {
|
||||
const { createNotice, goToNextStep, isError, updateProfileItems } = this.props;
|
||||
|
||||
recordEvent( 'storeprofiler_store_theme_choose', { theme } );
|
||||
recordEvent( 'storeprofiler_store_theme_choose', { theme, location } );
|
||||
await updateProfileItems( { theme } );
|
||||
|
||||
if ( ! isError ) {
|
||||
|
@ -56,10 +59,17 @@ class Theme extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
openDemo( theme ) {
|
||||
// @todo This should open a theme demo preview.
|
||||
onClosePreview() {
|
||||
const { demo } = this.state;
|
||||
recordEvent( 'storeprofiler_store_theme_demo_close', { theme: demo.slug } );
|
||||
document.body.classList.remove( 'woocommerce-theme-preview-active' );
|
||||
this.setState( { demo: null } );
|
||||
}
|
||||
|
||||
recordEvent( 'storeprofiler_store_theme_live_demo', { theme } );
|
||||
openDemo( theme ) {
|
||||
recordEvent( 'storeprofiler_store_theme_live_demo', { theme: theme.slug } );
|
||||
document.body.classList.add( 'woocommerce-theme-preview-active' );
|
||||
this.setState( { demo: theme } );
|
||||
}
|
||||
|
||||
renderTheme( theme ) {
|
||||
|
@ -90,12 +100,12 @@ class Theme extends Component {
|
|||
<Button
|
||||
isPrimary={ Boolean( demo_url ) }
|
||||
isDefault={ ! Boolean( demo_url ) }
|
||||
onClick={ () => this.onChoose( slug ) }
|
||||
onClick={ () => this.onChoose( slug, 'card' ) }
|
||||
>
|
||||
{ __( 'Choose', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
{ demo_url && (
|
||||
<Button isDefault onClick={ () => this.openDemo( slug ) }>
|
||||
<Button isDefault onClick={ () => this.openDemo( theme ) }>
|
||||
{ __( 'Live Demo', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
) }
|
||||
|
@ -152,11 +162,14 @@ class Theme extends Component {
|
|||
this.setState( {
|
||||
uploadedThemes: [ ...this.state.uploadedThemes, upload.theme_data ],
|
||||
} );
|
||||
|
||||
recordEvent( 'storeprofiler_store_theme_upload', { theme: upload.theme_data.slug } );
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const themes = this.getThemes();
|
||||
const { demo } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -192,6 +205,9 @@ class Theme extends Component {
|
|||
</div>
|
||||
) }
|
||||
</TabPanel>
|
||||
{ demo && (
|
||||
<ThemePreview theme={ demo } onChoose={ this.onChoose } onClose={ this.onClosePreview } />
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Button } from 'newspack-components';
|
||||
import classnames from 'classnames';
|
||||
import { Component } from '@wordpress/element';
|
||||
import interpolateComponents from 'interpolate-components';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { WebPreview } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal depdencies
|
||||
*/
|
||||
import { recordEvent } from 'lib/tracks';
|
||||
|
||||
const devices = [
|
||||
{
|
||||
key: 'mobile',
|
||||
icon: 'phone_android',
|
||||
},
|
||||
{
|
||||
key: 'tablet',
|
||||
icon: 'tablet_android',
|
||||
},
|
||||
{
|
||||
key: 'desktop',
|
||||
icon: 'desktop_windows',
|
||||
},
|
||||
];
|
||||
|
||||
class ThemePreview extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.state = {
|
||||
device: 'desktop',
|
||||
};
|
||||
|
||||
this.handleDeviceClick = this.handleDeviceClick.bind( this );
|
||||
}
|
||||
|
||||
handleDeviceClick( device ) {
|
||||
const { theme } = this.props;
|
||||
recordEvent( 'storeprofiler_store_theme_demo_device', { device, theme: theme.slug } );
|
||||
this.setState( { device } );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onChoose, onClose, theme } = this.props;
|
||||
const { demo_url, slug, title } = theme;
|
||||
const { device: currentDevice } = this.state;
|
||||
|
||||
return (
|
||||
<div className="woocommerce-theme-preview">
|
||||
<div className="woocommerce-theme-preview__toolbar">
|
||||
<Button className="woocommerce-theme-preview__close" onClick={ onClose }>
|
||||
<i className="material-icons-outlined">close</i>
|
||||
</Button>
|
||||
<div className="woocommerce-theme-preview__theme-name">
|
||||
{ interpolateComponents( {
|
||||
/* translators: Describing who a previewed theme is developed by. E.g., Storefront developed by WooCommerce */
|
||||
mixedString: sprintf(
|
||||
__( '{{strong}}%s{{/strong}} developed by WooCommerce', 'woocommerce-admin' ),
|
||||
title
|
||||
),
|
||||
components: {
|
||||
strong: <strong />,
|
||||
},
|
||||
} ) }
|
||||
</div>
|
||||
<div className="woocommerce-theme-preview__devices">
|
||||
{ devices.map( device => (
|
||||
<Button
|
||||
key={ device.key }
|
||||
className={ classnames( 'woocommerce-theme-preview__device', {
|
||||
'is-selected': device.key === currentDevice,
|
||||
} ) }
|
||||
onClick={ () => this.handleDeviceClick( device.key ) }
|
||||
>
|
||||
<i className="material-icons-outlined">{ device.icon }</i>
|
||||
</Button>
|
||||
) ) }
|
||||
</div>
|
||||
<Button isPrimary onClick={ () => onChoose( slug, 'preview' ) }>
|
||||
{ __( 'Choose', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
</div>
|
||||
<WebPreview src={ demo_url } title={ title } className={ `is-${ currentDevice }` } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemePreview;
|
|
@ -87,7 +87,7 @@
|
|||
padding: $gap;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.woocommerce-profile-wizard__theme-status {
|
||||
|
@ -178,3 +178,114 @@
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-theme-preview {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100% !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.woocommerce-theme-preview__toolbar {
|
||||
background: $white;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
height: 56px;
|
||||
border-bottom: 1px solid $muriel-gray-50;
|
||||
padding-left: $gap;
|
||||
padding-right: $gap;
|
||||
align-items: center;
|
||||
|
||||
.muriel-button.is-button.is-primary {
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-theme-preview__theme-name {
|
||||
padding-left: $gap;
|
||||
color: #1a1a1a;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.woocommerce-theme-preview__close {
|
||||
padding: 0 $gap 0 0;
|
||||
color: $muriel-gray-500;
|
||||
}
|
||||
|
||||
.woocommerce-theme-preview__devices {
|
||||
margin-left: auto;
|
||||
margin-right: $gap;
|
||||
|
||||
.muriel-button {
|
||||
padding: $gap-small;
|
||||
color: $muriel-gray-500;
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
|
||||
&.is-selected,
|
||||
&:focus {
|
||||
background: $muriel-gray-500;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-web-preview {
|
||||
flex: 1;
|
||||
padding: $gap-largest $gap;
|
||||
overflow: scroll;
|
||||
|
||||
.woocommerce-web-preview__iframe-wrapper {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
box-shadow: $muriel-box-shadow-1dp;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
|
||||
iframe {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-mobile .woocommerce-web-preview__iframe-wrapper {
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
&.is-tablet .woocommerce-web-preview__iframe-wrapper {
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
&.is-desktop {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
|
||||
.woocommerce-web-preview__iframe-wrapper {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-theme-preview-active {
|
||||
overflow: hidden;
|
||||
|
||||
.woocommerce-profile-wizard__header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,5 +30,6 @@
|
|||
{ "component": "Table" },
|
||||
{ "component": "Tag" },
|
||||
{ "component": "TextControlWithAffixes" },
|
||||
{ "component": "ViewMoreList" }
|
||||
{ "component": "ViewMoreList" },
|
||||
{ "component": "WebPreview" }
|
||||
]
|
||||
|
|
|
@ -9303,8 +9303,7 @@
|
|||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
@ -9322,13 +9321,11 @@
|
|||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -9341,18 +9338,15 @@
|
|||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -9455,8 +9449,7 @@
|
|||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -9466,7 +9459,6 @@
|
|||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
@ -9479,20 +9471,17 @@
|
|||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
|
@ -9509,7 +9498,6 @@
|
|||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -9582,8 +9570,7 @@
|
|||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -9593,7 +9580,6 @@
|
|||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -9669,8 +9655,7 @@
|
|||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
@ -9700,7 +9685,6 @@
|
|||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
@ -9718,7 +9702,6 @@
|
|||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
@ -9757,13 +9740,11 @@
|
|||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
- Add new component `<List />` for displaying interactive list items.
|
||||
- Fix z-index issue in `<Chart />` empty message.
|
||||
- Added a new `<SimpleSelectControl />` component.
|
||||
- Added a new `<WebPreview />` component.
|
||||
|
||||
# 3.1.0
|
||||
- Added support for a countLabel prop on SearchListItem to allow custom counts.
|
||||
|
|
|
@ -56,3 +56,4 @@ export { default as Tag } from './tag';
|
|||
export { default as TextControlWithAffixes } from './text-control-with-affixes';
|
||||
export { default as useFilters } from './higher-order/use-filters';
|
||||
export { default as ViewMoreList } from './view-more-list';
|
||||
export { default as WebPreview } from './web-preview';
|
||||
|
|
|
@ -35,3 +35,4 @@
|
|||
@import 'tag/style.scss';
|
||||
@import 'text-control-with-affixes/style.scss';
|
||||
@import 'view-more-list/style.scss';
|
||||
@import 'web-preview/style.scss';
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
```jsx
|
||||
import { WebPreview } from '@woocommerce/components';
|
||||
|
||||
const MyWebPreview = () => (
|
||||
<div>
|
||||
<WebPreview src="https://themes.woocommerce.com/?name=galleria" title="My Web Preview" />
|
||||
</div>
|
||||
);
|
||||
```
|
|
@ -0,0 +1,90 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { Component, createRef } from '@wordpress/element';
|
||||
import { noop } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Spinner from '../spinner';
|
||||
|
||||
/**
|
||||
* WebPreview component to display an iframe of another page.
|
||||
*/
|
||||
class WebPreview extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
};
|
||||
|
||||
this.iframeRef = createRef();
|
||||
this.setLoaded = this.setLoaded.bind( this );
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.iframeRef.current.addEventListener( 'load', this.setLoaded );
|
||||
}
|
||||
|
||||
setLoaded() {
|
||||
this.setState( { isLoading: false } );
|
||||
this.props.onLoad();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, loadingContent, src, title } = this.props;
|
||||
const { isLoading } = this.state;
|
||||
|
||||
const classes = classnames( 'woocommerce-web-preview', className, {
|
||||
'is-loading': isLoading,
|
||||
} );
|
||||
|
||||
return (
|
||||
<div className={ classes }>
|
||||
{ isLoading && loadingContent }
|
||||
<div className="woocommerce-web-preview__iframe-wrapper">
|
||||
<iframe
|
||||
ref={ this.iframeRef }
|
||||
title={ title }
|
||||
src={ src }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WebPreview.propTypes = {
|
||||
/**
|
||||
* Additional class name to style the component.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Content shown when iframe is still loading.
|
||||
*/
|
||||
loadingContent: PropTypes.node,
|
||||
/**
|
||||
* Function to fire when iframe content is loaded.
|
||||
*/
|
||||
onLoad: PropTypes.func,
|
||||
/**
|
||||
* Iframe src to load.
|
||||
*/
|
||||
src: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Iframe title.
|
||||
*/
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
WebPreview.defaultProps = {
|
||||
loadingContent: <Spinner />,
|
||||
onLoad: noop,
|
||||
};
|
||||
|
||||
export default WebPreview;
|
|
@ -0,0 +1,23 @@
|
|||
.woocommerce-web-preview {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $muriel-gray-0;
|
||||
|
||||
&.is-loading {
|
||||
.woocommerce-web-preview__iframe-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-web-preview__iframe-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue