From a9fb3b8c834df09d7f5fade0f7551a88f160aa9d Mon Sep 17 00:00:00 2001 From: Javi A Date: Thu, 12 Mar 2020 09:30:26 +0100 Subject: [PATCH] Gutenboarding: new design selector step (#39972) * Gutenboarding: design selector * Gutenboarding: new design selector * Use onboarding-font-recoleta mixin. * Interface for DesignSelector component props. * Update design interface * Set resolveJsonModule at the client root Co-authored-by: Yan Sern Co-authored-by: Jon Surrell --- .../design-selector/available-designs.json | 28 ++ .../design-selector/design-card.tsx | 61 ---- .../design-selector/index.tsx | 174 ++-------- .../design-selector/page-layout-selector.tsx | 66 ---- .../design-selector/style.scss | 310 ++++-------------- .../gutenboarding/stores/onboard/actions.ts | 6 +- .../gutenboarding/stores/onboard/reducer.ts | 7 +- .../gutenboarding/stores/onboard/types.ts | 7 + client/tsconfig.json | 1 + 9 files changed, 137 insertions(+), 523 deletions(-) create mode 100644 client/landing/gutenboarding/onboarding-block/design-selector/available-designs.json delete mode 100644 client/landing/gutenboarding/onboarding-block/design-selector/design-card.tsx delete mode 100644 client/landing/gutenboarding/onboarding-block/design-selector/page-layout-selector.tsx diff --git a/client/landing/gutenboarding/onboarding-block/design-selector/available-designs.json b/client/landing/gutenboarding/onboarding-block/design-selector/available-designs.json new file mode 100644 index 0000000000000..2c172e10bb41a --- /dev/null +++ b/client/landing/gutenboarding/onboarding-block/design-selector/available-designs.json @@ -0,0 +1,28 @@ +{ + "featured": [ + { + "title": "Twenty twenty", + "slug": "twentytwenty", + "src": "https://s.wordpress.com/mshots/v1/https%3A%2F%2Ftwentytwentydemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?h=332&vpw=960&wph=960", + "srcset": "https://s.wordpress.com/mshots/v1/https%3A%2F%2Ftwentytwentydemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?w=480&h=332&vpw=960&wph=960 480w, https://s.wordpress.com/mshots/v1/https%3A%2F%2Ftwentytwentydemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?w=240&h=332&vpw=960&wph=960 240w" + }, + { + "title": "Mayland", + "slug": "mayland", + "src": "https://s.wordpress.com/mshots/v1/https%3A%2F%2Fmaylanddemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?h=332&vpw=960&wph=960", + "srcset": "https://s.wordpress.com/mshots/v1/https%3A%2F%2Fmaylanddemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?w=480&h=332&vpw=960&wph=960 480w, https://s.wordpress.com/mshots/v1/https%3A%2F%2Fmaylanddemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?w=240&h=332&vpw=960&wph=960 240w" + }, + { + "title": "Rockfield", + "slug": "rockfield", + "src": "https://s.wordpress.com/mshots/v1/https%3A%2F%2Frockfielddemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?h=332&vpw=960&wph=960", + "srcset": "https://s.wordpress.com/mshots/v1/https%3A%2F%2Frockfielddemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?w=480&h=332&vpw=960&wph=960 480w, https://s.wordpress.com/mshots/v1/https%3A%2F%2Frockfielddemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?w=240&h=332&vpw=960&wph=960 240w" + }, + { + "title": "Barnsbury", + "slug": "barnsbury", + "src": "https://s.wordpress.com/mshots/v1/https%3A%2F%2Fbarnsburydemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?h=332&vpw=960&wph=960", + "srcset": "https://s.wordpress.com/mshots/v1/https%3A%2F%2Fbarnsburydemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?w=480&h=332&vpw=960&wph=960 480w, https://s.wordpress.com/mshots/v1/https%3A%2F%2Fbarnsburydemo.wordpress.com%2F%3Ftheme_preview%3Dtrue?w=240&h=332&vpw=960&wph=960 240w" + } + ] +} diff --git a/client/landing/gutenboarding/onboarding-block/design-selector/design-card.tsx b/client/landing/gutenboarding/onboarding-block/design-selector/design-card.tsx deleted file mode 100644 index 22f5e6da4a0be..0000000000000 --- a/client/landing/gutenboarding/onboarding-block/design-selector/design-card.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/** - * External dependencies - */ -import React, { FunctionComponent, MouseEventHandler, CSSProperties } from 'react'; -import { addQueryArgs, removeQueryArgs } from '@wordpress/url'; -import { useI18n } from '@automattic/react-i18n'; - -/** - * Internal dependencies - */ -import { Card, CardMedia } from '@wordpress/components'; - -const gridWidth = 960; -const srcSet = ( src: string, widths: number[] ) => - widths.map( width => addQueryArgs( src, { w: width } ) + ` ${ width }w` ).join( ', ' ); - -interface Props { - design: import('@automattic/data-stores').VerticalsTemplates.Template; - onClick: MouseEventHandler< HTMLButtonElement >; - style?: CSSProperties; - dialogId: string; - tabIndex: number; -} -const DesignCard: FunctionComponent< Props > = ( { - design, - dialogId, - onClick, - style, - tabIndex = 0, -} ) => { - const { __: NO__ } = useI18n(); - return ( - - - { - - - { NO__( 'Select this design' ) } - - - - - ); -}; - -export default DesignCard; diff --git a/client/landing/gutenboarding/onboarding-block/design-selector/index.tsx b/client/landing/gutenboarding/onboarding-block/design-selector/index.tsx index ae2d23e7adfc5..dc4440ddaa863 100644 --- a/client/landing/gutenboarding/onboarding-block/design-selector/index.tsx +++ b/client/landing/gutenboarding/onboarding-block/design-selector/index.tsx @@ -2,181 +2,65 @@ * External dependencies */ import { useDispatch, useSelect } from '@wordpress/data'; -import React, { useLayoutEffect, useRef, FunctionComponent } from 'react'; +import React, { FunctionComponent } from 'react'; import classnames from 'classnames'; -import PageLayoutSelector from './page-layout-selector'; -import { partition } from 'lodash'; -import { useDialogState, Dialog } from 'reakit/Dialog'; -import { useSpring, animated } from 'react-spring'; -import { useHistory } from 'react-router-dom'; -import { Step, usePath } from '../../path'; import { useI18n } from '@automattic/react-i18n'; /** * Internal dependencies */ import { STORE_KEY as ONBOARD_STORE } from '../../stores/onboard'; -import DesignCard from './design-card'; +import designs from './available-designs.json'; import './style.scss'; -import { VerticalsTemplates } from '@automattic/data-stores'; - -type Template = VerticalsTemplates.Template; - -const VERTICALS_TEMPLATES_STORE = VerticalsTemplates.register(); - interface Props { - showPageSelector?: boolean; + showPageSelector?: true; } -const DesignSelector: FunctionComponent< Props > = ( { showPageSelector = false } ) => { +const DesignSelector: FunctionComponent< Props > = () => { const { __: NO__ } = useI18n(); const { selectedDesign, siteVertical } = useSelect( select => select( ONBOARD_STORE ).getState() ); const { setSelectedDesign } = useDispatch( ONBOARD_STORE ); - // @FIXME: If we don't have an ID (because we're dealing with a user-supplied vertical that - // WordPress.com doesn't know about), fall back to the 'm1' (Business) vertical. This is the - // vertical that the endpoint would fall back to anyway if an unknown ID is passed. - // This seems okay since the list of templates currently appears to be the same for all verticals - // anyway. - // We should modify the endpoint (or rather, add a `verticals/templates` route that doesn't require - // a vertical ID) for this case. - const templates = - useSelect( select => - select( VERTICALS_TEMPLATES_STORE ).getTemplates( siteVertical?.id ?? 'm1' ) - ) ?? []; - - const [ designs, otherTemplates ] = partition( - templates, - ( { category } ) => category === 'home' - ); - - const headingContainer = useRef< HTMLDivElement >( null ); - const selectionTransitionShift = useRef< number >( 0 ); - useLayoutEffect( () => { - if ( headingContainer.current ) { - // We'll use this height to move the heading up out of the viewport. - const rect = headingContainer.current.getBoundingClientRect(); - selectionTransitionShift.current = rect.height; - } - }, [ selectedDesign ] ); - - const dialogId = 'page-selector-modal'; - const dialog = useDialogState( { visible: false, baseId: dialogId } ); - - const descriptionOnRight: boolean = - !! selectedDesign && - designs.findIndex( ( { slug } ) => slug === selectedDesign.slug ) % 2 === 0; - - const designSelectorSpring = useSpring( { - transform: `translate3d( 0, ${ - showPageSelector ? -selectionTransitionShift.current : 0 - }px, 0 )`, - } ); - - const descriptionContainerSpring = useSpring( { - transform: `translate3d( 0, ${ showPageSelector ? '0' : '100vh' }, 0 )`, - visibility: showPageSelector ? 'visible' : 'hidden', - } ); - - const pageSelectorSpring = useSpring( { - transform: `translate3d( 0, ${ showPageSelector ? '0' : '100vh' }, 0 )`, - onStart: () => { - showPageSelector && dialog.show(); - }, - onRest: () => { - ! showPageSelector && dialog.hide(); - }, - } ); - - const history = useHistory(); - const makePath = usePath(); - return ( - -
-

- { NO__( 'Choose a starting design for your site' ) } -

+
+
+

{ NO__( 'Choose a starting design' ) }

- { NO__( "You'll be able to customize your new site in hundreds of ways." ) } + { NO__( + 'Get started with one of our top website layouts. You can always change it later' + ) }

-
+
- { designs.map( design => ( - ( + ) ) }
- - -
{ selectedDesign?.title }
-
- { /* @TODO: Real description? */ } - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. -
-
- - - { - history.push( makePath( Step.DesignSelection ) ); - } } - aria-labelledby="page-layout-selector__title" - hideOnClickOutside={ false } - hideOnEsc - > - - - - +
); }; diff --git a/client/landing/gutenboarding/onboarding-block/design-selector/page-layout-selector.tsx b/client/landing/gutenboarding/onboarding-block/design-selector/page-layout-selector.tsx deleted file mode 100644 index 8f52c98210fe2..0000000000000 --- a/client/landing/gutenboarding/onboarding-block/design-selector/page-layout-selector.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/** - * External dependencies - */ -import React, { FunctionComponent } from 'react'; -import classnames from 'classnames'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { useI18n } from '@automattic/react-i18n'; - -/** - * Internal dependencies - */ -import { Card, CardFooter, CardMedia, Icon } from '@wordpress/components'; -import { removeQueryArgs } from '@wordpress/url'; -import { STORE_KEY as ONBOARD_STORE } from '../../stores/onboard'; - -type Template = import('@automattic/data-stores').VerticalsTemplates.Template; - -interface Props { - templates: Template[]; -} - -const PageLayoutSelector: FunctionComponent< Props > = ( { templates } ) => { - const { __: NO__ } = useI18n(); - const { pageLayouts } = useSelect( select => select( ONBOARD_STORE ).getState() ); - const { togglePageLayout } = useDispatch( ONBOARD_STORE ); - - return ( -
-
-

- { NO__( "Select the pages you'd like to include:" ) } -

-
- { templates.map( template => ( - togglePageLayout( template ) } - key={ template.slug } - > - - { - - - { template.title } - - - - - - ) ) } -
-
-
- ); -}; - -export default PageLayoutSelector; diff --git a/client/landing/gutenboarding/onboarding-block/design-selector/style.scss b/client/landing/gutenboarding/onboarding-block/design-selector/style.scss index 9881b8afbcaa0..fef9d78db8840 100644 --- a/client/landing/gutenboarding/onboarding-block/design-selector/style.scss +++ b/client/landing/gutenboarding/onboarding-block/design-selector/style.scss @@ -1,268 +1,94 @@ @import 'assets/stylesheets/gutenberg-base-styles'; @import '~@wordpress/base-styles/colors'; - -$design-selector-grid-gap: 4.5em; - -$design-selector-max-width: 1100px; -$design-selector-selection-space: 285px + $header-height; -$page-selector-width-offset: 20px; -$page-selector-shadow-size: 12px; - -.design-selector__title { - color: $dark-gray-800; - font-size: 2em; - font-weight: bold; - margin-bottom: 0.25em; - text-align: center; -} - -.design-selector__subtitle { - color: $dark-gray-800; - font-size: 1.3em; - text-align: center; -} - -.design-selector__grid-container { - position: absolute; - padding-bottom: 48px; - padding-top: 19px; - - &.is-page-selector-open { - overflow-y: hidden; - height: 100vh; - } -} - -.design-selector__grid { - display: grid; - grid-gap: $design-selector-grid-gap; - - @include breakpoint( '>660px' ) { - grid-template-columns: 1fr 1fr; - } -} - -.design-selector__page-layout-backdrop { +@import '../../mixins'; + +@font-face { + font-display: swap; + font-family: 'Recoleta'; + font-weight: 400; + src: url( 'https://s1.wp.com/i/fonts/recoleta/400.eot' ); + src: url( 'https://s1.wp.com/i/fonts/recoleta/400.eot?#iefix' ) format( 'embedded-opentype' ), + url( 'https://s1.wp.com/i/fonts/recoleta/400.woff2' ) format( 'woff2' ), + url( 'https://s1.wp.com/i/fonts/recoleta/400.woff' ) format( 'woff' ), + url( 'https://s1.wp.com/i/fonts/recoleta/400.ttf' ) format( 'truetype' ); +} + +.design-selector { + width: 90%; + height: 90%; + background-color: var( --contrastColor ); + color: var( --mainColor ); + padding: 5% 5% 0; position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -.design-selector__page-layout-container { - position: absolute; - top: $design-selector-selection-space; - width: calc( 100vw - #{$page-selector-width-offset} ); - background: $white; - margin-left: calc( ( ( 100vw - #{$page-selector-width-offset} ) - 100% ) / -2 ); - - &.is-open { - padding: 2em 4em; - box-shadow: $white 0 $page-selector-shadow-size 0 $page-selector-width-offset, - $dark-gray-300 0 ( $page-selector-shadow-size - $page-selector-width-offset ) - $page-selector-shadow-size; - } -} - -.page-layout-selector { - max-width: $design-selector-max-width; - margin: 0 auto; -} - -.design-selector__header-container { - padding-top: 48px; - padding-bottom: 29px; -} - -.page-layout-selector__title { - font-weight: bold; - margin-bottom: 1em; - font-size: 1.3em; -} - -.page-layout-selector__grid { - display: grid; - grid-gap: 1.25em; - grid-template-columns: repeat( auto-fill, minmax( 280px, 1fr ) ); -} - -.page-layout-selector__item { - position: relative; - overflow: hidden; - padding: 2px; - cursor: pointer; - - &::before { - content: ''; - display: inline-block; - width: 1px; - height: 0; - padding-bottom: calc( - 100% * 7 / 8 - ); // This gives us a 7:8 aspect ratio for individual grid items. - } - - &.is-selected { - .page-layout-selector__selected-indicator { - top: -2px; - right: -2px; - transform: translate3d( 0, 0, 0 ); - } + top: 0; + left: 0; - // Offset for the 2px of added border when hovered. - &:hover { - .page-layout-selector__selected-indicator { - top: -4px; - right: -4px; - } - } - } + .design-selector__header { + width: 100%; + margin: 5% 0%; + @include onboarding-font-recoleta; - &:hover { - border: 3px solid var( --color-neutral-dark ); - padding: 0; - // Offset absolute-positioned card content when border state occurs - .page-layout-selector__card-media { - width: calc( 100% + 4px ); /* This number is the change in border width when hovered * 2 */ - top: -2px; /* This is half of the above number. */ + h1 { + color: var( --mainColor ); + font-size: 42px; } - - .page-layout-selector__card-footer { - bottom: -2px; - width: calc( 100% + 4px ); + h2 { + color: var( --mainColor ); + font-size: 16px; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; } } -} - -.page-layout-selector__selected-indicator { - $size: 24px; // should match icon size - $full-size: floor( $size * 1.66 ); - position: absolute; - top: 0; - right: 0; - width: $full-size; - height: $full-size; - - text-align: right; - color: $white; - background: linear-gradient( - to bottom left, - $blue-medium-focus, - $blue-medium-focus 50%, - transparent 51% /* 1% difference helps prevent a rough edge */ - ); - - transition: 80ms transform linear; - transform: translate3d( $size, -$size, 0 ); -} -.design-selector__design-option { - position: relative; - cursor: pointer; - - .design-selector__option-overlay { - background-color: rgba( var( --color-primary-rgb ), 0.8 ); - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + .design-selector__grid { display: flex; - align-items: center; - justify-content: center; - opacity: 0; - transition: 400ms; - - .design-selector__option-overlay-text { - font-size: 24px; - color: $white; - border-bottom: 2px solid $white; - } + flex-wrap: wrap; + justify-content: space-between; } - &:hover { - .design-selector__option-overlay { - opacity: 1; + .design-selector__design-option { + width: 48%; + margin-bottom: 24px; + + &.selected .design-selector__image-frame { + border: 2px solid var( --studio-green-20 ); } } -} -// Adjust to display full with accounting for padding/borders -.page-layout-selector__card-footer, -.page-layout-selector__card-media { - display: block; - margin-left: -2px; - margin-right: -2px; -} + .design-selector__image-frame { + display: block; + height: 406px; + border: 1px solid var( --studio-gray-5 ); + border-radius: 4px; + overflow: hidden; + margin: 8px; + transition: transform 0.2s; + box-shadow: rgba( 0, 0, 0, 0.2 ) 0 0 0; -// Necessary for grid layout -.page-layout-selector__card-footer { - background: $white; - bottom: 0; - position: absolute; - width: 100%; -} + &:hover { + transform: scale( 1.03 ); + box-shadow: rgba( 0, 0, 0, 0.2 ) 0 0 15px; + } -// Necessary for grid layout -.page-layout-selector__card-media { - position: absolute; - top: 0; - width: 100%; -} -.design-selector__description-container { - position: fixed; - display: flex; - flex-direction: column; - width: calc( 50% - #{$design-selector-grid-gap} ); - padding-top: 23px; - - @include breakpoint( '>660px' ) { - &.on-right-side { - right: 0; + @include breakpoint( '<1400px' ) { + height: 326px; } - } -} -.design-selector__description-title { - color: $dark-gray-800; - font-size: 2em; - font-weight: bold; - margin-bottom: 0.8em; -} -.design-selector__description-description { - color: $dark-gray-800; - font-size: 1.3em; - line-height: 1.3; -} - -.accessible-focus { - .page-layout-selector__item { - &:focus { - box-shadow: 0 0 0 1px $white, 0 0 0 3px $blue-medium-500; - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; + @include breakpoint( '<1280px' ) { + height: 243px; } - } - .design-selector__design-option { - &:focus { - .design-selector__option-overlay { - opacity: 1; - } + @include breakpoint( '<660px' ) { + height: 163px; } } -} -// When page selector is open, design card should behave like a static image. -.design-selector__grid-container { - &.is-page-selector-open { - .design-selector__design-option { - cursor: default; - } - .design-selector__option-overlay { - display: none; - } + .design-selector__option-name { + margin-top: 16px; + display: block; + text-align: center; + width: 100%; + font-size: 24px; + @include onboarding-font-recoleta; } -} +} \ No newline at end of file diff --git a/client/landing/gutenboarding/stores/onboard/actions.ts b/client/landing/gutenboarding/stores/onboard/actions.ts index dc930ed6a4d56..ed8ee8ae37979 100644 --- a/client/landing/gutenboarding/stores/onboard/actions.ts +++ b/client/landing/gutenboarding/stores/onboard/actions.ts @@ -6,7 +6,7 @@ import { VerticalsTemplates } from '@automattic/data-stores'; /** * Internal dependencies */ -import { SiteVertical } from './types'; +import { Design, SiteVertical } from './types'; type Template = VerticalsTemplates.Template; @@ -17,9 +17,7 @@ export const setDomain = ( domain, } ); -export const setSelectedDesign = ( - selectedDesign: import('@automattic/data-stores').VerticalsTemplates.Template | undefined -) => ( { +export const setSelectedDesign = ( selectedDesign: Design | undefined ) => ( { type: 'SET_SELECTED_DESIGN' as const, selectedDesign, } ); diff --git a/client/landing/gutenboarding/stores/onboard/reducer.ts b/client/landing/gutenboarding/stores/onboard/reducer.ts index 7b407c1b55c51..4ae3687543087 100644 --- a/client/landing/gutenboarding/stores/onboard/reducer.ts +++ b/client/landing/gutenboarding/stores/onboard/reducer.ts @@ -7,7 +7,7 @@ import { combineReducers } from '@wordpress/data'; /** * Internal dependencies */ -import { SiteVertical } from './types'; +import { SiteVertical, Design } from './types'; import { OnboardAction } from './actions'; const domain: Reducer< @@ -23,10 +23,7 @@ const domain: Reducer< return state; }; -const selectedDesign: Reducer< - import('@automattic/data-stores').VerticalsTemplates.Template | undefined, - OnboardAction -> = ( state, action ) => { +const selectedDesign: Reducer< Design | undefined, OnboardAction > = ( state, action ) => { if ( action.type === 'SET_SELECTED_DESIGN' ) { return action.selectedDesign; } diff --git a/client/landing/gutenboarding/stores/onboard/types.ts b/client/landing/gutenboarding/stores/onboard/types.ts index b63d3b6e6e51d..fa8bab6c73527 100644 --- a/client/landing/gutenboarding/stores/onboard/types.ts +++ b/client/landing/gutenboarding/stores/onboard/types.ts @@ -15,3 +15,10 @@ export interface SiteVertical { */ id?: string; } + +export interface Design { + title: string; + slug: string; + src: string; + srcset: string; +} diff --git a/client/tsconfig.json b/client/tsconfig.json index 30f8a0a98e74c..b2760bc7c335d 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -4,6 +4,7 @@ // Disallow features that require cross-file information for emit. // Must be used with babel typescript "isolatedModules": true, + "resolveJsonModule": true, "baseUrl": ".", "rootDir": ".",