diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.js b/.buildkite/scripts/steps/storybooks/build_and_upload.js
index 9d72f518837e..c1032575ae4a 100644
--- a/.buildkite/scripts/steps/storybooks/build_and_upload.js
+++ b/.buildkite/scripts/steps/storybooks/build_and_upload.js
@@ -13,6 +13,7 @@ const STORYBOOKS = [
'dashboard_enhanced',
'data_enhanced',
'embeddable',
+ 'fleet',
'infra',
'security_solution',
'ui_actions_enhanced',
diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts
index 9395c5fdf883..a61a2618d642 100644
--- a/src/dev/storybook/aliases.ts
+++ b/src/dev/storybook/aliases.ts
@@ -24,6 +24,7 @@ export const storybookAliases = {
expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook',
expression_shape: 'src/plugins/expression_shape/.storybook',
expression_tagcloud: 'src/plugins/chart_expressions/expression_tagcloud/.storybook',
+ fleet: 'x-pack/plugins/fleet/storybook',
infra: 'x-pack/plugins/infra/.storybook',
security_solution: 'x-pack/plugins/security_solution/.storybook',
ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook',
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx
new file mode 100644
index 000000000000..d98f2b2408d5
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { AssetsFacetGroup as Component } from './assets_facet_group';
+
+export default {
+ component: Component,
+ title: 'Sections/EPM/Assets Facet Group',
+};
+
+interface Args {
+ width: number;
+}
+
+const args: Args = {
+ width: 250,
+};
+
+export const AssetsFacetGroup = ({ width }: Args) => {
+ return (
+
+
+
+ );
+};
+
+AssetsFacetGroup.args = args;
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx
new file mode 100644
index 000000000000..e8814b8b8c87
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import type { SavedObject } from 'src/core/public';
+
+import type { Installation } from '../../../../../../common';
+
+import type { PackageCardProps } from './package_card';
+import { PackageCard } from './package_card';
+
+export default {
+ title: 'Sections/EPM/Package Card',
+ description: 'A card representing a package available in Fleet',
+};
+
+type Args = Omit & { width: number };
+
+const args: Args = {
+ width: 250,
+ title: 'Title',
+ description: 'Description',
+ name: 'beats',
+ release: 'ga',
+ id: 'id',
+ version: '1.0.0',
+ download: '/',
+ path: 'path',
+};
+
+const argTypes = {
+ release: {
+ control: {
+ type: 'radio',
+ options: ['ga', 'beta', 'experimental'],
+ },
+ },
+};
+
+export const NotInstalled = ({ width, ...props }: Args) => (
+
+);
+
+export const Installed = ({ width, ...props }: Args) => {
+ const savedObject: SavedObject = {
+ id: props.id,
+ type: props.type || '',
+ attributes: {
+ name: props.name,
+ version: props.version,
+ install_version: props.version,
+ es_index_patterns: {},
+ installed_kibana: [],
+ installed_es: [],
+ install_status: 'installed',
+ install_source: 'registry',
+ install_started_at: '2020-01-01T00:00:00.000Z',
+ },
+ references: [],
+ };
+
+ return (
+
+ );
+};
+
+NotInstalled.args = args;
+NotInstalled.argTypes = argTypes;
+Installed.args = args;
+Installed.argTypes = argTypes;
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx
index c12e67fdb571..c2d6d0f1e028 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx
@@ -15,7 +15,7 @@ import { PackageIcon } from '../../../components';
import { RELEASE_BADGE_LABEL, RELEASE_BADGE_DESCRIPTION } from './release_badge';
-type PackageCardProps = PackageListItem;
+export type PackageCardProps = PackageListItem;
// adding the `href` causes EuiCard to use a `a` instead of a `button`
// `a` tags use `euiLinkColor` which results in blueish Badge text
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx
new file mode 100644
index 000000000000..d84e286b6f56
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx
@@ -0,0 +1,138 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { action } from '@storybook/addon-actions';
+
+import type { SavedObject } from 'src/core/public';
+
+import type { Installation } from '../../../../../../common';
+
+import type { ListProps } from './package_list_grid';
+import { PackageListGrid } from './package_list_grid';
+
+export default {
+ component: PackageListGrid,
+ title: 'Sections/EPM/Package List Grid',
+};
+
+type Args = Pick;
+
+const args: Args = {
+ title: 'Installed integrations',
+ isLoading: false,
+ showMissingIntegrationMessage: false,
+};
+
+const savedObject: SavedObject = {
+ id: 'id',
+ type: 'integration',
+ attributes: {
+ name: 'savedObject',
+ version: '1.2.3',
+ install_version: '1.2.3',
+ es_index_patterns: {},
+ installed_kibana: [],
+ installed_es: [],
+ install_status: 'installed',
+ install_source: 'registry',
+ install_started_at: '2020-01-01T00:00:00.000Z',
+ },
+ references: [],
+};
+
+export const EmptyList = (props: Args) => (
+
+);
+
+export const List = (props: Args) => (
+
+);
+
+EmptyList.args = args;
+List.args = args;
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx
index 6bbd479c5c2b..db63c5c7dd83 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx
@@ -28,7 +28,7 @@ import { useLocalSearch, searchIdField } from '../../../hooks';
import { PackageCard } from './package_card';
-interface ListProps {
+export interface ListProps {
isLoading?: boolean;
controls?: ReactNode;
title: string;
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/requirements.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/requirements.stories.tsx
new file mode 100644
index 000000000000..205d739d4869
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/requirements.stories.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { Requirements as Component } from './requirements';
+
+export default {
+ component: Component,
+ title: 'Sections/EPM/Requirements',
+};
+
+interface Args {
+ width: number;
+}
+
+const args: Args = {
+ width: 250,
+};
+
+export const Requirements = ({ width }: Args) => {
+ return (
+
+
+
+ );
+};
+
+Requirements.args = args;
diff --git a/x-pack/plugins/fleet/storybook/decorator.tsx b/x-pack/plugins/fleet/storybook/decorator.tsx
new file mode 100644
index 000000000000..499b01c2bfba
--- /dev/null
+++ b/x-pack/plugins/fleet/storybook/decorator.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { of } from 'rxjs';
+import type { DecoratorFn } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+import { createMemoryHistory } from 'history';
+
+import { I18nProvider } from '@kbn/i18n/react';
+
+import { ScopedHistory } from '../../../../src/core/public';
+import { IntegrationsAppContext } from '../public/applications/integrations/app';
+import type { FleetConfigType, FleetStartServices } from '../public/plugin';
+
+// TODO: clintandrewhall - this is not ideal, or complete. The root context of Fleet applications
+// requires full start contracts of its dependencies. As a result, we have to mock all of those contracts
+// with Storybook equivalents. This is a temporary solution, and should be replaced with a more complete
+// mock later, (or, ideally, Fleet starts to use a service abstraction).
+//
+// Expect this to grow as components that are given Stories need access to mocked services.
+export const contextDecorator: DecoratorFn = (story: Function) => {
+ const basepath = '/';
+ const memoryHistory = createMemoryHistory({ initialEntries: [basepath] });
+ const history = new ScopedHistory(memoryHistory, basepath);
+
+ const startServices = {
+ application: {
+ currentAppId$: of('home'),
+ navigateToUrl: (url: string) => action(`Navigate to: ${url}`),
+ getUrlForApp: (url: string) => url,
+ },
+ http: {
+ basePath: {
+ prepend: () => basepath,
+ },
+ },
+ notifications: {},
+ history,
+ uiSettings: {
+ get$: (key: string) => {
+ switch (key) {
+ case 'theme:darkMode':
+ return of(false);
+ default:
+ return of();
+ }
+ },
+ },
+ i18n: {
+ Context: I18nProvider,
+ },
+ } as unknown as FleetStartServices;
+
+ const config = {
+ enabled: true,
+ agents: {
+ enabled: true,
+ elasticsearch: {},
+ },
+ } as unknown as FleetConfigType;
+
+ const extensions = {};
+
+ const kibanaVersion = '1.2.3';
+
+ return (
+
+ {story()}
+
+ );
+};
diff --git a/x-pack/plugins/fleet/storybook/main.ts b/x-pack/plugins/fleet/storybook/main.ts
new file mode 100644
index 000000000000..2cf4124c6178
--- /dev/null
+++ b/x-pack/plugins/fleet/storybook/main.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { Configuration } from 'webpack';
+import { defaultConfig, WebpackConfig } from '@kbn/storybook';
+
+module.exports = {
+ ...defaultConfig,
+ addons: ['@storybook/addon-essentials'],
+ babel: () => ({
+ presets: [require.resolve('@kbn/babel-preset/webpack_preset')],
+ }),
+ webpackFinal: (config: Configuration) => {
+ return WebpackConfig({ config });
+ },
+};
diff --git a/x-pack/plugins/fleet/storybook/manager.ts b/x-pack/plugins/fleet/storybook/manager.ts
new file mode 100644
index 000000000000..471a735ed370
--- /dev/null
+++ b/x-pack/plugins/fleet/storybook/manager.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { addons } from '@storybook/addons';
+import { create } from '@storybook/theming';
+import { PANEL_ID } from '@storybook/addon-actions';
+
+addons.setConfig({
+ theme: create({
+ base: 'light',
+ brandTitle: 'Kibana Fleet Storybook',
+ brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/fleet',
+ }),
+ showPanel: true.valueOf,
+ selectedPanel: PANEL_ID,
+});
diff --git a/x-pack/plugins/fleet/storybook/preview.tsx b/x-pack/plugins/fleet/storybook/preview.tsx
new file mode 100644
index 000000000000..a50ff2faaff5
--- /dev/null
+++ b/x-pack/plugins/fleet/storybook/preview.tsx
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { addDecorator } from '@storybook/react';
+import { Title, Subtitle, Description, Primary, Stories } from '@storybook/addon-docs/blocks';
+
+import { contextDecorator } from './decorator';
+
+addDecorator(contextDecorator);
+
+export const parameters = {
+ docs: {
+ page: () => (
+ <>
+
+
+
+
+
+ >
+ ),
+ },
+};
diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json
index 5002bf289387..a9dd66ce503a 100644
--- a/x-pack/plugins/fleet/tsconfig.json
+++ b/x-pack/plugins/fleet/tsconfig.json
@@ -14,6 +14,7 @@
"server/**/*.json",
"scripts/**/*",
"package.json",
+ "storybook/**/*",
"../../../typings/**/*"
],
"references": [