From 06dbbfdfdbcd7b39240a591c4595f103e5cfdc55 Mon Sep 17 00:00:00 2001 From: harshit Date: Wed, 22 Jul 2026 17:56:38 +0530 Subject: [PATCH 1/3] Improve compact feature recommendation panel in onboarding --- onboarding/src/Components/FeaturesList.js | 23 +++--- onboarding/src/Components/SiteSettings.js | 3 +- onboarding/src/scss/_features_list.scss | 85 +++++++++++++++++------ onboarding/src/scss/_site-settings.scss | 30 +++++++- 4 files changed, 104 insertions(+), 37 deletions(-) diff --git a/onboarding/src/Components/FeaturesList.js b/onboarding/src/Components/FeaturesList.js index 411c41b4..68c3e29b 100644 --- a/onboarding/src/Components/FeaturesList.js +++ b/onboarding/src/Components/FeaturesList.js @@ -12,37 +12,31 @@ const featuredPluginCollection = [ id: 'pageBuilder', pluginSlug: 'otter-blocks', label: __('Site Builder', 'templates-patterns-collection'), - description: __('Build beautiful pages with a simple drag-and-drop page builder.', 'templates-patterns-collection') - }, - { - id: 'contactForm', - pluginSlug: 'otter-blocks', - label: __('Contact Form', 'templates-patterns-collection'), - description: __('Create forms to capture leads and feedback.', 'templates-patterns-collection') + description: __('Build pages and forms with Otter.', 'templates-patterns-collection') }, { id: 'imageOpt', pluginSlug: 'optimole-wp', label: __('Image Optimization', 'templates-patterns-collection'), - description: __('Make your site faster with automatic image optimization.', 'templates-patterns-collection') + description: __('Auto-compress images for faster pages.', 'templates-patterns-collection') }, { id: 'caching', pluginSlug: 'wp-cloudflare-page-cache', label: __('Caching', 'templates-patterns-collection'), - description: __('Supercharge your site’s speed with powerful caching.', 'templates-patterns-collection') + description: __('Speed up your site with smart caching.', 'templates-patterns-collection') }, { id: 'eCommerce', pluginSlug: 'wp-full-stripe-free', label: __('eCommerce', 'templates-patterns-collection'), - description: __('Set up your store and start accepting payments via Stripe.', 'templates-patterns-collection'), + description: __('Sell online and accept Stripe payments.', 'templates-patterns-collection'), }, { id: 'chatbot', pluginSlug: 'hyve-lite', label: __('AI ChatBot', 'templates-patterns-collection'), - description: __('Keep visitors engaged with an intelligent AI chatbot.', 'templates-patterns-collection'), + description: __('Engage visitors with an AI chatbot.', 'templates-patterns-collection'), } ]; @@ -54,7 +48,7 @@ const pluginCollection = [ id: 'visualizer', pluginSlug: 'visualizer', label: __('Tables and Chart', 'templates-patterns-collection'), - description: __('A powerful and easy to use plugin for responsive charts & tables.', 'templates-patterns-collection') + description: __('Responsive charts and tables.', 'templates-patterns-collection') }, { id: 'woocommerce', @@ -102,13 +96,13 @@ const pluginCollection = [ id: 'pods', pluginSlug: 'pods', label: __('Pods', 'templates-patterns-collection'), - description: __('A framework for creating, managing, and deploying customized content types and fields for any project.', 'templates-patterns-collection') + description: __('Custom content types and fields made easy.', 'templates-patterns-collection') }, { id: 'restrict-content', pluginSlug: 'restrict-content', label: __('Membership Plugin', 'templates-patterns-collection'), - description: __('Membership plugin that allows you to monetize content access.', 'templates-patterns-collection') + description: __('Monetize members-only content.', 'templates-patterns-collection') }, { id: 'learning-management-system', @@ -123,7 +117,6 @@ const FeaturesList = ({ requiredPlugins, onToggle }) => { const [selectedFeatures, setSelectedFeatures] = useState({ pageBuilder: false, - contactForm: false, eCommerce: false, donations: false, automation: false, diff --git a/onboarding/src/Components/SiteSettings.js b/onboarding/src/Components/SiteSettings.js index bd2d5527..b75be1a2 100644 --- a/onboarding/src/Components/SiteSettings.js +++ b/onboarding/src/Components/SiteSettings.js @@ -146,7 +146,8 @@ export const SiteSettings = ( {
{ ! fetching ? ( diff --git a/onboarding/src/scss/_features_list.scss b/onboarding/src/scss/_features_list.scss index a2a35a81..04fa8724 100644 --- a/onboarding/src/scss/_features_list.scss +++ b/onboarding/src/scss/_features_list.scss @@ -1,47 +1,78 @@ .ob-select-features { display: flex; flex-direction: column; + width: 100%; + overflow-x: hidden; .ob-features-grid { display: flex; flex-direction: column; - gap: 1rem; + gap: 10px; width: 100%; + min-width: 0; + overflow-x: hidden; } .ob-feature-card { display: flex; flex-direction: column; align-items: flex-start; - padding: 1rem; + width: 100%; + min-width: 0; + box-sizing: border-box; + padding: 12px 14px; background-color: #ffffff; - border: 1px solid #e0e0e0; - border-radius: 6px; + border: 1px solid #e3e8ef; + border-radius: 10px; cursor: pointer; transition: box-shadow 0.3s ease, border 0.3s ease; + text-align: left; &:hover:not(.ob-disabled) { - border-color: #007cba; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border-color: #9ec3ef; + box-shadow: 0 2px 8px rgba(3, 102, 213, 0.08); } &:focus { outline: none; - border-color: #007cba; - box-shadow: 0 0 0 4px rgba(0, 124, 186, 0.2); + border-color: #0366d5; + box-shadow: 0 0 0 3px rgba(3, 102, 213, 0.18); } &.selected { - border-color: #007cba; - background-color: #e8f4fa; + border-color: #8bb6f3; + background-color: #eaf3ff; + + .ob-feature-title { + color: #0f4fbf; + } + + .ob-feature-description { + color: #5d6f87; + } } &.ob-disabled { cursor: not-allowed; - opacity: 0.6; + opacity: 1; + border-color: #dce3ee; + background-color: #f7f9fc; + + .ob-feature-title { + color: #7b8697; + } + + .ob-feature-description { + color: #95a0b1; + } + + input[type='checkbox'] { + cursor: not-allowed; + opacity: 0.7; + } &:hover { - border-color: #e0e0e0; + border-color: #dce3ee; box-shadow: none; } } @@ -50,28 +81,42 @@ display: flex; align-items: center; justify-content: space-between; + gap: 10px; width: 100%; .ob-feature-title { - font-size: 1rem; - font-weight: bold; - color: #333333; + font-size: 16px; + line-height: 1.2; + font-weight: 700; + color: #1f2937; margin: 0; text-align: left; + min-width: 0; } input[type='checkbox'] { cursor: pointer; - accent-color: #007cba; - transform: scale(1.2); + accent-color: #2b62de; + margin: 0; + width: 18px; + height: 18px; + flex-shrink: 0; } } .ob-feature-description { - font-size: 0.875rem; - color: #666666; - margin-top: 0.5rem; + font-size: 14px; + line-height: 1.35; + color: #7d8895; + margin-top: 4px; text-align: left; + overflow-wrap: anywhere; + + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + line-clamp: 2; + overflow: hidden; } } } \ No newline at end of file diff --git a/onboarding/src/scss/_site-settings.scss b/onboarding/src/scss/_site-settings.scss index 30b4556c..ac2b42dd 100644 --- a/onboarding/src/scss/_site-settings.scss +++ b/onboarding/src/scss/_site-settings.scss @@ -96,4 +96,32 @@ padding: 20px; background: #fff; box-shadow: 4px 4px 40px 0 rgba(0, 0, 0, 0.10); -} \ No newline at end of file +} + +.ob-site-settings.is-step-4-features { + .ob-settings-description { + padding-top: 16px; + gap: 8px; + margin-bottom: 20px; + + p { + margin: 0; + font-size: 15px; + line-height: 1.45; + } + } + + .ob-settings-top { + gap: 16px; + } + + .ob-settings-bottom { + padding: 14px 20px 16px; + } + + .ob-feature-card { + .ob-feature-description { + white-space: normal; + } + } +} From 614655fad648ddccc259cc7ae9157912c5c184bf Mon Sep 17 00:00:00 2001 From: harshit Date: Wed, 22 Jul 2026 18:53:59 +0530 Subject: [PATCH 2/3] Improve feature card accessibility contrast and focus visibility --- onboarding/src/scss/_features_list.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/onboarding/src/scss/_features_list.scss b/onboarding/src/scss/_features_list.scss index 04fa8724..ba6876d3 100644 --- a/onboarding/src/scss/_features_list.scss +++ b/onboarding/src/scss/_features_list.scss @@ -2,7 +2,6 @@ display: flex; flex-direction: column; width: 100%; - overflow-x: hidden; .ob-features-grid { display: flex; @@ -10,7 +9,6 @@ gap: 10px; width: 100%; min-width: 0; - overflow-x: hidden; } .ob-feature-card { @@ -107,7 +105,7 @@ .ob-feature-description { font-size: 14px; line-height: 1.35; - color: #7d8895; + color: #666666; margin-top: 4px; text-align: left; overflow-wrap: anywhere; From cc30a46bca216d4f0e47372f4ec656155b25471e Mon Sep 17 00:00:00 2001 From: harshit Date: Tue, 4 Aug 2026 18:08:52 +0530 Subject: [PATCH 3/3] Auto-fit feature descriptions to the available panel height Descriptions now render expanded and collapse only when the card list would run past the footer, so the panel no longer needs scrolling on short viewports. Each card gets a chevron toggle that reuses the title for its accessible name, and selected features give up their description first since unselected ones still need the pitch. Updates the e2e selectors to target the new markup via data-plugin and relaxes the card count assertion, as the collection is now five entries. --- e2e-tests/specs/onboarding.spec.js | 15 ++- onboarding/src/Components/FeaturesList.js | 131 +++++++++++++++++----- onboarding/src/scss/_features_list.scss | 72 ++++++++---- onboarding/src/scss/_site-settings.scss | 6 - 4 files changed, 168 insertions(+), 56 deletions(-) diff --git a/e2e-tests/specs/onboarding.spec.js b/e2e-tests/specs/onboarding.spec.js index 604409cf..c5a2fc82 100644 --- a/e2e-tests/specs/onboarding.spec.js +++ b/e2e-tests/specs/onboarding.spec.js @@ -24,6 +24,9 @@ test.describe('Onboarding', () => { await expect(page.locator('.ob-error-wrap')).toHaveCount(0); }; + const featureCard = ( page, pluginSlug ) => + page.locator(`.ob-feature-header[data-plugin="${ pluginSlug }"] .ob-feature-select`); + test('Sub-menu in Admin page', async ({ page, admin }) => { await admin.visitAdminPage('/'); @@ -110,13 +113,17 @@ test.describe('Onboarding', () => { await openFirstSiteAndWaitForData( page ); await page.getByRole('button', { name: 'Continue' }).click(); - expect(await page.locator('.ob-feature-card').count()).toBe(6); + // FeaturesList caps the list at MAX_FEATURE_LIST_LENGTH (6). + const featureCardCount = await page.locator('.ob-feature-card').count(); + expect(featureCardCount).toBeGreaterThanOrEqual(5); + expect(featureCardCount).toBeLessThanOrEqual(6); expect( - await page.locator('.ob-feature-card.ob-disabled[aria-checked="true"]').count(), + await page.locator('.ob-feature-card.ob-disabled .ob-feature-select[aria-checked="true"]').count(), ).toBeGreaterThan(0); // We have some required plugin that are active by default. // Check if we can select a plugin to install. - const cachePlugin = page.getByRole('checkbox', { name: 'Caching Supercharge your site' }); + const cachePlugin = featureCard( page, 'wp-cloudflare-page-cache' ); + await expect(cachePlugin).toHaveAttribute('aria-checked', 'false'); await cachePlugin.click(); await expect(cachePlugin).toHaveAttribute('aria-checked', 'true'); @@ -133,7 +140,7 @@ test.describe('Onboarding', () => { await admin.visitAdminPage(ONBOARDING_URL); await openFirstSiteAndWaitForData( page ); await page.getByRole('button', { name: 'Continue' }).click(); - const cachePlugin = page.getByRole('checkbox', { name: 'Caching Supercharge your site' }); + const cachePlugin = featureCard( page, 'wp-cloudflare-page-cache' ); await cachePlugin.click(); await page.getByRole('button', { name: 'Import Website' }).click(); diff --git a/onboarding/src/Components/FeaturesList.js b/onboarding/src/Components/FeaturesList.js index 68c3e29b..679f49f1 100644 --- a/onboarding/src/Components/FeaturesList.js +++ b/onboarding/src/Components/FeaturesList.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from '@wordpress/element'; +import { useState, useEffect, useLayoutEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { decodeHtmlEntities } from '../utils/common'; @@ -18,25 +18,25 @@ const featuredPluginCollection = [ id: 'imageOpt', pluginSlug: 'optimole-wp', label: __('Image Optimization', 'templates-patterns-collection'), - description: __('Auto-compress images for faster pages.', 'templates-patterns-collection') + description: __('Make your site faster with automatic image optimization.', 'templates-patterns-collection') }, { id: 'caching', pluginSlug: 'wp-cloudflare-page-cache', label: __('Caching', 'templates-patterns-collection'), - description: __('Speed up your site with smart caching.', 'templates-patterns-collection') + description: __('Supercharge your site’s speed with powerful caching.', 'templates-patterns-collection') }, { id: 'eCommerce', pluginSlug: 'wp-full-stripe-free', label: __('eCommerce', 'templates-patterns-collection'), - description: __('Sell online and accept Stripe payments.', 'templates-patterns-collection'), + description: __('Set up your store and start accepting payments via Stripe.', 'templates-patterns-collection'), }, { id: 'chatbot', pluginSlug: 'hyve-lite', label: __('AI ChatBot', 'templates-patterns-collection'), - description: __('Engage visitors with an AI chatbot.', 'templates-patterns-collection'), + description: __('Keep visitors engaged with an intelligent AI chatbot.', 'templates-patterns-collection'), } ]; @@ -48,7 +48,7 @@ const pluginCollection = [ id: 'visualizer', pluginSlug: 'visualizer', label: __('Tables and Chart', 'templates-patterns-collection'), - description: __('Responsive charts and tables.', 'templates-patterns-collection') + description: __('A powerful and easy to use plugin for responsive charts & tables.', 'templates-patterns-collection') }, { id: 'woocommerce', @@ -96,13 +96,13 @@ const pluginCollection = [ id: 'pods', pluginSlug: 'pods', label: __('Pods', 'templates-patterns-collection'), - description: __('Custom content types and fields made easy.', 'templates-patterns-collection') + description: __('A framework for creating, managing, and deploying customized content types and fields for any project.', 'templates-patterns-collection') }, { id: 'restrict-content', pluginSlug: 'restrict-content', label: __('Membership Plugin', 'templates-patterns-collection'), - description: __('Monetize members-only content.', 'templates-patterns-collection') + description: __('Membership plugin that allows you to monetize content access.', 'templates-patterns-collection') }, { id: 'learning-management-system', @@ -126,6 +126,19 @@ const FeaturesList = ({ requiredPlugins, onToggle }) => { }); const [lockedPluginSlugs, setLockedPluginSlugs] = useState([]); + const [expandedFeatures, setExpandedFeatures] = useState({}); + + const gridRef = useRef(null); + const autoFitApplied = useRef(false); + + const toggleExpanded = (feature) => { + // Any manual toggle takes over from the automatic fit. + autoFitApplied.current = true; + setExpandedFeatures((prev) => ({ + ...prev, + [feature]: !prev[feature], + })); + }; const toggleFeature = (feature, pluginSlug) => { if (lockedPluginSlugs.includes(pluginSlug)) { @@ -191,41 +204,105 @@ const FeaturesList = ({ requiredPlugins, onToggle }) => { setFeatureList(orderedFeatures); setLockedPluginSlugs(requiredPluginSlugs); + setExpandedFeatures( + Object.fromEntries(orderedFeatures.map(({ id }) => [id, true])) + ); + autoFitApplied.current = false; }, [requiredPlugins]); + // Descriptions start open and collapse only if the list would run past the footer. + // Selected features give up their description first, since unselected ones still need the pitch. + useLayoutEffect(() => { + if (autoFitApplied.current || !gridRef.current || 0 === featureList.length) { + return; + } + autoFitApplied.current = true; + + const footer = document.querySelector('.ob-settings-bottom'); + const limit = window.innerHeight - (footer ? footer.offsetHeight : 0); + const overflow = gridRef.current.getBoundingClientRect().bottom - limit; + + if (overflow <= 0) { + return; + } + + const heightOf = (id) => { + const description = gridRef.current.querySelector(`#ob-feature-desc-${id}`); + if (!description || description.hidden) { + return 0; + } + return description.offsetHeight + parseFloat(window.getComputedStyle(description).marginTop || 0); + }; + + const isSelected = ({ id, pluginSlug }) => selectedFeatures[id] || lockedPluginSlugs.includes(pluginSlug); + const reclaimed = featureList.filter(isSelected).reduce((total, { id }) => total + heightOf(id), 0); + + setExpandedFeatures( + reclaimed >= overflow + ? Object.fromEntries(featureList.filter((feature) => !isSelected(feature)).map(({ id }) => [id, true])) + : {} + ); + }, [featureList]); + return (
-
+
{ featureList.map((feature) => { const checked = selectedFeatures[feature.id] || lockedPluginSlugs.includes(feature.pluginSlug); const isLocked = lockedPluginSlugs.includes(feature.pluginSlug); + const isExpanded = Boolean(expandedFeatures[feature.id]); + const titleId = `ob-feature-title-${feature.id}`; + const descriptionId = `ob-feature-desc-${feature.id}`; return ( - + {feature.description && ( + + )}
-
{feature.description}
- + {feature.description && ( + + )} +
); }) } diff --git a/onboarding/src/scss/_features_list.scss b/onboarding/src/scss/_features_list.scss index ba6876d3..231c0733 100644 --- a/onboarding/src/scss/_features_list.scss +++ b/onboarding/src/scss/_features_list.scss @@ -22,7 +22,6 @@ background-color: #ffffff; border: 1px solid #e3e8ef; border-radius: 10px; - cursor: pointer; transition: box-shadow 0.3s ease, border 0.3s ease; text-align: left; @@ -31,7 +30,7 @@ box-shadow: 0 2px 8px rgba(3, 102, 213, 0.08); } - &:focus { + &:focus-within { outline: none; border-color: #0366d5; box-shadow: 0 0 0 3px rgba(3, 102, 213, 0.18); @@ -44,10 +43,6 @@ .ob-feature-title { color: #0f4fbf; } - - .ob-feature-description { - color: #5d6f87; - } } &.ob-disabled { @@ -60,10 +55,6 @@ color: #7b8697; } - .ob-feature-description { - color: #95a0b1; - } - input[type='checkbox'] { cursor: not-allowed; opacity: 0.7; @@ -78,16 +69,33 @@ .ob-feature-header { display: flex; align-items: center; - justify-content: space-between; - gap: 10px; + gap: 8px; width: 100%; + .ob-feature-select { + display: flex; + align-items: center; + gap: 10px; + flex: 1; + min-width: 0; + padding: 0; + background: none; + border: 0; + cursor: pointer; + text-align: left; + + &:focus { + outline: none; + } + } + .ob-feature-title { font-size: 16px; line-height: 1.2; font-weight: 700; color: #1f2937; margin: 0; + flex: 1; text-align: left; min-width: 0; } @@ -100,21 +108,47 @@ height: 18px; flex-shrink: 0; } + + .ob-feature-expand { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 28px; + height: 28px; + margin-right: -6px; + padding: 0; + background: none; + border: 0; + border-radius: 4px; + color: #9aa3b2; + cursor: pointer; + + &:hover { + color: #0f4fbf; + background-color: rgba(3, 102, 213, 0.08); + } + + svg { + transition: transform 0.2s ease; + } + + &[aria-expanded='true'] svg { + transform: rotate(180deg); + } + } } .ob-feature-description { font-size: 14px; line-height: 1.35; color: #666666; - margin-top: 4px; + margin-top: 6px; text-align: left; overflow-wrap: anywhere; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - line-clamp: 2; - overflow: hidden; + // Align with the title rather than the leading checkbox. + padding-left: 28px; } } -} \ No newline at end of file +} diff --git a/onboarding/src/scss/_site-settings.scss b/onboarding/src/scss/_site-settings.scss index ac2b42dd..c7fdbbc8 100644 --- a/onboarding/src/scss/_site-settings.scss +++ b/onboarding/src/scss/_site-settings.scss @@ -118,10 +118,4 @@ .ob-settings-bottom { padding: 14px 20px 16px; } - - .ob-feature-card { - .ob-feature-description { - white-space: normal; - } - } }