Add tour kit component (#33229)
* Add tour-kit component * Add @types/wordpress__viewport to @woocommerce/components devDeps * Add tour-kit README.md * Add primaryButtonText option to tour kit step meta * Add changelog * Remove unneeded style import * Set position and z-index style for tour-kit * Add disable primary button feature for tour kit * Export TourKitTypes * Update style-build config for @automattic/* packages * Add @automattic/* deps for components * Ignore fs in webpack.config.js * Update tour-kit stories * Add tour-kit tests * Update tour-kit README.md * Update tour-kit types * Update webpack.config.js * Update style.scss * Add auto focus feature * Update type doc
This commit is contained in:
parent
be15a35038
commit
5db5c8b110
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add tour kit component
|
|
@ -26,7 +26,9 @@
|
|||
],
|
||||
"types": "build-types",
|
||||
"dependencies": {
|
||||
"@automattic/calypso-color-schemes": "^2.1.1",
|
||||
"@automattic/interpolate-components": "^1.2.0",
|
||||
"@automattic/tour-kit": "^1.0.0",
|
||||
"@woocommerce/csv-export": "workspace:*",
|
||||
"@woocommerce/currency": "workspace:*",
|
||||
"@woocommerce/data": "workspace:*",
|
||||
|
@ -93,6 +95,7 @@
|
|||
"@testing-library/jest-dom": "^5.16.2",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/wordpress__viewport": "^2.5.4",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@woocommerce/internal-style-build": "workspace:*",
|
||||
"@wordpress/browserslist-config": "^4.1.1",
|
||||
|
|
|
@ -58,3 +58,5 @@ export { default as ViewMoreList } from './view-more-list';
|
|||
export { default as WebPreview } from './web-preview';
|
||||
export { Badge } from './badge';
|
||||
export { DynamicForm } from './dynamic-form';
|
||||
export { default as TourKit } from './tour-kit';
|
||||
export * as TourKitTypes from './tour-kit/types';
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* External Dependencies
|
||||
*/
|
||||
@import '@automattic/tour-kit/dist/esm/styles.scss';
|
||||
|
||||
/**
|
||||
* Internal Dependencies
|
||||
*/
|
||||
|
@ -38,3 +43,4 @@
|
|||
@import 'web-preview/style.scss';
|
||||
@import 'badge/style.scss';
|
||||
@import 'dynamic-form/style.scss';
|
||||
@import 'tour-kit/style.scss';
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
# tour-kit
|
||||
|
||||
A Woocommerce Tour Kit variant based on [@automattic/tour-kit](https://github.com/Automattic/wp-calypso/blob/trunk/packages/tour-kit/README.md) for configurable guided tours. Contains some optional effects (like spotlight and overlay) that can be enabled/disabled depending on the desired use..
|
||||
|
||||
Uses [Popper.js](https://popper.js.org/) underneath (also customizable via tour configuration).
|
||||
|
||||
## Usage
|
||||
|
||||
A typical expected workflow builds around:
|
||||
|
||||
1. Define the criteria for showing a tour.
|
||||
2. Define a configuration for the tour, passing along a handler for closing.
|
||||
3. Render it (or not).
|
||||
|
||||
### Sample
|
||||
|
||||
```jsx
|
||||
import { TourKit } from '@woocommerce/components';
|
||||
|
||||
function Tour() {
|
||||
// 1. Define the criteria for showing a tour:
|
||||
const [ showTour, setShowTour ] = useState( true );
|
||||
|
||||
// 2. Define a configuration for the tour, passing along a handler for closing.
|
||||
const config = {
|
||||
steps: [
|
||||
{
|
||||
referenceElements: {
|
||||
desktop: '.render-step-near-me',
|
||||
},
|
||||
meta: {
|
||||
heading: 'Lorem ipsum dolor sit amet.',
|
||||
descriptions: {
|
||||
desktop: 'Lorem ipsum dolor sit amet.',
|
||||
},
|
||||
primaryButtonText: "Done"
|
||||
},
|
||||
},
|
||||
],
|
||||
closeHandler: () => setShowTour( false ),
|
||||
options: {},
|
||||
};
|
||||
|
||||
// 3. Render it (or not):
|
||||
|
||||
if ( ! showTour ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TourKit config={ config } />;
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
When a tour is rendered and focused, the following functionality exists:
|
||||
|
||||
- Close the tour on `ESC` key (in minimized view)
|
||||
- Go to previous/next step on `left/right` arrow keys (in step view)
|
||||
|
||||
## Configuration
|
||||
|
||||
The main API for configuring a tour is the config object. See example usage and [types.ts](./types.ts) for the full definition.
|
||||
|
||||
`config.steps`: An array of objects that define the content we wish to render on the page. Each step defined by:
|
||||
|
||||
- `referenceElements` (optional): A set of `desktop` & `mobile` selectors to render the step near.
|
||||
- `focusElement` (optional): A set of `desktop` & `mobile` & `iframe` selectors to automatically focus.
|
||||
- `meta`: Arbitrary object that encloses the content we want to render for each step.
|
||||
- `classNames` (optional): An array or CSV of CSS classes applied to a step.
|
||||
|
||||
`config.closeHandler`: The callback responsible for closing the tour.
|
||||
|
||||
- `tourStep`: A React component that will be called to render each step. Receives the following properties:
|
||||
|
||||
- `steps`: The steps defined for the tour.
|
||||
- `currentStepIndex`
|
||||
- `onDismiss`: Handler that dismissed/closes the tour.
|
||||
- `onNext`: Handler that progresses the tour to the next step.
|
||||
- `onPrevious`: Handler that takes the tour to the previous step.
|
||||
- `onMinimize`: Handler that minimizes the tour (passes rendering to `tourMinimized`).
|
||||
- `setInitialFocusedElement`: A dispatcher that assigns an element to be initially focused when a step renders (see examples).
|
||||
- `onGoToStep`: Handler that progresses the tour to a given step index.
|
||||
|
||||
- `tourMinimized`: A React component that will be called to render a minimized view for the tour. Receives the following properties:
|
||||
- `steps`: The steps defined for the tour.
|
||||
- `currentStepIndex`
|
||||
- `onDismiss`: Handler that dismissed/closes the tour.
|
||||
- `onMaximize`: Handler that expands the tour (passes rendering to `tourStep`).
|
||||
|
||||
`config.options` (optional):
|
||||
|
||||
- `classNames` (optional): An array or CSV of CSS classes to enclose the main tour frame with.
|
||||
|
||||
- `effects`: An object to enable/disable/combine various tour effects:
|
||||
|
||||
- `spotlight`: Adds a semi-transparent overlay and highlights the reference element when provided with a transparent box over it. Expects an object with optional styles to override the default highlight/spotlight behavior when provided (default: spotlight wraps the entire reference element).
|
||||
- `interactivity`: An object that configures whether the user is allowed to interact with the referenced element during the tour
|
||||
- `styles`: CSS properties that configures the styles applied to the spotlight overlay
|
||||
- `arrowIndicator`: Adds an arrow tip pointing at the reference element when provided.
|
||||
- `overlay`: Includes the semi-transparent overlay for all the steps (also blocks interactions with the rest of the page)
|
||||
- `autoScroll`: The page scrolls up and down automatically such that the step target element is visible to the user.
|
||||
|
||||
- `callbacks`: An object of callbacks to handle side effects from various interactions (see [types.ts](./src/types.ts)).
|
||||
|
||||
- `popperModifiers`: The tour uses Popper to position steps near reference elements (and for other effects). An implementation can pass its own modifiers to tailor the functionality further e.g. more offset or padding from the reference element.
|
||||
- `tourRating` (optional - only in WPCOM Tour Kit variant):
|
||||
- `enabled`: Whether to show rating in last step.
|
||||
- `useTourRating`: (optional) A hook to provide the rating from an external source/state (see [types.ts](./src/types.ts)).
|
||||
- `onTourRate`: (optional) A callback to fire off when a rating is submitted.
|
||||
|
||||
- `portalElementId`: A string that lets you customize under which DOM element the Tour will be appended.
|
||||
|
||||
`placement` (Optional) : Describes the preferred placement of the popper. Possible values are left-start, left, left-end, top-start, top, top-end, right-start, right, right-end, bottom-start, bottom, and bottom-end.
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button, Flex, Icon } from '@wordpress/components';
|
||||
import { closeSmall } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { TourStepRendererProps } from '@automattic/tour-kit';
|
||||
|
||||
interface Props {
|
||||
onDismiss: TourStepRendererProps[ 'onDismiss' ];
|
||||
}
|
||||
|
||||
const StepControls: React.FunctionComponent< Props > = ( { onDismiss } ) => {
|
||||
return (
|
||||
<Flex className="woocommerce-tour-kit-step-controls" justify="flex-end">
|
||||
<Button
|
||||
className="woocommerce-tour-kit-step-controls__close-btn"
|
||||
label={ __( 'Close Tour', 'woocommerce' ) }
|
||||
icon={ <Icon icon={ closeSmall } viewBox="6 4 12 14" /> }
|
||||
iconSize={ 16 }
|
||||
size={ 16 }
|
||||
onClick={ onDismiss( 'close-btn' ) }
|
||||
></Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepControls;
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button } from '@wordpress/components';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { WooTourStepRendererProps } from '../types';
|
||||
|
||||
type Props = Omit<
|
||||
WooTourStepRendererProps,
|
||||
'onMinimize' | 'setInitialFocusedElement'
|
||||
>;
|
||||
|
||||
const StepNavigation: React.FunctionComponent< Props > = ( {
|
||||
currentStepIndex,
|
||||
onNextStep,
|
||||
onPreviousStep,
|
||||
onDismiss,
|
||||
steps,
|
||||
} ) => {
|
||||
const isFirstStep = currentStepIndex === 0;
|
||||
const isLastStep = currentStepIndex === steps.length - 1;
|
||||
const { primaryButton = { text: '', isDisabled: false } } = steps[
|
||||
currentStepIndex
|
||||
].meta;
|
||||
|
||||
const NextButton = (
|
||||
<Button
|
||||
className="woocommerce-tour-kit-step-navigation__next-btn"
|
||||
isPrimary
|
||||
disabled={ primaryButton.isDisabled }
|
||||
onClick={ onNextStep }
|
||||
>
|
||||
{ primaryButton.text || __( 'Next', 'woocommerce' ) }
|
||||
</Button>
|
||||
);
|
||||
|
||||
const BackButton = (
|
||||
<Button
|
||||
className="woocommerce-tour-kit-step-navigation__back-btn"
|
||||
isSecondary
|
||||
onClick={ onPreviousStep }
|
||||
>
|
||||
{ __( 'Back', 'woocommerce' ) }
|
||||
</Button>
|
||||
);
|
||||
|
||||
const renderButtons = () => {
|
||||
if ( isFirstStep ) {
|
||||
return <div>{ NextButton }</div>;
|
||||
}
|
||||
|
||||
if ( isLastStep ) {
|
||||
return (
|
||||
<div>
|
||||
{ BackButton }
|
||||
<Button
|
||||
isPrimary
|
||||
disabled={ primaryButton.isDisabled }
|
||||
className="woocommerce-tour-kit-step-navigation__done-btn"
|
||||
onClick={ onDismiss( 'done-btn' ) }
|
||||
>
|
||||
{ primaryButton.text || __( 'Done', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ BackButton }
|
||||
{ NextButton }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="woocommerce-tour-kit-step-navigation">
|
||||
<div className="woocommerce-tour-kit-step-navigation__step">
|
||||
{ sprintf(
|
||||
/* translators: current progress in tour, eg: "Step 2 of 4" */
|
||||
__( 'Step %1$d of %2$d', 'woocommerce' ),
|
||||
currentStepIndex + 1,
|
||||
steps.length
|
||||
) }
|
||||
</div>
|
||||
{ renderButtons() }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepNavigation;
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withViewportMatch } from '@wordpress/viewport';
|
||||
import { Card, CardBody, CardFooter, CardHeader } from '@wordpress/components';
|
||||
import { createElement, useEffect } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import StepNavigation from './step-navigation';
|
||||
import StepControls from './step-controls';
|
||||
import type { WooTourStepRendererProps } from '../types';
|
||||
|
||||
const getFocusElement = (
|
||||
focusElementSelector: string | null,
|
||||
iframeSelector: string | null
|
||||
) => {
|
||||
if ( ! focusElementSelector ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( iframeSelector ) {
|
||||
const iframeElement = document.querySelector< HTMLIFrameElement >(
|
||||
iframeSelector
|
||||
);
|
||||
if ( ! iframeElement ) {
|
||||
return null;
|
||||
}
|
||||
const innerDoc =
|
||||
iframeElement.contentDocument ||
|
||||
( iframeElement.contentWindow &&
|
||||
iframeElement.contentWindow.document );
|
||||
|
||||
if ( ! innerDoc ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return innerDoc.querySelector< HTMLElement >( focusElementSelector );
|
||||
}
|
||||
|
||||
return document.querySelector< HTMLElement >( focusElementSelector );
|
||||
};
|
||||
|
||||
const Step: React.FunctionComponent<
|
||||
WooTourStepRendererProps & {
|
||||
isViewportMobile: boolean;
|
||||
}
|
||||
> = ( {
|
||||
steps,
|
||||
currentStepIndex,
|
||||
onDismiss,
|
||||
onNextStep,
|
||||
onPreviousStep,
|
||||
setInitialFocusedElement,
|
||||
onGoToStep,
|
||||
isViewportMobile,
|
||||
} ) => {
|
||||
const { descriptions, heading } = steps[ currentStepIndex ].meta;
|
||||
const description =
|
||||
descriptions[ isViewportMobile ? 'mobile' : 'desktop' ] ??
|
||||
descriptions.desktop;
|
||||
|
||||
const focusElementSelector =
|
||||
steps[ currentStepIndex ].focusElement?.[
|
||||
isViewportMobile ? 'mobile' : 'desktop'
|
||||
] || null;
|
||||
|
||||
const iframeSelector =
|
||||
steps[ currentStepIndex ].focusElement?.iframe || null;
|
||||
|
||||
const focusElement = getFocusElement(
|
||||
focusElementSelector,
|
||||
iframeSelector
|
||||
);
|
||||
|
||||
/*
|
||||
* Focus the element when step renders.
|
||||
*/
|
||||
useEffect( () => {
|
||||
if ( focusElement ) {
|
||||
setInitialFocusedElement( focusElement );
|
||||
}
|
||||
}, [ focusElement, setInitialFocusedElement ] );
|
||||
|
||||
return (
|
||||
<Card className="woocommerce-tour-kit-step" isElevated>
|
||||
<CardHeader isBorderless={ true } size="small">
|
||||
<StepControls onDismiss={ onDismiss } />
|
||||
</CardHeader>
|
||||
<CardBody className="woocommerce-tour-kit-step__body" size="small">
|
||||
<h2 className="woocommerce-tour-kit-step__heading">
|
||||
{ heading }
|
||||
</h2>
|
||||
<p className="woocommerce-tour-kit-step__description">
|
||||
{ description }
|
||||
</p>
|
||||
</CardBody>
|
||||
<CardFooter isBorderless={ true } size="small">
|
||||
<StepNavigation
|
||||
currentStepIndex={ currentStepIndex }
|
||||
onGoToStep={ onGoToStep }
|
||||
onNextStep={ onNextStep }
|
||||
onPreviousStep={ onPreviousStep }
|
||||
onDismiss={ onDismiss }
|
||||
steps={ steps }
|
||||
></StepNavigation>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default withViewportMatch( {
|
||||
isViewportMobile: '< medium',
|
||||
} )( Step );
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
import { createElement } from '@wordpress/element';
|
||||
import TourKit, { TourStepRenderer } from '@automattic/tour-kit';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TourKitStep from './components/step';
|
||||
import type { WooConfig } from './types';
|
||||
|
||||
interface Props {
|
||||
config: WooConfig;
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
effects: { spotlight: {}, arrowIndicator: true },
|
||||
};
|
||||
|
||||
const WooTourKit: React.FunctionComponent< Props > = ( { config } ) => {
|
||||
return (
|
||||
<TourKit
|
||||
__temp__className={ 'woocommerce-tour-kit' }
|
||||
config={ {
|
||||
options: {
|
||||
...defaultOptions,
|
||||
...config.options,
|
||||
},
|
||||
...config,
|
||||
renderers: {
|
||||
tourStep: TourKitStep as TourStepRenderer,
|
||||
// Disable minimize feature for woo tour kit.
|
||||
tourMinimized: () => null,
|
||||
},
|
||||
} }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default WooTourKit;
|
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import '../style.scss';
|
||||
|
||||
import WooTourKit from '..';
|
||||
import type { WooConfig, WooOptions } from '../types';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Admin/components/TourKit',
|
||||
component: WooTourKit,
|
||||
};
|
||||
|
||||
const References = () => {
|
||||
return (
|
||||
<div className={ 'storybook__tourkit-references' }>
|
||||
<div className={ 'storybook__tourkit-references-container' }>
|
||||
<div className={ 'storybook__tourkit-references-a' }>
|
||||
<p>Reference A</p>
|
||||
</div>
|
||||
<div className={ 'storybook__tourkit-references-b' }>
|
||||
<p>Reference B</p>
|
||||
<div style={ { display: 'grid', placeItems: 'center' } }>
|
||||
<input
|
||||
style={ { margin: 'auto', display: 'block' } }
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ 'storybook__tourkit-references-c' }>
|
||||
<p>Reference C</p>
|
||||
</div>
|
||||
<div className={ 'storybook__tourkit-references-d' }>
|
||||
<p>Reference D</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Tour = ( {
|
||||
onClose,
|
||||
options,
|
||||
placement,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
options?: WooOptions;
|
||||
placement?: WooConfig[ 'placement' ];
|
||||
} ) => {
|
||||
const config: WooConfig = {
|
||||
placement,
|
||||
steps: [
|
||||
{
|
||||
referenceElements: {
|
||||
desktop: '.storybook__tourkit-references-a',
|
||||
mobile: '.storybook__tourkit-references-a',
|
||||
},
|
||||
meta: {
|
||||
heading: 'Change content',
|
||||
descriptions: {
|
||||
desktop:
|
||||
'You can change the content and add any relevant links.',
|
||||
mobile:
|
||||
'You can change the content and add any relevant links.',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
referenceElements: {
|
||||
desktop: '.storybook__tourkit-references-b',
|
||||
mobile: '.storybook__tourkit-references-b',
|
||||
},
|
||||
focusElement: {
|
||||
desktop: '.storybook__tourkit-references-b input',
|
||||
},
|
||||
meta: {
|
||||
heading: 'Shipping zones',
|
||||
descriptions: {
|
||||
desktop:
|
||||
'We added a few shipping zones for you based on your location, but you can manage them at any time.',
|
||||
mobile:
|
||||
'A shipping zone is a geographic area where a certain set of shipping methods are offered.',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
referenceElements: {
|
||||
desktop: '.storybook__tourkit-references-c',
|
||||
mobile: '.storybook__tourkit-references-c',
|
||||
},
|
||||
meta: {
|
||||
heading: 'Shipping methods',
|
||||
descriptions: {
|
||||
desktop:
|
||||
'We defaulted to some recommended shipping methods based on your store location, but you can manage them at any time within each shipping zone settings. ',
|
||||
mobile:
|
||||
'We defaulted to some recommended shipping methods based on your store location, but you can manage them at any time within each shipping zone settings. ',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
referenceElements: {
|
||||
desktop: '.storybook__tourkit-references-d',
|
||||
mobile: '.storybook__tourkit-references-d',
|
||||
},
|
||||
meta: {
|
||||
heading: 'Laura 4',
|
||||
descriptions: {
|
||||
desktop: 'Lorem ipsum dolor sit amet.',
|
||||
mobile: 'Lorem ipsum dolor sit amet.',
|
||||
},
|
||||
primaryButton: {
|
||||
isDisabled: true,
|
||||
text: 'Keep editing',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
closeHandler: onClose,
|
||||
options: {
|
||||
classNames: [ 'mytour' ],
|
||||
...options,
|
||||
},
|
||||
};
|
||||
|
||||
return <WooTourKit config={ config } />;
|
||||
};
|
||||
|
||||
const StoryTour = ( {
|
||||
options = {},
|
||||
placement,
|
||||
}: {
|
||||
options?: WooConfig[ 'options' ];
|
||||
placement?: WooConfig[ 'placement' ];
|
||||
} ) => {
|
||||
const [ showTour, setShowTour ] = useState( false );
|
||||
|
||||
return (
|
||||
<div className="storybook__tourkit">
|
||||
<References />
|
||||
{ ! showTour && (
|
||||
<button onClick={ () => setShowTour( true ) }>
|
||||
Start Tour
|
||||
</button>
|
||||
) }
|
||||
{ showTour && (
|
||||
<Tour
|
||||
placement={ placement }
|
||||
onClose={ () => setShowTour( false ) }
|
||||
options={ options }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const NoEffects = () => (
|
||||
<StoryTour
|
||||
options={ {
|
||||
effects: {},
|
||||
} }
|
||||
/>
|
||||
);
|
||||
export const Spotlight = () => (
|
||||
<StoryTour
|
||||
options={ {
|
||||
effects: { arrowIndicator: true, spotlight: {} },
|
||||
} }
|
||||
/>
|
||||
);
|
||||
export const Overlay = () => (
|
||||
<StoryTour
|
||||
options={ {
|
||||
effects: { arrowIndicator: true, overlay: true },
|
||||
} }
|
||||
/>
|
||||
);
|
||||
export const SpotlightInteractivity = () => (
|
||||
<StoryTour
|
||||
options={ {
|
||||
effects: {
|
||||
spotlight: {
|
||||
interactivity: {
|
||||
rootElementSelector: '#root',
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} }
|
||||
/>
|
||||
);
|
||||
export const AutoScroll = () => (
|
||||
<>
|
||||
<div style={ { height: '10vh' } }></div>
|
||||
<StoryTour
|
||||
options={ {
|
||||
effects: {
|
||||
autoScroll: {
|
||||
behavior: 'smooth',
|
||||
},
|
||||
},
|
||||
} }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
export const Placement = () => <StoryTour placement={ 'left' } />;
|
|
@ -0,0 +1,73 @@
|
|||
.storybook__tourkit {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.storybook__tourkit-step,
|
||||
.storybook__tourkit-minimized {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.storybook__tourkit-step {
|
||||
min-height: 150px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.storybook__tourkit-step-controls,
|
||||
.storybook__tourkit-minimized-controls {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.storybook__tourkit-references {
|
||||
height: 400px;
|
||||
margin-bottom: 30px;
|
||||
overflow: auto;
|
||||
resize: auto;
|
||||
|
||||
.storybook__tourkit-references-container {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr 1fr 1fr;
|
||||
gap: 0 0;
|
||||
grid-auto-flow: row;
|
||||
grid-template-areas:
|
||||
'Ref-A Ref-B Ref-B Ref-B Ref-D'
|
||||
'Ref-A Ref-B Ref-B Ref-B Ref-D'
|
||||
'Ref-A Ref-C Ref-C Ref-C Ref-D'
|
||||
'Ref-A Ref-C Ref-C Ref-C Ref-D';
|
||||
}
|
||||
|
||||
.storybook__tourkit-references-a,
|
||||
.storybook__tourkit-references-b,
|
||||
.storybook__tourkit-references-c,
|
||||
.storybook__tourkit-references-d {
|
||||
display: grid;
|
||||
> p {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.storybook__tourkit-references-a {
|
||||
background-color: silver;
|
||||
grid-area: Ref-A;
|
||||
}
|
||||
|
||||
.storybook__tourkit-references-b {
|
||||
background-color: orange;
|
||||
grid-area: Ref-B;
|
||||
}
|
||||
|
||||
.storybook__tourkit-references-c {
|
||||
background-color: palevioletred;
|
||||
grid-area: Ref-C;
|
||||
}
|
||||
|
||||
.storybook__tourkit-references-d {
|
||||
background-color: gainsboro;
|
||||
grid-area: Ref-D;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
.woocommerce-tour-kit{
|
||||
box-shadow: 0 0 8px rgba( 0, 0, 0, 0.15 );
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
|
||||
.woocommerce-tour-kit-step {
|
||||
width: 350px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.woocommerce-tour-kit-step__body {
|
||||
padding-top: 0;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.woocommerce-tour-kit-step__heading {
|
||||
color: $studio-gray-100;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
letter-spacing: -0.1px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.woocommerce-tour-kit-step__description {
|
||||
color: $studio-gray-80;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.24px;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
|
||||
.woocommerce-tour-kit-step-controls__close-btn {
|
||||
height: 16px;
|
||||
min-width: 16px;
|
||||
padding: 0;
|
||||
svg {
|
||||
fill: #1e1e1e;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-tour-kit-step-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
margin-left: $gap;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-tour-kit-step-navigation__step {
|
||||
color: $studio-gray-60;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tour-kit-frame__arrow, .tour-kit-frame__arrow::before {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TourKit from '..';
|
||||
|
||||
jest.mock( '@automattic/calypso-config' );
|
||||
|
||||
const config = {
|
||||
steps: [
|
||||
{
|
||||
referenceElements: {
|
||||
desktop: '.render-step-near-me',
|
||||
},
|
||||
meta: {
|
||||
heading: 'Step1',
|
||||
descriptions: {
|
||||
desktop: 'Description',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
referenceElements: {
|
||||
desktop: '.render-step-near-me',
|
||||
},
|
||||
meta: {
|
||||
heading: 'Step2',
|
||||
descriptions: {
|
||||
desktop: 'Description',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
closeHandler: () => jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
|
||||
describe( 'TourKit', () => {
|
||||
it( 'should render TourKit', () => {
|
||||
const { queryByText } = render( <TourKit config={ config } /> );
|
||||
expect( queryByText( 'Step1' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should go to next step and show a back button', async () => {
|
||||
const { queryByText, getByRole } = render(
|
||||
<TourKit config={ config } />
|
||||
);
|
||||
userEvent.click( getByRole( 'button', { name: 'Next' } ) );
|
||||
|
||||
await waitFor( () =>
|
||||
expect( queryByText( 'Step2' ) ).toBeInTheDocument()
|
||||
);
|
||||
expect( getByRole( 'button', { name: 'Back' } ) ).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
Step,
|
||||
Options,
|
||||
Config,
|
||||
TourStepRendererProps,
|
||||
} from '@automattic/tour-kit';
|
||||
|
||||
export interface WooStep extends Step {
|
||||
meta: {
|
||||
heading: string | null;
|
||||
descriptions: {
|
||||
desktop: string | React.ReactElement | null;
|
||||
mobile?: string | React.ReactElement | null;
|
||||
};
|
||||
primaryButton?: {
|
||||
/** Set a text for the button. Default to "Done" for the last step and "Next" for the other steps */
|
||||
text?: string;
|
||||
/** Disable the button or not. Default to False */
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
};
|
||||
/** Auto apply the focus state for the element. Default to null */
|
||||
focusElement?: {
|
||||
desktop?: string;
|
||||
mobile?: string;
|
||||
/** Iframe HTML selector. Default to null. If set, it will find the focus element in the iframe */
|
||||
iframe?: string;
|
||||
};
|
||||
}
|
||||
export type WooOptions = Options;
|
||||
|
||||
export interface WooConfig extends Omit< Config, 'renderers' | 'isMinimized' > {
|
||||
steps: WooStep[];
|
||||
options?: WooOptions;
|
||||
}
|
||||
|
||||
export interface WooTourStepRendererProps extends TourStepRendererProps {
|
||||
steps: WooStep[];
|
||||
}
|
|
@ -34,12 +34,26 @@ module.exports = {
|
|||
],
|
||||
},
|
||||
webpackImporter: true,
|
||||
additionalData:
|
||||
additionalData: ( content, loaderContext ) => {
|
||||
const { resourcePath } = loaderContext;
|
||||
if ( resourcePath.includes( '@automattic+' ) ) {
|
||||
/*
|
||||
* Skip adding additional data for @automattic/* packages to
|
||||
* fix "SassError: @use rules must be written before any other rules."
|
||||
* @automattic/* packages have included '@use "sass:math" and other necessary imports.
|
||||
*/
|
||||
return content;
|
||||
}
|
||||
|
||||
return (
|
||||
'@use "sass:math";' +
|
||||
'@import "_colors"; ' +
|
||||
'@import "_variables"; ' +
|
||||
'@import "_breakpoints"; ' +
|
||||
'@import "_mixins"; ',
|
||||
'@import "_mixins"; ' +
|
||||
content
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -138,6 +138,9 @@ const webpackConfig = {
|
|||
// Reduce bundle size by omitting Node crypto library.
|
||||
// See https://github.com/woocommerce/woocommerce-admin/pull/5768
|
||||
crypto: 'empty',
|
||||
// Ignore fs, path to skip resolve errors for @automattic/calypso-config
|
||||
fs: false,
|
||||
path: false,
|
||||
},
|
||||
extensions: [ '.json', '.js', '.jsx', '.ts', '.tsx' ],
|
||||
alias: {
|
||||
|
|
2548
pnpm-lock.yaml
2548
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -77,5 +77,11 @@ module.exports = ( storybookConfig ) => {
|
|||
} )
|
||||
);
|
||||
|
||||
storybookConfig.resolve.fallback = {
|
||||
...storybookConfig.resolve.fallback,
|
||||
// Ignore fs to fix resolve 'fs' error for @automattic/calypso-config
|
||||
fs: false,
|
||||
};
|
||||
|
||||
return storybookConfig;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue