Skip to content

Commit a7be94d

Browse files
FAMarfuatyclaude
andcommitted
refactor(social): keep the header help link out of @yoast/components
Adding to @yoast/components is discouraged, and it turns out nothing there needed to change. CollapsibleStateless already takes a Heading prop, and the stateful Collapsible spreads its own props after setting Heading, so a caller can pass its own heading. MetaboxCollapsible now does that: with a HeaderHelpLink it renders a heading holding the toggle button and the link as siblings, and all the styling lives alongside it in this package. This reverts the headerHelpLink prop and StyledHeaderRow from Collapsible, its export, and its tests. Those tests move to a MetaboxCollapsible suite, which also covers that the heading keeps its identity across renders: HeaderHelpLink is a component rather than an element so it can be memoized, because a heading that changed identity every render would remount the toggle button and drop focus. Also reverts the analysis report snapshot. Its styled-components class hashes only shifted because Collapsible gained a component; with that change gone the hashes are back to their previous values. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3667b0c commit a7be94d

8 files changed

Lines changed: 250 additions & 462 deletions

File tree

packages/analysis-report/tests/__snapshots__/SiteSEOReportTest.js.snap

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,78 +10,78 @@ exports[`The SiteSEOReport testing suite The SiteSEOReport should match the snap
1010
Test assessment
1111
</p>
1212
<div
13-
className="bDYRu progress"
13+
className="bjXoTs progress"
1414
>
1515
<span
16-
className="jgrznC progress__part"
16+
className="zSbSj progress__part"
1717
/>
1818
<span
19-
className="gIBJcL progress__part"
19+
className="jFdQTa progress__part"
2020
/>
2121
<span
22-
className="iUbGTg progress__part"
22+
className="jnzOIl progress__part"
2323
/>
2424
</div>
2525
<ul
26-
className="eMsIPs assessments"
26+
className="cVQvbD assessments"
2727
role="list"
2828
>
2929
<li
30-
className="jxpGHY assessments__item"
30+
className="eYuGur assessments__item"
3131
>
3232
<span
33-
className="kQnQax dmMXOp assessments__item-bullet"
33+
className="hNYQft jLZaQs assessments__item-bullet"
3434
/>
3535
<span
36-
className="kQnQax ijUdyb assessments__item-text"
36+
className="hNYQft kXgChN assessments__item-text"
3737
dangerouslySetInnerHTML={
3838
Object {
3939
"__html": "<div></div>",
4040
}
4141
}
4242
/>
4343
<span
44-
className="kQnQax ivSAPf assessments__item-score"
44+
className="hNYQft cFTcQJ assessments__item-score"
4545
>
4646
10
4747
</span>
4848
</li>
4949
<li
50-
className="jxpGHY assessments__item"
50+
className="eYuGur assessments__item"
5151
>
5252
<span
53-
className="kQnQax gTqHkA assessments__item-bullet"
53+
className="hNYQft eEkVPZ assessments__item-bullet"
5454
/>
5555
<span
56-
className="kQnQax ijUdyb assessments__item-text"
56+
className="hNYQft kXgChN assessments__item-text"
5757
dangerouslySetInnerHTML={
5858
Object {
5959
"__html": "<div></div>",
6060
}
6161
}
6262
/>
6363
<span
64-
className="kQnQax ivSAPf assessments__item-score"
64+
className="hNYQft cFTcQJ assessments__item-score"
6565
>
6666
15
6767
</span>
6868
</li>
6969
<li
70-
className="jxpGHY assessments__item"
70+
className="eYuGur assessments__item"
7171
>
7272
<span
73-
className="kQnQax bsFrdz assessments__item-bullet"
73+
className="hNYQft kWkurS assessments__item-bullet"
7474
/>
7575
<span
76-
className="kQnQax ijUdyb assessments__item-text"
76+
className="hNYQft kXgChN assessments__item-text"
7777
dangerouslySetInnerHTML={
7878
Object {
7979
"__html": "<div></div>",
8080
}
8181
}
8282
/>
8383
<span
84-
className="kQnQax ivSAPf assessments__item-score"
84+
className="hNYQft cFTcQJ assessments__item-score"
8585
>
8686
20
8787
</span>

packages/components/src/Collapsible.js

Lines changed: 17 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getDirectionalStyle } from "@yoast/helpers";
2-
import { colors, rgba } from "@yoast/style-guide";
2+
import { colors } from "@yoast/style-guide";
33
import { omit } from "lodash";
44
import PropTypes from "prop-types";
55
import React from "react";
@@ -52,79 +52,6 @@ export const StyledIconsButton = styled( IconsButton )`
5252
}
5353
`;
5454

55-
/**
56-
* Header row that lets a help link sit next to the title, before the suffix icon.
57-
*
58-
* The link has to be a sibling of the toggle button: a link inside a button is invalid HTML, and a
59-
* click on it would open the link and toggle the panel at the same time. To make room for it the
60-
* button shrinks to its title, which costs the two behaviours the full-width button provided. Both
61-
* are restored here without a click handler on this row:
62-
*
63-
* - The button's `::after` covers the row, so clicks anywhere on it still reach the button. The help
64-
* link is lifted above that overlay so it stays clickable.
65-
* - The suffix icon is pinned to this row's edge while remaining a child of the button, which keeps
66-
* it inside the toggle's hit area and keeps integrations working that look the icon up and click
67-
* its parent element to open a collapsible.
68-
*
69-
* The hover background belongs to the collapsible that renders this row, because the button no
70-
* longer spans the row and each collapsible picks its own colour.
71-
*/
72-
export const StyledHeaderRow = styled.div`
73-
position: relative;
74-
display: flex;
75-
align-items: center;
76-
// Reserve the suffix icon's lane so the title and the help link shrink before reaching it.
77-
padding-inline-end: 48px;
78-
79-
> h2 {
80-
flex: 0 1 auto;
81-
min-width: 0;
82-
}
83-
84-
// Anything that is not the heading, so in practice the help link.
85-
> :not(h2) {
86-
position: relative;
87-
z-index: 1;
88-
flex: 0 0 auto;
89-
}
90-
91-
${ StyledIconsButton } {
92-
width: auto;
93-
background-color: transparent;
94-
// The help link brings its own leading gap, so the button ends at its title.
95-
padding-inline-end: 0;
96-
97-
&::after {
98-
content: "";
99-
position: absolute;
100-
inset: 0;
101-
}
102-
103-
/*
104-
* The button only wraps its title now, so its own focus ring would cover part of the header
105-
* instead of all of it. The overlay still spans the row, so it carries the ring instead.
106-
*/
107-
&:focus {
108-
outline: none;
109-
box-shadow: none;
110-
}
111-
112-
&:focus::after {
113-
outline: 1px solid ${ colors.$color_blue };
114-
outline-offset: -1px;
115-
box-shadow: 0 0 3px ${ rgba( colors.$color_blue_dark, 0.8 ) };
116-
}
117-
118-
> svg:last-child {
119-
position: absolute;
120-
inset-inline-end: 16px;
121-
top: 50%;
122-
transform: translateY(-50%);
123-
margin: 0;
124-
}
125-
}
126-
`;
127-
12855
/**
12956
* Wraps a component in a heading element with a defined heading level.
13057
*
@@ -183,10 +110,6 @@ const StyledHeading = wrapInHeading( StyledIconsButton, { level: 2, fontSize: "1
183110
* @param {string} props.id The id for the Heading button.
184111
* @param {function} props.renderNewBadgeLabel Function to render a "New" badge label.
185112
* @param {boolean} props.hasNewBadgeLabel Whether to show a "New" badge label.
186-
* @param {ReactElement} props.headerHelpLink A link rendered next to the title, before the suffix icon. It is a
187-
* sibling of the toggle button, never a child of it. Passing it wraps
188-
* the heading in StyledHeaderRow, which changes the header layout; when
189-
* it is absent the header markup is unchanged.
190113
*
191114
* @returns {ReactElement} A collapsible panel.
192115
*/
@@ -209,7 +132,6 @@ export function CollapsibleStateless( props ) {
209132
titleScreenReaderText,
210133
renderNewBadgeLabel,
211134
hasNewBadgeLabel,
212-
headerHelpLink,
213135
} = props;
214136

215137
let wrappedChildren = children;
@@ -218,33 +140,27 @@ export function CollapsibleStateless( props ) {
218140
}
219141
const Container = ( hasSeparator ) ? StyledContainerTopLevel : StyledContainer;
220142

221-
const heading = (
222-
<Heading
223-
id={ id }
224-
aria-expanded={ isOpen }
225-
onClick={ onToggle }
226-
prefixIcon={ isOpen ? prefixIcon : prefixIconCollapsed }
227-
suffixIcon={ isOpen ? suffixIcon : suffixIconCollapsed }
228-
hasSubTitle={ !! subTitle }
229-
>
230-
<SectionTitle
231-
title={ title }
232-
titleScreenReaderText={ titleScreenReaderText }
233-
subTitle={ subTitle }
234-
renderNewBadgeLabel={ renderNewBadgeLabel }
235-
hasNewBadgeLabel={ hasNewBadgeLabel }
236-
/>
237-
</Heading>
238-
);
239-
240143
return (
241144
<Container
242145
// Pass the classname to allow re-styling with styled-components.
243146
className={ className }
244147
>
245-
{ headerHelpLink
246-
? <StyledHeaderRow>{ heading }{ headerHelpLink }</StyledHeaderRow>
247-
: heading }
148+
<Heading
149+
id={ id }
150+
aria-expanded={ isOpen }
151+
onClick={ onToggle }
152+
prefixIcon={ isOpen ? prefixIcon : prefixIconCollapsed }
153+
suffixIcon={ isOpen ? suffixIcon : suffixIconCollapsed }
154+
hasSubTitle={ !! subTitle }
155+
>
156+
<SectionTitle
157+
title={ title }
158+
titleScreenReaderText={ titleScreenReaderText }
159+
subTitle={ subTitle }
160+
renderNewBadgeLabel={ renderNewBadgeLabel }
161+
hasNewBadgeLabel={ hasNewBadgeLabel }
162+
/>
163+
</Heading>
248164
{ wrappedChildren }
249165
</Container>
250166
);
@@ -287,7 +203,6 @@ CollapsibleStateless.propTypes = {
287203
id: PropTypes.string,
288204
renderNewBadgeLabel: PropTypes.func,
289205
hasNewBadgeLabel: PropTypes.bool,
290-
headerHelpLink: PropTypes.element,
291206
};
292207

293208
CollapsibleStateless.defaultProps = {
@@ -305,7 +220,6 @@ CollapsibleStateless.defaultProps = {
305220
suffixIconCollapsed: null,
306221
renderNewBadgeLabel: () => {},
307222
hasNewBadgeLabel: false,
308-
headerHelpLink: null,
309223
};
310224

311225
/**
@@ -325,7 +239,6 @@ export class Collapsible extends React.Component {
325239
* @param {Object} props.suffixIconCollapsed Suffix icon when in collapsed state.
326240
* @param {string} props.title Title for in the Heading.
327241
* @param {string} props.titleScreenReaderText Chance for an extra text to feed to a screenreader.
328-
* @param {ReactElement} props.headerHelpLink A link rendered next to the title, before the suffix icon.
329242
*
330243
* @returns {ReactElement} Base collapsible panel.
331244
*/
@@ -452,7 +365,6 @@ Collapsible.propTypes = {
452365
onToggle: PropTypes.func,
453366
renderNewBadgeLabel: PropTypes.func,
454367
hasNewBadgeLabel: PropTypes.bool,
455-
headerHelpLink: PropTypes.element,
456368
};
457369

458370
Collapsible.defaultProps = {
@@ -484,7 +396,6 @@ Collapsible.defaultProps = {
484396
onToggle: null,
485397
renderNewBadgeLabel: () => {},
486398
hasNewBadgeLabel: false,
487-
headerHelpLink: null,
488399
};
489400

490401
export default Collapsible;

packages/components/src/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ export {
4747
StyledIconsButton,
4848
StyledContainer,
4949
StyledContainerTopLevel,
50-
StyledHeaderRow,
5150
wrapInHeading,
5251
} from "./Collapsible";
5352

packages/components/tests/CollapsibleTest.js

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -71,49 +71,6 @@ describe( "Collapsible", () => {
7171
component.getInstance().toggleCollapse();
7272
expect( component.toJSON() ).toMatchSnapshot();
7373
} );
74-
75-
it( "renders a header help link outside the toggle button", () => {
76-
const component = renderer.create(
77-
<Collapsible
78-
title="Lorem ipsum dolor sit amet"
79-
initialIsOpen={ true }
80-
headerHelpLink={ <a className="header-help-link" href="https://example.com/help">Help</a> }
81-
>
82-
{ content }
83-
</Collapsible>
84-
);
85-
86-
const button = component.root.findByType( "button" );
87-
88-
/*
89-
* A link inside the button would be invalid HTML, and a click on it would both open the link
90-
* and toggle the panel. The link has to be a sibling of the button instead.
91-
*/
92-
expect( button.findAllByType( "a" ) ).toHaveLength( 0 );
93-
expect( component.root.findAllByType( "a" ) ).toHaveLength( 1 );
94-
95-
expect( component.toJSON() ).toMatchSnapshot();
96-
} );
97-
98-
it( "keeps the suffix icon inside the toggle button when a header help link is given", () => {
99-
const component = renderer.create(
100-
<Collapsible
101-
title="Lorem ipsum dolor sit amet"
102-
initialIsOpen={ true }
103-
headerHelpLink={ <a className="header-help-link" href="https://example.com/help">Help</a> }
104-
>
105-
{ content }
106-
</Collapsible>
107-
);
108-
109-
/*
110-
* The suffix icon is positioned at the edge of the header row with CSS, but stays a child of
111-
* the button: that keeps it inside the toggle's hit area, and integrations look the icon up
112-
* and click its parent element to open a collapsible.
113-
*/
114-
const button = component.root.findByType( "button" );
115-
expect( button.findAllByType( "svg" ).length ).toBeGreaterThan( 0 );
116-
} );
11774
} );
11875

11976
describe( "CollapsibleStateless", () => {

0 commit comments

Comments
 (0)