Skip to content

Commit 5d8a720

Browse files
committed
Keep the modal layout stable during generation
1 parent 64e8032 commit 5d8a720

2 files changed

Lines changed: 126 additions & 72 deletions

File tree

src/experiments/content-resizing/components/ContentResizingToolbar.tsx

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@ export default function ContentResizingToolbar( {
147147
}, [] );
148148

149149
const handleRetry = useCallback( () => {
150-
if ( lastAction ) {
150+
if ( lastAction && ! isLoading ) {
151151
handleAction( lastAction );
152152
}
153-
}, [ handleAction, lastAction ] );
153+
}, [ handleAction, isLoading, lastAction ] );
154154

155155
// Calculate the word difference between the original and suggested content.
156156
const wordDiff = useMemo( () => {
@@ -248,69 +248,90 @@ export default function ContentResizingToolbar( {
248248
size="medium"
249249
className="ai-content-resizing-modal"
250250
>
251-
{ isLoading ? (
252-
<div className="ai-content-resizing-modal__loading">
253-
<Spinner />
254-
<p>{ __( 'Generating…', 'ai' ) }</p>
251+
<section
252+
className="ai-content-resizing-modal__panel"
253+
aria-label={ __( 'Original content', 'ai' ) }
254+
>
255+
<div className="ai-content-resizing-modal__label">
256+
<span>{ __( 'Original', 'ai' ) }</span>
255257
</div>
256-
) : (
257-
<>
258-
<section
259-
className="ai-content-resizing-modal__panel"
260-
aria-label={ __( 'Original content', 'ai' ) }
258+
<div
259+
className="ai-content-resizing-modal__text ai-content-resizing-modal__text--original"
260+
dangerouslySetInnerHTML={ {
261+
__html: blockContent,
262+
} }
263+
/>
264+
</section>
265+
<section
266+
className="ai-content-resizing-modal__panel"
267+
aria-label={ __( 'Suggested content', 'ai' ) }
268+
>
269+
<div className="ai-content-resizing-modal__label">
270+
<span>{ __( 'Suggested', 'ai' ) }</span>
271+
{ ! isLoading && wordDiff && (
272+
<span
273+
className={ `ai-content-resizing-modal__diff ai-content-resizing-modal__diff--${ wordDiff.modifier }` }
274+
aria-label={ wordDiff.ariaLabel }
275+
>
276+
{ wordDiff.label }
277+
</span>
278+
) }
279+
</div>
280+
{ isLoading ? (
281+
<div
282+
className="ai-content-resizing-modal__text ai-content-resizing-modal__loading"
283+
role="status"
284+
aria-live="polite"
261285
>
262-
<div className="ai-content-resizing-modal__label">
263-
<span>{ __( 'Original', 'ai' ) }</span>
286+
<div className="ai-content-resizing-modal__loading-status">
287+
<Spinner />
288+
<span>{ __( 'Generating…', 'ai' ) }</span>
264289
</div>
265290
<div
266-
className="ai-content-resizing-modal__text ai-content-resizing-modal__text--original"
267-
dangerouslySetInnerHTML={ {
268-
__html: blockContent,
269-
} }
270-
/>
271-
</section>
272-
<section
273-
className="ai-content-resizing-modal__panel"
274-
aria-label={ __( 'Suggested content', 'ai' ) }
275-
>
276-
<div className="ai-content-resizing-modal__label">
277-
<span>{ __( 'Suggested', 'ai' ) }</span>
278-
{ wordDiff && (
279-
<span
280-
className={ `ai-content-resizing-modal__diff ai-content-resizing-modal__diff--${ wordDiff.modifier }` }
281-
aria-label={ wordDiff.ariaLabel }
282-
>
283-
{ wordDiff.label }
284-
</span>
291+
className="ai-content-resizing-modal__loading-skeleton"
292+
aria-hidden="true"
293+
>
294+
{ Array.from( { length: 3 } ).map(
295+
( _, index ) => (
296+
<span
297+
key={ index }
298+
className="ai-content-resizing-modal__loading-skeleton-line"
299+
/>
300+
)
285301
) }
286302
</div>
287-
<div
288-
className="ai-content-resizing-modal__text"
289-
dangerouslySetInnerHTML={ {
290-
__html: suggestedContent ?? '',
291-
} }
292-
/>
293-
</section>
294-
<Flex
295-
justify="flex-start"
296-
gap={ 2 }
297-
className="ai-content-resizing-modal__actions"
298-
>
299-
<Button
300-
variant="primary"
301-
onClick={ handleAccept }
302-
>
303-
{ __( 'Accept', 'ai' ) }
304-
</Button>
305-
<Button
306-
variant="secondary"
307-
onClick={ handleRetry }
308-
>
309-
{ __( 'Regenerate', 'ai' ) }
310-
</Button>
311-
</Flex>
312-
</>
313-
) }
303+
</div>
304+
) : (
305+
<div
306+
className="ai-content-resizing-modal__text"
307+
dangerouslySetInnerHTML={ {
308+
__html: suggestedContent ?? '',
309+
} }
310+
/>
311+
) }
312+
</section>
313+
<Flex
314+
justify="flex-start"
315+
gap={ 2 }
316+
className="ai-content-resizing-modal__actions"
317+
>
318+
<Button
319+
variant="primary"
320+
onClick={ handleAccept }
321+
disabled={ isLoading || suggestedContent === null }
322+
>
323+
{ __( 'Accept', 'ai' ) }
324+
</Button>
325+
<Button
326+
variant="secondary"
327+
onClick={ handleRetry }
328+
disabled={ isLoading || lastAction === null }
329+
isBusy={ isLoading }
330+
accessibleWhenDisabled
331+
>
332+
{ __( 'Regenerate', 'ai' ) }
333+
</Button>
334+
</Flex>
314335
</Modal>
315336
) }
316337
</>

src/experiments/content-resizing/index.scss

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
1+
@keyframes ai-content-resizing-skeleton {
2+
0% { background-position: 100% 0; }
3+
100% { background-position: 0 0; }
4+
}
5+
16
.ai-content-resizing-toolbar--has-changes {
27
.components-button {
38
color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
49
}
510
}
611

7-
.ai-content-resizing-modal__loading {
8-
display: flex;
9-
flex-direction: column;
10-
align-items: center;
11-
gap: 12px;
12-
padding: 32px 0;
13-
14-
p {
15-
color: var(--wp-components-color-gray-700, #757575);
16-
margin: 0;
17-
}
18-
}
19-
2012
.ai-content-resizing-modal__panel {
2113
& + & {
2214
margin-top: 16px;
@@ -79,6 +71,47 @@
7971
}
8072
}
8173

74+
.ai-content-resizing-modal__loading {
75+
display: flex;
76+
flex-direction: column;
77+
gap: var(--wpds-dimension-gap-md, 12px);
78+
color: var(--wp-components-color-gray-700, #757575);
79+
80+
&-status {
81+
display: flex;
82+
align-items: center;
83+
gap: var(--wpds-dimension-gap-sm, 8px);
84+
85+
svg {
86+
margin: 0;
87+
}
88+
}
89+
90+
&-skeleton {
91+
display: flex;
92+
flex-direction: column;
93+
gap: var(--wpds-dimension-gap-sm, 8px);
94+
}
95+
96+
&-skeleton-line {
97+
display: block;
98+
width: 100%;
99+
height: var(--wpds-dimension-gap-md, 12px);
100+
border-radius: var(--wpds-border-radius-sm, 2px);
101+
background: linear-gradient(
102+
90deg,
103+
color-mix(in srgb, var(--wpds-color-fg-content-neutral-weak, #707070) 8%, transparent) 25%,
104+
color-mix(in srgb, var(--wpds-color-fg-content-neutral-weak, #707070) 18%, transparent) 37%,
105+
color-mix(in srgb, var(--wpds-color-fg-content-neutral-weak, #707070) 8%, transparent) 63%
106+
);
107+
background-size: 400% 100%;
108+
109+
@media (prefers-reduced-motion: no-preference) {
110+
animation: ai-content-resizing-skeleton 1.4s ease infinite;
111+
}
112+
}
113+
}
114+
82115
.ai-content-resizing-modal__actions {
83116
margin-top: 16px;
84117
}

0 commit comments

Comments
 (0)