/
index.tsx
175 lines (160 loc) · 3.87 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/**
* External dependencies
*/
import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { useState, useEffect, Children, useRef } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import Modal from '../modal';
import Button from '../button';
import PageControl from './page-control';
import type { GuideProps } from './types';
/**
* `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 = __( 'Finish' ),
onFinish,
pages = [],
}: GuideProps ) {
const ref = useRef< HTMLDivElement >( null );
const [ currentPage, setCurrentPage ] = useState( 0 );
useEffect( () => {
// Place focus at the top of the guide on mount and when the page changes.
const frame = ref.current?.querySelector( '.components-guide' );
if ( frame instanceof HTMLElement ) {
frame.focus();
}
}, [ currentPage ] );
useEffect( () => {
if ( Children.count( children ) ) {
deprecated( 'Passing children to <Guide>', {
since: '5.5',
alternative: 'the `pages` prop',
} );
}
}, [ children ] );
if ( Children.count( children ) ) {
pages =
Children.map( children, ( child ) => ( {
content: child,
} ) ) ?? [];
}
const canGoBack = currentPage > 0;
const canGoForward = currentPage < pages.length - 1;
const goBack = () => {
if ( canGoBack ) {
setCurrentPage( currentPage - 1 );
}
};
const goForward = () => {
if ( canGoForward ) {
setCurrentPage( currentPage + 1 );
}
};
if ( pages.length === 0 ) {
return null;
}
return (
<Modal
className={ clsx( 'components-guide', className ) }
contentLabel={ contentLabel }
isDismissible={ pages.length > 1 }
onRequestClose={ onFinish }
onKeyDown={ ( event ) => {
if ( event.code === 'ArrowLeft' ) {
goBack();
// Do not scroll the modal's contents.
event.preventDefault();
} else if ( event.code === 'ArrowRight' ) {
goForward();
// Do not scroll the modal's contents.
event.preventDefault();
}
} }
ref={ ref }
>
<div className="components-guide__container">
<div className="components-guide__page">
{ pages[ currentPage ].image }
{ pages.length > 1 && (
<PageControl
currentPage={ currentPage }
numberOfPages={ pages.length }
setCurrentPage={ setCurrentPage }
/>
) }
{ pages[ currentPage ].content }
</div>
<div className="components-guide__footer">
{ canGoBack && (
<Button
className="components-guide__back-button"
variant="tertiary"
onClick={ goBack }
>
{ __( 'Previous' ) }
</Button>
) }
{ canGoForward && (
<Button
className="components-guide__forward-button"
variant="primary"
onClick={ goForward }
>
{ __( 'Next' ) }
</Button>
) }
{ ! canGoForward && (
<Button
className="components-guide__finish-button"
variant="primary"
onClick={ onFinish }
>
{ finishButtonText }
</Button>
) }
</div>
</div>
</Modal>
);
}
export default Guide;