Skip to content

Commit

Permalink
Guide: refactor to TypeScript (#47493)
Browse files Browse the repository at this point in the history
* `Guide`: refactor to TypeScript

* fix remaining type errors & remove unnecessary

* be specific about `onFinish` type

* align prop requirements with readme

* add type descriptions

* types: improve deprecation notice for `children`

Co-authored-by: Lena Morita <[email protected]>

* types/page: declare as non-polymorphic

Co-authored-by: Lena Morita <[email protected]>

* guide: re-arrange default export

* guide: add jsdoc desc + example

* add changelog entry

* types: add `@default` values

* README: sync props with types.ts

* move default for `finishButtonText` to param decl

* move changelog entry

* sync `pages` prop description

* move CHANGELOG entry to unreleased section

---------

Co-authored-by: Lena Morita <[email protected]>
Co-authored-by: Marco Ciampini <[email protected]>
  • Loading branch information
3 people authored Mar 2, 2023
1 parent 438bbc6 commit d67b3b3
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 35 deletions.
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Internal

- `Guide`: Convert to TypeScript ([#47493](https://github.com/WordPress/gutenberg/pull/47493)).

## 23.5.0 (2023-03-01)

### Enhancements
Expand Down
32 changes: 17 additions & 15 deletions packages/components/src/guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,6 @@ function MyTutorial() {

The component accepts the following props:

### onFinish

A function which is called when the guide is finished. The guide is finished when the modal is closed or when the user clicks _Finish_ on the last page of the guide.

- Type: `function`
- Required: Yes

### pages

A list of objects describing each page in the guide. Each object **must** contain a `'content'` property and may optionally contain a `'image'` property.

- Type: `array`
- Required: Yes

### className

A custom class to add to the modal.
Expand All @@ -62,7 +48,7 @@ A custom class to add to the modal.

This property is used as the modal's accessibility label. It is required for accessibility reasons.

- Type: `String`
- Type: `string`
- Required: Yes

### finishButtonText
Expand All @@ -71,3 +57,19 @@ Use this to customize the label of the _Finish_ button shown at the end of the g

- Type: `string`
- Required: No
- Default: `'Finish'`

### onFinish

A function which is called when the guide is finished. The guide is finished when the modal is closed or when the user clicks _Finish_ on the last page of the guide.

- Type: `( event?: KeyboardEvent< HTMLDivElement > | SyntheticEvent ) => void`
- Required: Yes

### pages

A list of objects describing each page in the guide. Each object **must** contain a `'content'` property and may optionally contain a `'image'` property.

- Type: `{ content: ReactNode; image?: ReactNode; }[]`
- Required: No
- Default: `[]`
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { SVG, Circle } from '@wordpress/primitives';

export const PageControlIcon = ( { isSelected } ) => (
export const PageControlIcon = ( { isSelected }: { isSelected: boolean } ) => (
<SVG width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg">
<Circle
cx="4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,49 @@ import { focus } from '@wordpress/dom';
import Modal from '../modal';
import Button from '../button';
import PageControl from './page-control';
import type { GuideProps } from './types';

export default function Guide( {
/**
* `Guide` is a React component that renders a _user guide_ in a modal. The guide consists of several pages which the user can step through one by one. The guide is finished when the modal is closed or when the user clicks _Finish_ on the last page of the guide.
*
* ```jsx
* function MyTutorial() {
* const [ isOpen, setIsOpen ] = useState( true );
*
* if ( ! isOpen ) {
* return null;
* }
*
* return (
* <Guide
* onFinish={ () => setIsOpen( false ) }
* pages={ [
* {
* content: <p>Welcome to the ACME Store!</p>,
* },
* {
* image: <img src="https://acmestore.com/add-to-cart.png" />,
* content: (
* <p>
* Click <i>Add to Cart</i> to buy a product.
* </p>
* ),
* },
* ] }
* />
* );
* }
* ```
*/
function Guide( {
children,
className,
contentLabel,
finishButtonText,
finishButtonText = __( 'Finish' ),
onFinish,
pages = [],
} ) {
const guideContainer = useRef();
}: GuideProps ) {
const guideContainer = useRef< HTMLDivElement >( null );
const [ currentPage, setCurrentPage ] = useState( 0 );

useEffect( () => {
Expand All @@ -42,12 +75,17 @@ export default function Guide( {
// Each time we change the current page, start from the first element of the page.
// This also solves any focus loss that can happen.
if ( guideContainer.current ) {
focus.tabbable.find( guideContainer.current )?.[ 0 ]?.focus();
(
focus.tabbable.find( guideContainer.current ) as HTMLElement[]
)[ 0 ]?.focus();
}
}, [ currentPage ] );

if ( Children.count( children ) ) {
pages = Children.map( children, ( child ) => ( { content: child } ) );
pages =
Children.map( children, ( child ) => ( {
content: child,
} ) ) ?? [];
}

const canGoBack = currentPage > 0;
Expand Down Expand Up @@ -124,11 +162,13 @@ export default function Guide( {
className="components-guide__finish-button"
onClick={ onFinish }
>
{ finishButtonText || __( 'Finish' ) }
{ finishButtonText }
</Button>
) }
</div>
</div>
</Modal>
);
}

export default Guide;
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import { __, sprintf } from '@wordpress/i18n';
*/
import Button from '../button';
import { PageControlIcon } from './icons';
import type { PageControlProps } from './types';

export default function PageControl( {
currentPage,
numberOfPages,
setCurrentPage,
} ) {
}: PageControlProps ) {
return (
<ul
className="components-guide__page-control"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
import { useEffect } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';

export default function GuidePage( props ) {
/**
* Internal dependencies
*/
import type { WordPressComponentProps } from '../ui/context';

export default function GuidePage(
props: WordPressComponentProps< {}, 'div', false >
) {
useEffect( () => {
deprecated( '<GuidePage>', {
since: '5.5',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import type { ComponentMeta, ComponentStory } from '@storybook/react';

/**
* WordPress dependencies
*/
Expand All @@ -7,9 +12,9 @@ import { useState } from '@wordpress/element';
* Internal dependencies
*/
import Button from '../../button';
import Guide from '../';
import Guide from '..';

export default {
const meta: ComponentMeta< typeof Guide > = {
title: 'Components/Guide',
component: Guide,
argTypes: {
Expand All @@ -18,8 +23,9 @@ export default {
onFinish: { action: 'onFinish' },
},
};
export default meta;

const Template = ( { onFinish, ...props } ) => {
const Template: ComponentStory< typeof Guide > = ( { onFinish, ...props } ) => {
const [ isOpen, setOpen ] = useState( false );

const openGuide = () => setOpen( true );
Expand All @@ -34,8 +40,8 @@ const Template = ( { onFinish, ...props } ) => {
<Guide
{ ...props }
onFinish={ ( ...finishArgs ) => {
closeGuide( ...finishArgs );
onFinish( ...finishArgs );
closeGuide();
onFinish?.( ...finishArgs );
} }
/>
) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
import Guide from '../';
import Guide from '..';

const defaultProps = {
onFinish: () => {},
contentLabel: 'Arbitrary Content Label',
};

describe( 'Guide', () => {
it( 'renders nothing when there are no pages', () => {
render( <Guide pages={ [] } /> );
render( <Guide { ...defaultProps } pages={ [] } /> );
expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument();
} );

it( 'renders one page at a time', () => {
render(
<Guide
{ ...defaultProps }
pages={ [
{ content: <p>Page 1</p> },
{ content: <p>Page 2</p> },
Expand All @@ -33,6 +39,7 @@ describe( 'Guide', () => {
it( 'hides back button and shows forward button on the first page', () => {
render(
<Guide
{ ...defaultProps }
pages={ [
{ content: <p>Page 1</p> },
{ content: <p>Page 2</p> },
Expand All @@ -55,6 +62,7 @@ describe( 'Guide', () => {
const user = userEvent.setup();
render(
<Guide
{ ...defaultProps }
pages={ [
{ content: <p>Page 1</p> },
{ content: <p>Page 2</p> },
Expand All @@ -78,7 +86,12 @@ describe( 'Guide', () => {
} );

it( "doesn't display the page control if there is only one page", () => {
render( <Guide pages={ [ { content: <p>Page 1</p> } ] } /> );
render(
<Guide
{ ...defaultProps }
pages={ [ { content: <p>Page 1</p> } ] }
/>
);
expect(
screen.queryByRole( 'list', { name: 'Guide controls' } )
).not.toBeInTheDocument();
Expand All @@ -89,6 +102,7 @@ describe( 'Guide', () => {
const onFinish = jest.fn();
render(
<Guide
{ ...defaultProps }
onFinish={ onFinish }
pages={ [ { content: <p>Page 1</p> } ] }
/>
Expand All @@ -103,6 +117,7 @@ describe( 'Guide', () => {
const onFinish = jest.fn();
render(
<Guide
{ ...defaultProps }
onFinish={ onFinish }
pages={ [ { content: <p>Page 1</p> } ] }
/>
Expand All @@ -115,7 +130,7 @@ describe( 'Guide', () => {

describe( 'page navigation', () => {
it( 'renders an empty list when there are no pages', () => {
render( <Guide pages={ [] } /> );
render( <Guide { ...defaultProps } pages={ [] } /> );
expect(
screen.queryByRole( 'list', {
name: 'Guide controls',
Expand All @@ -131,6 +146,7 @@ describe( 'Guide', () => {
it( 'renders a button for each page', () => {
render(
<Guide
{ ...defaultProps }
pages={ [
{ content: <p>Page 1</p> },
{ content: <p>Page 2</p> },
Expand All @@ -154,6 +170,7 @@ describe( 'Guide', () => {

render(
<Guide
{ ...defaultProps }
pages={ [
{ content: <p>Page 1</p> },
{ content: <p>Page 2</p> },
Expand Down Expand Up @@ -196,6 +213,7 @@ describe( 'Guide', () => {

render(
<Guide
{ ...defaultProps }
pages={ [
{ content: <p>Page 1</p> },
{ content: <p>Page 2</p> },
Expand Down
Loading

1 comment on commit d67b3b3

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in d67b3b3.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4314657081
📝 Reported issues:

Please sign in to comment.