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
+ */