• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 79498a351b2ddb942f3f0f8c97de97707acced3c

27 Feb 2024 03:31PM UTC coverage: 41.466% (-11.7%) from 53.156%
79498a351b2ddb942f3f0f8c97de97707acced3c

Pull #21121

github

web-flow
Merge 434fcab1a into d617035e7
Pull Request #21121: UI-Library / Align button heights with input fields

4568 of 10443 branches covered (43.74%)

Branch coverage included in aggregate %.

0 of 5 new or added lines in 3 files covered. (0.0%)

1 existing line in 1 file now uncovered.

12971 of 31854 relevant lines covered (40.72%)

70210.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/js/src/first-time-configuration/tailwind-components/stepper.js
1
import { Fragment, useCallback, useState, useEffect, useContext, createContext } from "@wordpress/element";
2
import { __, sprintf } from "@wordpress/i18n";
3
import { Button } from "@yoast/ui-library";
4
import AnimateHeight from "react-animate-height";
5
import PropTypes from "prop-types";
6
import { stepperTimings, stepperTimingClasses } from "../stepper-helper";
7
import StepHeader from "./step-header";
8
import { FadeInAlert } from "../tailwind-components/base/alert";
9

10
/* eslint-disable complexity */
11
const {
12
        slideDuration,
13
        delayBeforeOpening,
14
        delayBeforeFadingIn,
15
        delayBeforeClosing,
16
} = stepperTimings;
×
17

18
const { fadeDuration, delayUntilStepFaded, slideDuration: slideDurationClass } = stepperTimingClasses;
×
19

20
const StepperContext = createContext();
×
21

22
/**
23
 * A hook for getting the StepperContext value, with an informative error message.
24
 *
25
 * @returns {*} The value provided to the StepperContext provider.
26
 */
27
export function useStepperContext() {
28
        const context = useContext( StepperContext );
×
29
        if ( ! context ) {
×
30
          throw new Error(
×
31
                        "Stepper compound components cannot be rendered outside the Stepper component"
32
          );
33
        }
34
        return context;
×
35
}
36

37
/**
38
 * The component button used to navigate the steps
39
 *
40
 * @param {Object}     props             The props object.
41
 * @param {number}     props.children    The children of the component.
42
 * @param {function}   props.beforeGo    A function to call when the button is clicked.
43
 * @param {int|string} props.destination A number of steps to take relative to the current step or "first" or "last".
44
 *
45
 * @returns {WPElement} The button element.
46
 */
47
function GoButton( { beforeGo, children, destination, ...restProps } ) {
48
        const { stepIndex, setActiveStepIndex, lastStepIndex } = useStepperContext();
×
49
        const goToDestination = useCallback( () => {
×
50
                if ( typeof destination === "string" ) {
×
51
                        setActiveStepIndex( destination === "last" ? lastStepIndex : 0 );
×
52
                } else {
53
                        setActiveStepIndex( stepIndex + destination );
×
54
                }
55
        }, [ stepIndex, lastStepIndex, setActiveStepIndex, destination ] );
56

57
        const goFunction = useCallback( async() => {
×
58
                let canGo = true;
×
59
                if ( beforeGo ) {
×
60
                        canGo = false;
×
61
                        canGo = await beforeGo();
×
62
                }
63
                if ( canGo ) {
×
64
                        goToDestination();
×
65
                }
66
        }, [ goToDestination, beforeGo ] );
67

NEW
68
        return <Button
×
69
                onClick={ goFunction }
70
                { ...restProps }
71
        >
72
                { children }
73
        </Button>;
74
}
75

76
GoButton.propTypes = {
×
77
        beforeGo: PropTypes.func,
78
        children: PropTypes.node,
79
        destination: PropTypes.oneOfType( [
80
                PropTypes.number,
81
                PropTypes.oneOf( [ "first", "last" ] ),
82
        ] ),
83
};
84

85
GoButton.defaultProps = {
×
86
        beforeGo: null,
87
        children: <Fragment>{ __( "Continue", "wordpress-seo" ) }</Fragment>,
88
        destination: 1,
89
};
90

91
/**
92
 * The StepButtons component.
93
 *
94
 * @param {Object}   props The props object.
95
 *
96
 * @returns {WPElement} The EditButton component.
97
 */
98
function EditButton( { children, ...restProps } ) {
99
        const { stepIndex, setActiveStepIndex } = useStepperContext();
×
100

101
        const editFunction = useCallback( () => {
×
102
                setActiveStepIndex( stepIndex );
×
103
        }, [ setActiveStepIndex, stepIndex ] );
104

NEW
105
        return <Button
×
106
                onClick={ editFunction }
107
                variant="secondary"
108
                size="small"
109
                { ...restProps }
110
        >
111
                { children }
112
        </Button>;
113
}
114

115
EditButton.propTypes = {
×
116
        children: PropTypes.node,
117
};
118

119
EditButton.defaultProps = {
×
120
        children: <Fragment>{ __( "Edit", "wordpress-seo" ) }</Fragment>,
121
};
122

123
/**
124
 * The Step Element
125
 *
126
 * @param {Object} props            The props object.
127
 * @param {Node}   props.children   The children of the component.
128
 *
129
 * @returns {WPElement} The Step
130
 */
131
export function Step( { children } ) {
132
        const { lastStepIndex, stepIndex, activeStepIndex } = useStepperContext();
×
133
        return <Fragment>
×
134
                { /* Line. */ }
135
                { stepIndex !== lastStepIndex &&
×
136
                        <Fragment>
137
                                <div
138
                                        className={ "yst--ml-px yst-absolute yst-left-4 yst-w-0.5 yst-h-full yst-bg-slate-300 yst--bottom-6" }
139
                                        aria-hidden="true"
140
                                />
141
                                <div
142
                                        className={ `yst-h-12 yst-transition-transform ${ delayUntilStepFaded } yst-ease-linear ${ slideDurationClass } ${ stepIndex < activeStepIndex  ? "yst-scale-y-1" : "yst-scale-y-0" } yst-origin-top yst--ml-px yst-absolute yst-left-4 yst-w-0.5 yst-bg-primary-500 yst-top-8` }
×
143
                                        aria-hidden="true"
144
                                />
145
                        </Fragment>
146
                }
147
                { children }
148
        </Fragment>;
149
}
150

151
Step.propTypes = {
×
152
        children: PropTypes.node.isRequired,
153
};
154

155
/**
156
 * Provides a way to provide error messages at the Step level.
157
 * @param {Object} props The props.
158
 *
159
 * @returns {WPElement} The StepError component.
160
 */
161
export function StepError( { id, message, className } ) {
162
        return <FadeInAlert
×
163
                id={ id }
164
                type="error"
165
                isVisible={ !! message }
166
                className={ className }
167
        >
168
                {
169
                        sprintf(
170
                                /* translators: %1$s expands to the error message returned by the server */
171
                                __(
172
                                        "An error has occurred: %1$s",
173
                                        "wordpress-seo"
174
                                ),
175
                                message
176
                        )
177
                }
178
        </FadeInAlert>;
179
}
180

181
StepError.propTypes = {
×
182
        id: PropTypes.string.isRequired,
183
        message: PropTypes.string.isRequired,
184
        className: PropTypes.string,
185
};
186

187
StepError.defaultProps = {
×
188
        className: "",
189
};
190

191
/**
192
 * The (Tailwind) Step component
193
 *
194
 * @param {Object} props The props.
195
 *
196
 * @returns {WPElement} The Step component.
197
 */
198
function Content( { children } ) {
199
        const { activeStepIndex, stepIndex } = useStepperContext();
×
200
        const isActiveStep = activeStepIndex === stepIndex;
×
201

202
        const [ contentHeight, setContentHeight ] = useState( isActiveStep ? "auto" : 0 );
×
203
        const [ isFaded, setIsFaded ] = useState( ! isActiveStep );
×
204

205
        useEffect( () => {
×
206
                if ( isActiveStep ) {
×
207
                        setContentHeight( "auto" );
×
208
                        // Wait until all other animations are done.
209
                        setTimeout( () => setIsFaded( false ), delayBeforeFadingIn );
×
210
                } else {
211
                        setIsFaded( true );
×
212
                        setContentHeight( 0 );
×
213
                }
214
        }, [ isActiveStep ] );
215

216
        return (
×
217
                <Fragment>
218
                        { /* Child component and buttons. */ }
219
                        <AnimateHeight
220
                                id={ `content-${stepIndex}` }
221
                                delay={ contentHeight === 0 ? delayBeforeClosing : delayBeforeOpening }
×
222
                                height={ contentHeight }
223
                                easing="ease-in-out"
224
                                duration={ slideDuration }
225
                        >
226
                                <div className={ `yst-transition-opacity ${ fadeDuration } yst-relative yst-ml-12 yst-mt-4 yst-pb-1 ${ isFaded ? "yst-opacity-0 yst-pointer-events-none" : "yst-opacity-100" }` }>
×
227
                                        { children }
228
                                </div>
229
                        </AnimateHeight>
230
                </Fragment>
231
        );
232
}
233

234
Content.propTypes = {
×
235
        children: PropTypes.node.isRequired,
236
};
237

238
/**
239
 * The Tailwind Stepper component.
240
 *
241
 * @param {Object} props The props.
242
 *
243
 * @returns {WPElement} The Stepper component.
244
 */
245
export default function Stepper( { children, setActiveStepIndex, activeStepIndex, isStepperFinished } ) {
246
        return (
×
247
                <ol>
248
                        { children.map( ( child, stepIndex ) => {
249
                                return <li key={ `${ child.props.name }-${ stepIndex }` } className={ ( stepIndex === children.length - 1 ? "" : "yst-pb-8" ) + " yst-mb-0 yst-relative yst-max-w-none" }>
×
250
                                        <StepperContext.Provider
251
                                                value={ { stepIndex, activeStepIndex, setActiveStepIndex, lastStepIndex: children.length - 1, isStepperFinished } }
252
                                        >
253
                                                { child }
254
                                        </StepperContext.Provider>
255
                                </li>;
256
                        } ) }
257
                </ol>
258
        );
259
}
260

261
Stepper.propTypes = {
×
262
        setActiveStepIndex: PropTypes.func.isRequired,
263
        activeStepIndex: PropTypes.number.isRequired,
264
        isStepperFinished: PropTypes.bool,
265
        children: PropTypes.node.isRequired,
266
};
267

268
Stepper.defaultProps = {
×
269
        isStepperFinished: false,
270
};
271

272
Step.Content = Content;
×
273
Step.Error = StepError;
×
274
Step.Header = StepHeader;
×
275
Step.GoButton = GoButton;
×
276
Step.EditButton = EditButton;
×
277
/* eslint-enable complexity */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc