diff --git a/src/components/EditListingWizard/EditListingWizard.css b/src/components/EditListingWizard/EditListingWizard.css
index f24ab657..6a00506d 100644
--- a/src/components/EditListingWizard/EditListingWizard.css
+++ b/src/components/EditListingWizard/EditListingWizard.css
@@ -18,15 +18,17 @@
/* Layout */
display: flex;
flex-direction: row;
- padding: 0 24px 0 24px;
+ padding: 0;
flex-shrink: 0;
background-color: var(--matterColorLight);
box-shadow: var(--boxShadowLight);
border-top: 1px solid var(--matterColorNegative);
+ overflow-x: scroll;
@media (--viewportLarge) {
padding: 126px 0 82px 15vw;
flex-direction: column;
+ overflow-x: auto;
background-color: var(--matterColorBright);
box-shadow: none;
@@ -43,10 +45,26 @@
&:first-child {
margin-left: 0;
+
+ /* Padding added to tab so that it will be visible after call to scrollToTab */
+ padding-left: 24px;
+ }
+
+ &:last-child {
+ /* Padding added to tab so that it will be visible after call to scrollToTab */
+ padding-right: 24px;
}
@media (--viewportLarge) {
margin-left: 0;
+
+ &:first-child {
+ padding-left: 0;
+ }
+
+ &:last-child {
+ padding-right: 0;
+ }
}
}
diff --git a/src/components/EditListingWizard/EditListingWizard.js b/src/components/EditListingWizard/EditListingWizard.js
index b3d123a0..f1aa91bc 100644
--- a/src/components/EditListingWizard/EditListingWizard.js
+++ b/src/components/EditListingWizard/EditListingWizard.js
@@ -1,9 +1,11 @@
-import React from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
+import { compose } from 'redux';
import { injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import routeConfiguration from '../../routeConfiguration';
import { propTypes } from '../../util/types';
+import { withViewport } from '../../util/contextHelpers';
import { ensureListing } from '../../util/data';
import { createResourceLocatorString } from '../../util/routes';
import {
@@ -23,6 +25,9 @@ const PRICING = 'pricing';
const PHOTOS = 'photos';
const STEPS = [DESCRIPTION, LOCATION, PRICING, PHOTOS];
+// Tabs are horizontal in small screens
+const MAX_HORIZONTAL_NAV_SCREEN_WIDTH = 1023;
+
const submitText = (intl, isNew, step) => {
let key = null;
if (step === DESCRIPTION) {
@@ -66,189 +71,231 @@ const stepsActive = listing => {
};
};
+const scrollToTab = (tabPrefix, tabId) => {
+ const el = document.querySelector(`#${tabPrefix}_${tabId}`);
+ if (el) {
+ el.scrollIntoView({
+ block: 'start',
+ behavior: 'smooth',
+ });
+ }
+};
+
// Create a new or edit listing through EditListingWizard
-const EditListingWizard = props => {
- const {
- className,
- params,
- errors,
- fetchInProgress,
- newListingCreated,
- history,
- images,
- listing,
- onCreateListing,
- onUpdateListing,
- onCreateListingDraft,
- onImageUpload,
- onPayoutDetailsFormChange,
- onPayoutDetailsSubmit,
- onUpdateImageOrder,
- onRemoveImage,
- onUpdateListingDraft,
- onChange,
- rootClassName,
- currentUser,
- onManageDisableScrolling,
- updatedTab,
- updateInProgress,
- intl,
- } = props;
+class EditListingWizard extends Component {
+ constructor(props) {
+ super(props);
- const isNew = params.type === 'new';
- const selectedTab = params.tab;
- const rootClasses = rootClassName || css.root;
- const classes = classNames(rootClasses, className);
- const currentListing = ensureListing(listing);
- const stepsStatus = stepsActive(currentListing);
-
- const tabParams = tab => {
- return { ...params, tab };
- };
- const tabLink = tab => {
- return { name: 'EditListingPage', params: tabParams(tab) };
- };
-
- // If selectedStep is not active, redirect to the beginning of wizard
- if (!stepsStatus[selectedTab]) {
- return ;
+ // Having this info in state would trigger unnecessary rerendering
+ this.hasScrolledToTab = false;
}
- const onUpsertListingDraft = currentListing.id ? onUpdateListingDraft : onCreateListingDraft;
- const update = (tab, values) => {
- onUpdateListing(tab, { ...values, id: currentListing.id });
- };
+ render() {
+ const {
+ id,
+ className,
+ params,
+ errors,
+ fetchInProgress,
+ newListingCreated,
+ history,
+ images,
+ listing,
+ onCreateListing,
+ onUpdateListing,
+ onCreateListingDraft,
+ onImageUpload,
+ onPayoutDetailsFormChange,
+ onPayoutDetailsSubmit,
+ onUpdateImageOrder,
+ onRemoveImage,
+ onUpdateListingDraft,
+ onChange,
+ rootClassName,
+ currentUser,
+ onManageDisableScrolling,
+ updatedTab,
+ updateInProgress,
+ intl,
+ viewport,
+ } = this.props;
- return (
-
- {
- const { title, description, category } = values;
- const updateValues = {
- title,
- description,
- customAttributes: { category },
- publicData: { category },
- };
+ const isNew = params.type === 'new';
+ const selectedTab = params.tab;
+ const rootClasses = rootClassName || css.root;
+ const classes = classNames(rootClasses, className);
+ const currentListing = ensureListing(listing);
+ const stepsStatus = stepsActive(currentListing);
- if (isNew) {
- onUpsertListingDraft(updateValues);
- const pathParams = tabParams(LOCATION);
- // Redirect to location tab
- history.push(
- createResourceLocatorString('EditListingPage', routeConfiguration(), pathParams, {})
- );
- } else {
- update(DESCRIPTION, updateValues);
- }
- }}
- />
- {
- const { building = '', location } = values;
- const { selectedPlace: { address, origin } } = location;
- const updateValues = {
- geolocation: origin,
- publicData: {
- location: { address, building },
- },
- };
+ const tabParams = tab => {
+ return { ...params, tab };
+ };
+ const tabLink = tab => {
+ return { name: 'EditListingPage', params: tabParams(tab) };
+ };
- if (isNew) {
- onUpdateListingDraft(updateValues);
- const pathParams = tabParams(PRICING);
- // Redirect to pricing tab
- history.push(
- createResourceLocatorString('EditListingPage', routeConfiguration(), pathParams, {})
- );
- } else {
- update(LOCATION, updateValues);
- }
- }}
- />
- {
- if (isNew) {
- onUpdateListingDraft(values);
- const pathParams = tabParams(PHOTOS);
- // Redirect to photos tab
- history.push(
- createResourceLocatorString('EditListingPage', routeConfiguration(), pathParams, {})
- );
- } else {
- update(PRICING, values);
- }
- }}
- />
- {
- const { images: updatedImages } = values;
- const updateValues = { ...listing.attributes, images: updatedImages };
- if (isNew) {
- onCreateListing(updateValues);
- } else {
- const imageIds = updatedImages.map(img => img.imageId || img.id);
- update(PHOTOS, { images: imageIds });
- }
- }}
- onUpdateImageOrder={onUpdateImageOrder}
- currentUser={currentUser}
- onManageDisableScrolling={onManageDisableScrolling}
- />
-
- );
-};
+ // If selectedStep is not active, redirect to the beginning of wizard
+ if (!stepsStatus[selectedTab]) {
+ return ;
+ }
+
+ const onUpsertListingDraft = currentListing.id ? onUpdateListingDraft : onCreateListingDraft;
+ const update = (tab, values) => {
+ onUpdateListing(tab, { ...values, id: currentListing.id });
+ };
+
+ const { width } = viewport;
+ const hasViewport = width > 0;
+ const hasHorizontalTabLayout = hasViewport && width <= MAX_HORIZONTAL_NAV_SCREEN_WIDTH;
+ const hasVerticalTabLayout = hasViewport && width > MAX_HORIZONTAL_NAV_SCREEN_WIDTH;
+ const hasFontsLoaded =
+ hasViewport && document.documentElement.classList.contains('fontsLoaded');
+
+ // Check if scrollToTab call is needed (tab is not visible on mobile)
+ if (hasVerticalTabLayout) {
+ this.hasScrolledToTab = true;
+ } else if (hasHorizontalTabLayout && !this.hasScrolledToTab && hasFontsLoaded) {
+ const tabPrefix = id;
+ scrollToTab(tabPrefix, selectedTab);
+ this.hasScrolledToTab = true;
+ }
+
+ return (
+
+ {
+ const { title, description, category } = values;
+ const updateValues = {
+ title,
+ description,
+ customAttributes: { category },
+ publicData: { category },
+ };
+
+ if (isNew) {
+ onUpsertListingDraft(updateValues);
+ const pathParams = tabParams(LOCATION);
+ // Redirect to location tab
+ history.push(
+ createResourceLocatorString('EditListingPage', routeConfiguration(), pathParams, {})
+ );
+ } else {
+ update(DESCRIPTION, updateValues);
+ }
+ }}
+ />
+ {
+ const { building = '', location } = values;
+ const { selectedPlace: { address, origin } } = location;
+ const updateValues = {
+ geolocation: origin,
+ publicData: {
+ location: { address, building },
+ },
+ };
+
+ if (isNew) {
+ // TODO When API supports building number, etc. change this to use those fields instead.
+ onUpdateListingDraft(updateValues);
+ const pathParams = tabParams(PRICING);
+ // Redirect to pricing tab
+ history.push(
+ createResourceLocatorString('EditListingPage', routeConfiguration(), pathParams, {})
+ );
+ } else {
+ update(LOCATION, updateValues);
+ }
+ }}
+ />
+ {
+ if (isNew) {
+ onUpdateListingDraft(values);
+ const pathParams = tabParams(PHOTOS);
+ // Redirect to photos tab
+ history.push(
+ createResourceLocatorString('EditListingPage', routeConfiguration(), pathParams, {})
+ );
+ } else {
+ update(PRICING, values);
+ }
+ }}
+ />
+ {
+ const { country, images: updatedImages } = values;
+ const updateValues = { ...listing.attributes, country, images: updatedImages };
+ if (isNew) {
+ onCreateListing(updateValues);
+ } else {
+ const imageIds = updatedImages.map(img => img.imageId || img.id);
+ update(PHOTOS, { images: imageIds });
+ }
+ }}
+ onUpdateImageOrder={onUpdateImageOrder}
+ currentUser={currentUser}
+ onManageDisableScrolling={onManageDisableScrolling}
+ />
+
+ );
+ }
+}
EditListingWizard.defaultProps = {
className: null,
@@ -259,9 +306,10 @@ EditListingWizard.defaultProps = {
updatedTab: null,
};
-const { array, bool, func, object, oneOf, shape, string } = PropTypes;
+const { array, bool, func, number, object, oneOf, shape, string } = PropTypes;
EditListingWizard.propTypes = {
+ id: string.isRequired,
className: string,
params: shape({
id: string.isRequired,
@@ -311,8 +359,14 @@ EditListingWizard.propTypes = {
updatedTab: string,
updateInProgress: bool.isRequired,
+ // from withViewport
+ viewport: shape({
+ width: number.isRequired,
+ height: number.isRequired,
+ }).isRequired,
+
// from injectIntl
intl: intlShape.isRequired,
};
-export default injectIntl(EditListingWizard);
+export default compose(withViewport, injectIntl)(EditListingWizard);
diff --git a/src/components/TabNav/TabNav.js b/src/components/TabNav/TabNav.js
index 53262f9e..9ba8fdc5 100644
--- a/src/components/TabNav/TabNav.js
+++ b/src/components/TabNav/TabNav.js
@@ -6,14 +6,14 @@ import { NamedLink } from '../../components';
import css from './TabNav.css';
const Tab = props => {
- const { className, disabled, text, selected, linkProps } = props;
+ const { className, id, disabled, text, selected, linkProps } = props;
const linkClasses = classNames(css.link, {
[css.selectedLink]: selected,
[css.disabled]: disabled,
});
return (
-
+
{text}
@@ -26,6 +26,7 @@ Tab.defaultProps = { className: null, disabled: false, selected: false };
const { arrayOf, bool, node, object, string } = PropTypes;
Tab.propTypes = {
+ id: string.isRequired,
className: string,
text: node.isRequired,
disabled: bool,
@@ -40,8 +41,8 @@ const TabNav = props => {
return (
);
diff --git a/src/components/Tabs/Tabs.example.js b/src/components/Tabs/Tabs.example.js
index bcf21fad..ed7a85ed 100644
--- a/src/components/Tabs/Tabs.example.js
+++ b/src/components/Tabs/Tabs.example.js
@@ -20,13 +20,13 @@ const selfLinkProps = {
const TabsWrapper = () => {
return (
-
+
Description form stuff
-
+
Location form stuff
-
+
Price form stuff
diff --git a/src/components/Tabs/Tabs.js b/src/components/Tabs/Tabs.js
index aaf8a1a4..d7b19071 100644
--- a/src/components/Tabs/Tabs.js
+++ b/src/components/Tabs/Tabs.js
@@ -28,18 +28,20 @@ const Tabs = props => {
const classes = classNames(rootClasses, className);
const tabNavTabs = React.Children.map(children, child => {
- const { tabLabel, tabLinkProps } = child.props;
+ const { tabId, tabLabel, tabLinkProps } = child.props;
// Child components need to have TabNav props included
- if (!tabLabel || !tabLinkProps) {
+ if (!tabId || !tabLabel || !tabLinkProps) {
throw new Error(
`Tabs component: a child component is missing required props.
+ tabId: (${tabId})
tabLabel: (${tabLabel})
tabLinkProps: (${tabLinkProps})`
);
}
return {
+ id: tabId,
text: child.props.tabLabel,
linkProps: child.props.tabLinkProps,
disabled: child.props.disabled,
diff --git a/src/containers/EditListingPage/EditListingPage.js b/src/containers/EditListingPage/EditListingPage.js
index 070c7ef2..6ca4b854 100644
--- a/src/containers/EditListingPage/EditListingPage.js
+++ b/src/containers/EditListingPage/EditListingPage.js
@@ -120,6 +120,7 @@ export const EditListingPageComponent = props => {
mobileClassName={css.mobileTopbar}
/>
-