Add Content planner outline modal with loading state#23099
Add Content planner outline modal with loading state#23099leonidasmi merged 21 commits intofeature/content-plannerfrom
Conversation
… to use structured suggestion objects
…ntentOutlineModal for improved drag-and-drop functionality
…nd implement tests for modal functionality
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lity with aria attributes and upodate tests
…g state management
…date form field handling to support character length tracking
|
A merge conflict has been detected for the proposed code changes in this PR. Please resolve the conflict by either rebasing the PR or merging in changes from the base branch. |
73bbe9a to
474fa32
Compare
Pull Request Test Coverage Report for Build 63c01b14af4e07c7cc4914777476bf978e0be915Details
💛 - Coveralls |
…, loading state and accessibility
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Content Outline Modal view to the AI Content Planner flow and wires it into the existing FeatureModal transition stack, including a simulated loading state and initial unit tests.
Changes:
- Added
ContentOutlineModalcomponent with loading → loaded transitions, editable fields, and reorderable structure list. - Updated
FeatureModalandContentSuggestionsModalto support selecting a suggestion and transitioning to the outline view. - Added unit tests for the new outline modal and updated feature modal tests to support new dependencies.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
packages/js/src/ai-content-planner/components/feature-modal.js |
Orchestrates new outline panel and transitions; adds suggestion selection state. |
packages/js/src/ai-content-planner/components/content-suggestions-modal.js |
Adds onSuggestionClick wiring and passes full suggestion object on click. |
packages/js/src/ai-content-planner/components/content-outline-modal.js |
New outline modal UI (loading state, editable fields, structure reorder, footer actions). |
packages/js/tests/ai-content-planner/components/content-outline-modal.test.js |
New test suite covering outline modal rendering, loading/loaded states, a11y roles, and keyboard reorder. |
packages/js/tests/ai-content-planner/components/feature-modal.test.js |
Updates mocks to accommodate the new outline modal dependency chain. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const handleDrop = useCallback( ( e, dropIndex ) => { | ||
| e.preventDefault(); | ||
| const dragIndex = dragIndexRef.current; | ||
| if ( dragIndex === null || dragIndex === dropIndex ) { | ||
| setDragOverIndex( null ); |
There was a problem hiding this comment.
Drag-and-drop reordering logic is newly introduced but isn’t covered by unit tests, while keyboard reordering is. Add a test that simulates dragStart/dragOver/drop and asserts the structure order updates as expected to protect against regressions in the native DnD handlers.
…' attributes to form fields in ContentOutlineModal
leonidasmi
left a comment
There was a problem hiding this comment.
CR: 🚧 (I didn't pay too close attention to the drag&drop parts because it doesn't work for me, as per below)
I also saw a couple of acceptance test issues already:
- The transition from the suggestions modal to the outline modal is not very smooth, I'm adding a slowed-down screen recoding of it, to make that clear
- There's a weird double border in the input fields when you focus on them:
which comes from the **.yst-root textarea:focus** CSS rules (probably changing to using components from the UI library fixes this?)
*drag-and-drop doesn't work for me, when I do it via the mouse. it does work when I do it via the keyboard
…ntent-planner-outline-modal
…badge component; replace form fields with TextField and TextareaField components for improved consistency.
…ature modal to include SuggestionsPanel with transition handling for improved user experience.
…modal Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on is focused when loading content suggestions.
…ndling to trigger when the panel becomes active.
… Remove unnecessary state management in ContentSuggestionsModal and implement a utility function for determining panel visibility in FeatureModal.
leonidasmi
left a comment
There was a problem hiding this comment.
CR: ✅
Acceptance test: 🚧 I'm fine if we want to tackle those in a separate PR, to unblock things, but we have to make tasks for them if so:
-
I see in the design that toggling off the
Suggest categorytoggle, hides the category badge with a transition (I see the content below slowly moving up), but in the implementation it's an instant disappearing. -
about the step:
Verify the loading state shows, matching the design
I see a couple of disprepancies:
- The
Suggest categorytoggle is not disabled even though it is in the designs - The skeleton loader for the category badge has a border in the designs but not in the implementation:
- about the step:
In the outline modal footer, click the "Content suggestions" back button.
Verify the switch back to suggestions is also instant — the suggestions list appears immediately (already loaded, no loading state).
It's not the case for me, the first time I go back, I get the fade-in/out thing. The next time I'm trying to do so, it's instant.
…props and enhance focus management. Introduce skipTransitions prop for conditional rendering in ContentSuggestionsModal, improving loading state handling.
leonidasmi
left a comment
There was a problem hiding this comment.
Acceptance: ✅
I left a CR comment on the last commit. I'm merging the CR nevertheless to keep things unblocked, but I think we should come back to this when you're back @JorPV
| <div> | ||
| <Modal.Description className="yst-mb-4">{ __( "Select a suggestion to generate a structured outline for your post.", "wordpress-seo" ) }</Modal.Description> | ||
| { suggestions.map( ( suggestion ) => ( | ||
| <SuggestionButton | ||
| key={ suggestion.title } | ||
| { ...suggestion } | ||
| suggestion={ suggestion } | ||
| onClick={ onSuggestionClick } | ||
| /> | ||
| ) ) } | ||
| </div> |
There was a problem hiding this comment.
This is duplicated both in the branch where skipTransitions is true and where it's false., so that's not optimal. And it's weird that the skipTransitions check, controls not only the transitions but also other stuff (like the LoadingModalContent display logic, or that duplicated code's display logic). We should simplify this by making the if statement be in control of fewer things, or at the very least, rename the skipTransitions condition.
Context
This PR adds the Content Outline Modal component for the AI Content Planner feature. The modal appears after selecting a suggestion from the content suggestions modal and displays the AI-generated outline (focus keyphrase, title, meta description, blog post structure) for the user to review before adding it to their post.
Summary
This PR can be summarized in the following changelog entry:
Relevant technical choices
useCallbackfor all handlers to comply withreact/jsx-no-bindlint rule.withIds()helper to assign IDs at initialization time, avoiding index-based keys that break React reconciliation during reorder.Modal.Container.Contentwithpb-0on the scroll container andpb-4on the content div, matching the pattern from the task-list children modal.useSimulatedLoadingcustom hook — manages loading → success transition on mount usinguseState+useEffect(100ms delay to start loading, 3s to complete). Temporary: to be replaced with real API loading state when the outline endpoint is available.intentBadgeconfiguration — extracted tointent-badge.jsand shared betweencontent-suggestions-modalandcontent-outline-modalto eliminate duplication (addresses reviewer feedback).SuggestionsPanelcomponent andcameFromApproveModalflag).Transitionwrapper) to avoid layout flash caused by different panel sizes.SuggestionsPanelcomponent conditionally wrapsContentSuggestionsModalin a HeadlessUITransitiononly when coming from the approve modal, and uses a plain conditional render otherwise. This avoids the unmount-frame flicker thatTransitioncauses even with empty leave classes.getSuggestionsEnterTransitionhelper — extracted to keepFeatureModalcomplexity within the ESLint max (6).TextField/TextareaFieldwith local state, allowing users to adjust the suggested text before applying.backgroundColorstyles to match the Yoast snippet editor color palette ($color_good#7ad03afor 121–156,$color_ok#ee7c1bfor too short/long) — these are Yoast-specific colors that don't map to standard Tailwind classes.Modal.Title(HeadlessUI Dialog.Title) for properaria-labelledbyassociationModal.Descriptionforaria-describedbyon instruction textModal.CloseButtonwith ref for programmatic focus on mount — triggers screen reader to re-announce the dialog context when transitioning between panelsuseSvgAriahook on all SVG icons (YoastIcon, intent badge icons, drag handle) for consistentrole="img"+aria-hiddenaria-busy={isLoading}on the content region instead ofaria-liverole="listbox"+role="option"witharia-labelandaria-roledescriptionon structure rowsrole="note"on IntentCalloutTextField/TextareaFieldprovide proper<label htmlFor>/idassociation automaticallyTest instructions for the acceptance test before the PR gets merged
This PR can be acceptance tested by following these steps:
With only Free active
Loading state
The loading state is simulated internally, but it can also be triggered by following these instructions:
window.contentPlanner = { isOutlineLoading: true }Content state
TextField/TextareaField— no double border on focus)Transitions
Accessibility
htmlFor/idassociation automatically.Unit tests
Run
yarn jest tests/ai-content-planner/ --no-coveragefrompackages/js/— all 79 tests should pass.Relevant test scenarios
Test instructions for QA when the code is in the RC
Impact check
ContentOutlineModalcomponent inpackages/js/src/ai-content-planner/components/intent-badge.jsmodule (extracted from both suggestion and outline modals)packages/js/tests/ai-content-planner/components/content-outline-modal.test.jsContentSuggestionsModal— uses shared intent badge, transitions restored for loading/success internal cross-fadeFeatureModal— newSuggestionsPanelcomponent for conditional transition handling, instant switches for suggestions↔outlineContentPlannerEditorItem(added outline modal state, wiring, andwindow.contentPlanner.isOutlineLoading)Other environments
Documentation
Quality assurance
Innovation
innovationlabel.Fixes Yoast/reserved-tasks#1103