diff --git a/projects/js-packages/storybook/changelog/add-storybook-stories-search-179 b/projects/js-packages/storybook/changelog/add-storybook-stories-search-179 new file mode 100644 index 000000000000..7ca0a2600590 --- /dev/null +++ b/projects/js-packages/storybook/changelog/add-storybook-stories-search-179 @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Storybook Vite plugin: resolve bare `store` and `store/` imports for search dashboard stories. diff --git a/projects/js-packages/storybook/storybook/main.js b/projects/js-packages/storybook/storybook/main.js index 50742bc60808..da5ffe9cdf7c 100644 --- a/projects/js-packages/storybook/storybook/main.js +++ b/projects/js-packages/storybook/storybook/main.js @@ -83,7 +83,7 @@ const sbconfig = { name: 'search-dashboard-modules', async resolveId( id, importer ) { if ( - id.startsWith( 'components/' ) && + ( id.startsWith( 'components/' ) || id === 'store' || id.startsWith( 'store/' ) ) && importer?.includes( '/search/src/dashboard/' ) ) { const dummyFile = path.join( diff --git a/projects/packages/search/changelog/add-storybook-stories-search-179 b/projects/packages/search/changelog/add-storybook-stories-search-179 new file mode 100644 index 000000000000..e089dbb9da53 --- /dev/null +++ b/projects/packages/search/changelog/add-storybook-stories-search-179 @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Add Storybook stories for feature-selector components (ExperienceOption and FeatureSelector). diff --git a/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx b/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx new file mode 100644 index 000000000000..49a86ed2c511 --- /dev/null +++ b/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx @@ -0,0 +1,187 @@ +import { createReduxStore, createRegistry, RegistryProvider } from '@wordpress/data'; +import { storeConfig, STORE_ID } from '../../../store'; +import { EXPERIENCE } from '../constants'; +import ExperienceOption from '../experience-option'; + +export default { + title: 'Packages/Search/FeatureSelector/ExperienceOption', + component: ExperienceOption, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
+ +
+ ), + ], + argTypes: { + experience: { + control: 'select', + options: [ 'embedded', 'overlay', 'inline', 'off' ], + }, + disabled: { + control: 'boolean', + }, + }, +}; + +const createStoreWithSettings = jetpackSettings => { + const registry = createRegistry(); + const store = createReduxStore( STORE_ID, { + ...storeConfig, + initialState: { ...( storeConfig.initialState || {} ), jetpackSettings }, + } ); + registry.register( store ); + return registry; +}; + +// Pick a default `active` that won't accidentally match the rendered row, +// so baseline stories render as unselected/inactive unless `activeExperience` +// is set explicitly. Without this, the legacy boolean fallback in the +// `getActiveExperience` selector derives `'overlay'` from +// `instant_search_enabled: true` and contaminates every Overlay story. +const defaultActiveFor = experience => + experience === EXPERIENCE.EMBEDDED ? EXPERIENCE.INLINE : EXPERIENCE.EMBEDDED; + +const Template = args => { + const baseSettings = { + module_active: true, + instant_search_enabled: true, + pending_experience: args.pendingExperience ?? null, + experience: args.activeExperience ?? defaultActiveFor( args.experience ), + }; + const registry = createStoreWithSettings( baseSettings ); + return ( + + + + ); +}; + +// Embedded experience (default - shows RECOMMENDED badge) +export const Embedded = Template.bind( {} ); +Embedded.args = { + experience: EXPERIENCE.EMBEDDED, + disabled: false, + pendingExperience: null, + activeExperience: null, +}; + +// Embedded selected +export const EmbeddedSelected = Template.bind( {} ); +EmbeddedSelected.args = { + experience: EXPERIENCE.EMBEDDED, + disabled: false, + pendingExperience: EXPERIENCE.EMBEDDED, + activeExperience: null, +}; + +// Embedded active (shows ACTIVE badge) +export const EmbeddedActive = Template.bind( {} ); +EmbeddedActive.args = { + experience: EXPERIENCE.EMBEDDED, + disabled: false, + pendingExperience: null, + activeExperience: EXPERIENCE.EMBEDDED, +}; + +// Overlay experience +export const Overlay = Template.bind( {} ); +Overlay.args = { + experience: EXPERIENCE.OVERLAY, + disabled: false, + pendingExperience: null, + activeExperience: null, +}; + +// Overlay selected +export const OverlaySelected = Template.bind( {} ); +OverlaySelected.args = { + experience: EXPERIENCE.OVERLAY, + disabled: false, + pendingExperience: EXPERIENCE.OVERLAY, + activeExperience: null, +}; + +// Overlay active (instant_search_enabled=true maps to overlay) +export const OverlayActive = Template.bind( {} ); +OverlayActive.args = { + experience: EXPERIENCE.OVERLAY, + disabled: false, + pendingExperience: null, + activeExperience: EXPERIENCE.OVERLAY, +}; + +// Theme search (inline) experience +export const Inline = Template.bind( {} ); +Inline.args = { + experience: EXPERIENCE.INLINE, + disabled: false, + pendingExperience: null, + activeExperience: null, +}; + +// Inline selected +export const InlineSelected = Template.bind( {} ); +InlineSelected.args = { + experience: EXPERIENCE.INLINE, + disabled: false, + pendingExperience: EXPERIENCE.INLINE, + activeExperience: null, +}; + +// Inline active +export const InlineActive = Template.bind( {} ); +InlineActive.args = { + experience: EXPERIENCE.INLINE, + disabled: false, + pendingExperience: null, + activeExperience: EXPERIENCE.INLINE, +}; + +// Off experience +export const Off = Template.bind( {} ); +Off.args = { + experience: EXPERIENCE.OFF, + disabled: false, + pendingExperience: null, + activeExperience: null, +}; + +// Off selected +export const OffSelected = Template.bind( {} ); +OffSelected.args = { + experience: EXPERIENCE.OFF, + disabled: false, + pendingExperience: EXPERIENCE.OFF, + activeExperience: null, +}; + +// Off active +export const OffActive = Template.bind( {} ); +OffActive.args = { + experience: EXPERIENCE.OFF, + disabled: false, + pendingExperience: null, + activeExperience: EXPERIENCE.OFF, +}; + +// Disabled experience (shows upgrade tooltip) +export const Disabled = Template.bind( {} ); +Disabled.args = { + experience: EXPERIENCE.EMBEDDED, + disabled: true, + pendingExperience: null, + activeExperience: null, +}; + +// Unselected state +export const Unselected = Template.bind( {} ); +Unselected.args = { + experience: EXPERIENCE.INLINE, + disabled: false, + pendingExperience: EXPERIENCE.EMBEDDED, + activeExperience: null, +}; diff --git a/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx b/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx new file mode 100644 index 000000000000..b4969d499062 --- /dev/null +++ b/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx @@ -0,0 +1,193 @@ +import { createReduxStore, createRegistry, RegistryProvider } from '@wordpress/data'; +import { storeConfig, STORE_ID } from '../../../store'; +import { EXPERIENCE } from '../constants'; +import FeatureSelector from '../index'; + +export default { + title: 'Packages/Search/FeatureSelector', + component: FeatureSelector, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
+ +
+ ), + ], +}; + +const createStoreWithSettings = ( jetpackSettings, sitePlan = {}, siteData = {} ) => { + const registry = createRegistry(); + const store = createReduxStore( STORE_ID, { + ...storeConfig, + initialState: { + ...( storeConfig.initialState || {} ), + jetpackSettings, + sitePlan, + siteData, + }, + } ); + registry.register( store ); + return registry; +}; + +// Clean state - Save button is aria-disabled (no changes made) +export const Clean = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: null, + experience: EXPERIENCE.OVERLAY, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Dirty state - Save button enabled (user selected a different experience) +export const Dirty = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: EXPERIENCE.INLINE, + experience: EXPERIENCE.OVERLAY, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Saving state - Save button shows loading spinner +export const Saving = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: EXPERIENCE.INLINE, + experience: EXPERIENCE.OVERLAY, + is_updating: true, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Classic-only plan - Embedded and Overlay rows are disabled +export const ClassicOnlyPlan = () => { + const settings = { + module_active: true, + instant_search_enabled: false, + pending_experience: null, + experience: EXPERIENCE.INLINE, + is_updating: false, + }; + const sitePlan = { + supports_only_classic_search: true, + }; + const registry = createStoreWithSettings( settings, sitePlan ); + return ( + + + + ); +}; + +// Embedded experience active +export const EmbeddedActive = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: null, + experience: EXPERIENCE.EMBEDDED, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Overlay experience active +export const OverlayActive = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: null, + experience: EXPERIENCE.OVERLAY, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Theme search (inline) experience active +export const InlineActive = () => { + const settings = { + module_active: true, + instant_search_enabled: false, + pending_experience: null, + experience: EXPERIENCE.INLINE, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Off experience active +export const OffActive = () => { + const settings = { + module_active: false, + instant_search_enabled: false, + pending_experience: null, + experience: EXPERIENCE.OFF, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// WordPress.com site - Off option is hidden +export const WpcomSite = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: null, + experience: EXPERIENCE.OVERLAY, + is_updating: false, + }; + const siteData = { + isWpcom: true, + }; + const registry = createStoreWithSettings( settings, {}, siteData ); + return ( + + + + ); +}; diff --git a/projects/packages/search/src/dashboard/dummy.js b/projects/packages/search/src/dashboard/dummy.js new file mode 100644 index 000000000000..d9d8ef4f6c15 --- /dev/null +++ b/projects/packages/search/src/dashboard/dummy.js @@ -0,0 +1,11 @@ +/** + * Dummy file used by the Storybook Vite plugin (`search-dashboard-modules`) + * to resolve bare `components/` and `store` imports relative to the dashboard + * directory. + * + * The plugin intercepts imports like `import { STORE_ID } from 'store';` in + * dashboard components and resolves them as `./store` relative to this file's + * location, so they correctly point to `src/dashboard/store/index.js`. + * + * See: projects/js-packages/storybook/storybook/main.js + */