diff --git a/lib/one-onboarding/src/components/AppRoutes.js b/lib/one-onboarding/src/components/AppRoutes.js
index 9d1a10ad0e..5e5d01eb68 100644
--- a/lib/one-onboarding/src/components/AppRoutes.js
+++ b/lib/one-onboarding/src/components/AppRoutes.js
@@ -16,6 +16,8 @@ import {
import Header from './Header';
import { ACTIONS, useStore } from '@/store';
+const { screens: screensConfig } = window.oneOnboardingData || {};
+
const screens = [
{ id: 'welcome', element: Welcome },
{ id: 'user-info', element: UserInformation },
@@ -23,7 +25,7 @@ const screens = [
{ id: 'starter-templates', element: StarterTemplates },
{ id: 'add-ons', element: AddOns },
{ id: 'done', element: Done },
-];
+].filter( ( screen ) => screensConfig?.[ screen.id ]?.disabled !== true );
const AppRoutes = () => {
const { state, dispatch } = useStore();
diff --git a/lib/one-onboarding/src/screens/AddOns.js b/lib/one-onboarding/src/screens/AddOns.js
index e967711dfe..efedc219d6 100644
--- a/lib/one-onboarding/src/screens/AddOns.js
+++ b/lib/one-onboarding/src/screens/AddOns.js
@@ -11,7 +11,7 @@ import {
classnames,
} from '@/utils/functions';
-const { screens } = window.oneOnboardingData || {};
+const { screens, addonStatuses } = window.oneOnboardingData || {};
const screen = screens?.[ 'add-ons' ] || {};
const AddonItem = ( { addon, status, isSelected, onToggle, processing } ) => {
@@ -119,7 +119,11 @@ const AddonItem = ( { addon, status, isSelected, onToggle, processing } ) => {
};
const AddOns = () => {
- const [ addonStatus, setAddonStatus ] = useState( {} );
+ // Seed from server-provided statuses so already installed/active plugins show
+ // their status immediately; the async check below just refreshes it. Without
+ // this the badge is blank until the REST round-trip resolves, and clicking
+ // Continue in that window treats active plugins as not-installed.
+ const [ addonStatus, setAddonStatus ] = useState( addonStatuses || {} );
const [ processing, setProcessing ] = useState( false );
const { navigateToNextScreen, navigateToPreviousScreen, skipScreen } =
useRouterSync();
@@ -128,6 +132,21 @@ const AddOns = () => {
dispatch,
} = useStore();
+ useEffect( () => {
+ if ( selectedAddons.length === 0 ) {
+ const defaults =
+ screen?.addonList
+ ?.filter( ( a ) => a?.defaultSelected )
+ ?.map( ( a ) => a?.slug ) || [];
+ if ( defaults.length ) {
+ dispatch( {
+ type: ACTIONS.SET_SELECTED_ADDONS,
+ payload: defaults,
+ } );
+ }
+ }
+ }, [] );
+
useEffect( () => {
const fetchAddonStatuses = async () => {
if ( ! screen?.addonList?.length ) {
diff --git a/lib/one-onboarding/src/screens/Features.js b/lib/one-onboarding/src/screens/Features.js
index 46d633d093..854d317249 100644
--- a/lib/one-onboarding/src/screens/Features.js
+++ b/lib/one-onboarding/src/screens/Features.js
@@ -10,9 +10,23 @@ import {
classnames,
} from '@/utils/functions';
-const { screens, proSlug } = window.oneOnboardingData || {};
+const { screens, proSlug, selectAllProFeatures } =
+ window.oneOnboardingData || {};
const screen = screens?.features || {};
+/**
+ * When enabled, toggling any single Pro feature checkbox selects or clears the
+ * entire Pro set. Opt-in per product via the onboarding config
+ * (`select_all_pro_features`) so the library stays product-agnostic — every
+ * product that omits the flag keeps the default per-checkbox toggle.
+ */
+const useSelectAllProFeatures = !! selectAllProFeatures;
+
+// All Pro feature IDs — used to select/clear the whole Pro set at once.
+const proFeatureIds = ( screen?.featureList || [] )
+ .filter( ( feature ) => feature?.isPro )
+ .map( ( feature ) => feature?.id );
+
const Features = () => {
const { navigateToNextScreen, navigateToPreviousScreen, skipScreen } =
useRouterSync();
@@ -70,10 +84,32 @@ const Features = () => {
};
}, [ checkAndUpdateProStatus ] );
+ // A feature checkbox is locked (always checked, non-interactive) when it is a
+ // Free feature, or — only for products that opt into the Spectra Pro-as-a-set
+ // behavior via `selectAllProFeatures` — a Pro feature while the Pro plugin is
+ // already active. Other products keep their Pro checkboxes toggleable.
+ const isFeatureLocked = ( feature ) =>
+ ! feature?.isPro ||
+ ( useSelectAllProFeatures && proStatus === 'active' );
+
const handleFeatureToggle = ( feature, value ) => {
- const selectedFeatures = value
- ? [ ...proFeatures, feature?.id ]
- : proFeatures.filter( ( f ) => f !== feature?.id );
+ // Locked features (Free, or an active-Pro feature under the Spectra
+ // behavior) cannot be toggled off.
+ if ( isFeatureLocked( feature ) ) {
+ return;
+ }
+
+ let selectedFeatures;
+
+ if ( useSelectAllProFeatures ) {
+ // Spectra behavior: toggling any Pro feature selects or clears the
+ // entire Pro set, so all Pro checkboxes move together.
+ selectedFeatures = value ? [ ...proFeatureIds ] : [];
+ } else {
+ selectedFeatures = value
+ ? [ ...proFeatures, feature?.id ]
+ : proFeatures.filter( ( f ) => f !== feature?.id );
+ }
dispatch( {
type: ACTIONS.SET_PRO_FEATURES,
@@ -153,7 +189,15 @@ const Features = () => {
};
const isFeatureChecked = ( feature ) => {
- return feature?.isPro ? proFeatures?.includes( feature.id ) : true;
+ if ( ! feature?.isPro ) {
+ return true;
+ }
+ // Spectra behavior only: when the Pro plugin is active every Pro feature
+ // is available, so it is shown checked and cannot be toggled off.
+ if ( useSelectAllProFeatures && proStatus === 'active' ) {
+ return true;
+ }
+ return proFeatures?.includes( feature.id );
};
return (
@@ -181,7 +225,7 @@ const Features = () => {
div]:flex-row-reverse [&>div]:w-full [&>div]:justify-between border-0 border-b border-solid border-border-subtle',
- ! feature?.isPro &&
+ isFeatureLocked( feature ) &&
'[&>div]:pointer-events-none'
) }
>
@@ -214,7 +258,7 @@ const Features = () => {
onKeyDown={ ( event ) => {
if (
event.key === 'Enter' &&
- feature?.isPro
+ ! isFeatureLocked( feature )
) {
event.preventDefault();
handleFeatureToggle(
@@ -223,10 +267,12 @@ const Features = () => {
);
}
} }
- tabIndex={ feature?.isPro ? 0 : -1 }
+ tabIndex={
+ isFeatureLocked( feature ) ? -1 : 0
+ }
className={ classnames(
'focus:border-border-interactive',
- ! feature?.isPro &&
+ isFeatureLocked( feature ) &&
'checked:bg-[#E5E7EB] checked:border-[#E5E7EB] shadow-none'
) }
/>
diff --git a/lib/one-onboarding/src/screens/UserInformation.js b/lib/one-onboarding/src/screens/UserInformation.js
index 025fe5d807..b3da5b88c8 100644
--- a/lib/one-onboarding/src/screens/UserInformation.js
+++ b/lib/one-onboarding/src/screens/UserInformation.js
@@ -231,158 +231,167 @@ const UserInformation = () => {
) ) }
-
-
+ render={ ( value ) => value?.name }
+ />
+
+
+ { Object.entries( sourceOptions )?.map(
+ ( [ id, name ] ) => (
+
+ { name }
+
+ )
+ ) }
+
+
+
+
+ ) }
-
-
- dispatch( {
- type: ACTIONS.SET_USER_INFO,
- payload: { newUser: value },
- } )
- }
- placeholder={ __(
- 'Select an option',
- 'one-onboarding'
- ) }
- size="md"
- defaultValue={ userInfo.newUser }
- >
-
+
+ dispatch( {
+ type: ACTIONS.SET_USER_INFO,
+ payload: { newUser: value },
+ } )
+ }
+ placeholder={ __(
+ 'Select an option',
'one-onboarding'
) }
- render={ ( value ) => value?.name }
- />
-
-
- { Object.entries( freshUserOptions )?.map(
- ( [ id, name ] ) => (
-
- { name }
-
- )
- ) }
-
-
-
-
-
-
-
- dispatch( {
- type: ACTIONS.SET_USER_INFO,
- payload: { benefit: value },
- } )
- }
- placeholder={ __(
- 'Select an option',
- 'one-onboarding'
- ) }
- size="md"
- defaultValue={ userInfo.benefit }
- >
-
+ value?.name }
- />
-
-
- { Object.entries( benefitOptions )?.map(
- ( [ id, name ] ) => (
+ ) }
+ render={ ( value ) => value?.name }
+ />
+
+
+ { Object.entries(
+ freshUserOptions
+ )?.map( ( [ id, name ] ) => (
{ name }
- )
- ) }
-
-
-
+ ) ) }
+
+
+
+
+ ) }
- { userInfo.benefit?.id === 'other' && (
-