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

My Jetpack: add slider for recommendations section in My Jetpack #39850

Open
wants to merge 9 commits into
base: trunk
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Components: add ref for container component
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import clsx from 'clsx';
import { createElement, useMemo } from 'react';
import { createElement, forwardRef, useMemo } from 'react';
import { ContainerProps } from '../types';
import styles from './style.module.scss';
import type React from 'react';

/**
* JP Container
*
* @param {ContainerProps} props - Component properties.
* @param {ContainerProps} props - Component properties.
* @param {React.MutableRefObject} ref - Ref to the component
* @return {React.ReactElement} Container component.
*/
const Container: React.FC< ContainerProps > = ( {
children,
fluid = false,
tagName = 'div',
className,
horizontalGap = 1,
horizontalSpacing = 1,
} ) => {
const Container = (
{
children,
fluid = false,
tagName = 'div',
className,
horizontalGap = 1,
horizontalSpacing = 1,
}: ContainerProps,
ref: React.MutableRefObject< HTMLElement | null >
): React.ReactElement => {
const containerStyle = useMemo( () => {
const padding = `calc( var(--horizontal-spacing) * ${ horizontalSpacing } )`;
const rowGap = `calc( var(--horizontal-spacing) * ${ horizontalGap } )`;
Expand All @@ -38,9 +42,10 @@ const Container: React.FC< ContainerProps > = ( {
{
className: containerClassName,
style: containerStyle,
ref,
},
children
);
};

export default Container;
export default forwardRef< HTMLElement, ContainerProps >( Container );
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Container, Col, Text } from '@automattic/jetpack-components';
import { Flex, FlexItem, DropdownMenu, Button } from '@wordpress/components';
import { Icon } from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
import { __, _n } from '@wordpress/i18n';
import { moreHorizontalMobile } from '@wordpress/icons';
import { useEffect, useCallback } from 'react';
import { chevronLeft, chevronRight } from '@wordpress/icons';
import clsx from 'clsx';
import { useState, useEffect, useCallback, useRef } from 'react';
import useEvaluationRecommendations from '../../data/evaluation-recommendations/use-evaluation-recommendations';
import useAnalytics from '../../hooks/use-analytics';
import getPurchasePlanUrl from '../../utils/get-purchase-plan-url';
Expand All @@ -17,15 +20,57 @@ interface Props {
}

const EvaluationRecommendations: FC< Props > = ( { welcomeFlowExperimentVariation } ) => {
const containerRef = useRef( null );
const { recordEvent } = useAnalytics();
const { recommendedModules, isFirstRun, redoEvaluation, removeEvaluationResult } =
useEvaluationRecommendations();
const [ isAtStart, setIsAtStart ] = useState( true );
const [ isAtEnd, setIsAtEnd ] = useState( false );

const isTreatmentVariation = welcomeFlowExperimentVariation === 'treatment';

const checkScrollPosition = useCallback( () => {
if ( containerRef.current ) {
const { scrollLeft, scrollWidth, clientWidth } = containerRef.current;
setIsAtStart( scrollLeft === 0 );
setIsAtEnd( scrollLeft + clientWidth >= scrollWidth );
}
}, [ containerRef ] );

const handleExploreAllPlansLinkClick = useCallback( () => {
recordEvent( 'jetpack_myjetpack_evaluation_recommendations_explore_all_plans_click' );
}, [ recordEvent ] );

const handleSlide = (
cardContainerRef: React.RefObject< HTMLUListElement >,
direction: number,
gap: number = 24
) => {
if ( cardContainerRef.current ) {
const cardWidth = cardContainerRef.current.querySelector( 'li' ).clientWidth;

cardContainerRef.current.scrollBy( {
left: direction * ( cardWidth + gap ),
behavior: 'smooth',
} );
}
};

const handleNextSlide = useCallback( () => {
handleSlide( containerRef, 1 );

recordEvent( 'jetpack_myjetpack_recommendations_slide_arrow_click', {
direction: 'next',
} );
}, [ recordEvent, containerRef ] );

const handlePrevSlide = useCallback( () => {
handleSlide( containerRef, -1 );
recordEvent( 'jetpack_myjetpack_recommendations_slide_arrow_click', {
direction: 'previous',
} );
}, [ recordEvent, containerRef ] );

// We're defining each of these translations in separate variables here, otherwise optimizations in
// the build step end up breaking the translations and causing error.
const recommendationsHeadline = _n(
Expand All @@ -48,6 +93,21 @@ const EvaluationRecommendations: FC< Props > = ( { welcomeFlowExperimentVariatio
'jetpack-my-jetpack'
);

useEffect( () => {
const container = containerRef.current;

if ( container ) {
container.addEventListener( 'scroll', checkScrollPosition );
checkScrollPosition();
}

return () => {
if ( container ) {
container.removeEventListener( 'scroll', checkScrollPosition );
}
};
}, [ checkScrollPosition ] );

useEffect( () => {
recordEvent( 'jetpack_myjetpack_evaluation_recommendations_view', {
modules: recommendedModules,
Expand Down Expand Up @@ -99,6 +159,7 @@ const EvaluationRecommendations: FC< Props > = ( { welcomeFlowExperimentVariatio
</Col>
<Col>
<Container
ref={ containerRef }
tagName="ul"
className={ styles[ 'recommendations-list' ] }
horizontalGap={ 4 }
Expand All @@ -116,6 +177,30 @@ const EvaluationRecommendations: FC< Props > = ( { welcomeFlowExperimentVariatio
);
} ) }
</Container>
<Flex align="center" justify="center">
<FlexItem>
<Button
className={ clsx( styles[ 'slider-button' ], styles[ 'prev-button' ] ) }
onClick={ handlePrevSlide }
disabled={ isAtStart }
aria-disabled={ isAtStart }
aria-label={ __( 'Previous', 'jetpack-my-jetpack' ) }
>
<Icon icon={ chevronLeft } />
</Button>
</FlexItem>
<FlexItem>
<Button
className={ clsx( styles[ 'slider-button' ], styles[ 'next-button' ] ) }
onClick={ handleNextSlide }
disabled={ isAtEnd }
aria-disabled={ isAtEnd }
aria-label={ __( 'Next', 'jetpack-my-jetpack' ) }
>
<Icon icon={ chevronRight } />
</Button>
</FlexItem>
</Flex>
</Col>
{ isTreatmentVariation && (
<Col>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,42 @@
font-size: var( --font-body );
}

// Setting min-width of recommendation (product) cards to 300px; see: /components/product-cards-section/style.module.scss:62
@media screen and (min-width: 599px) and (max-width: 1290px) {
ul.recommendations-list {
grid-template-columns: repeat( auto-fill, minmax( 300px, 1fr ) );
> li {
grid-column-end: auto;
ul.recommendations-list {
grid-template-columns: 1fr;
grid-auto-flow: column;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;

scrollbar-width: none;
-ms-overflow-style: none;

&::-webkit-scrollbar {
display: none;
}

li {
min-width: 320px;
scroll-snap-align: start;
grid-column: unset;
grid-column-end: unset;
}

@media screen and (max-width: 1024px) {
li {
min-width: 450px;
}
}
}
}

.slider-button {
transition: all 0.3s ease;
}

.prev-button:not(:disabled):hover {
transform: translateX(-4px);
}

.next-button:not(:disabled):hover {
transform: translateX(4px);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { getMyJetpackWindowInitialState } from '../utils/get-my-jetpack-window-s
import isJetpackUserNew from '../utils/is-jetpack-user-new';
import useWelcomeBanner from '../welcome-banner/use-welcome-banner';

const NUMBER_OF_RECOMMENDATIONS_TO_SHOW = 5;

type SubmitRecommendationsResult = Record< string, number >;

const getInitialRecommendedModules = (): JetpackModule[] | null => {
Expand Down Expand Up @@ -42,8 +44,10 @@ const useEvaluationRecommendations = () => {
? [ 'anti-spam', 'creator', 'extras', 'stats', 'jetpack-ai' ]
: getMyJetpackWindowInitialState( 'lifecycleStats' )?.ownedProducts || []
) as JetpackModule[];
// We filter out owned modules, and return top 3 recommendations
return recommendedModules?.filter( module => ! ownedProducts.includes( module ) ).slice( 0, 3 );
// We filter out owned modules, and return the top recommendations
return recommendedModules
?.filter( module => ! ownedProducts.includes( module ) )
.slice( 0, NUMBER_OF_RECOMMENDATIONS_TO_SHOW );
}, [ recommendedModules ] );

const isEligibleForRecommendations = useMemo( () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards.
Loading