Upgrade to Storybook 8 (#195148)

## Summary

Depends on #191106
Closes #171591

This PR migrates Storybook from `6.x` to `8.x`. Please see the
[migration
guide](https://storybook.js.org/docs/migration-guide/from-older-version)
for an overview of the changes because there are many breaking changes
which effect Kibana. The TODO list below is not inclusive of all the
changes.

## Reviewers
### Each commit contains all files changed for a specific codeowner,
please find your respective commit to make review easier.

A **first step before code review** should be checking the [`Storybooks
Preview`](https://ci-artifacts.kibana.dev/storybooks/pr-195148/index.html)
from CI for any runtime or style issues which were missed. The preview
can be compared to a build from `main`
[here](https://ci-artifacts.kibana.dev/storybooks/pr-212585/index.html).
It is worth noting that some stories have runtime issues which existed
before this migration.

Most stories appear to have been migrated properly, but the Operations
team does not have prior knowledge into every story. Some of the
migration was able to be automated through Storybook provided scripts.
It is possible this wasn't entirely correct due to the structure of some
stories. Additionally, part of this migration is moving Storybook to
Webpack 5 which changed how styles are being loaded.

#### TODO
- [x] Migrate `stories.mdx`
- [x] storyshots
- [x] [Migrate
packages](https://storybook.js.org/docs/migration-guide/from-older-version#package-structure-changes)
which were removed in `8.0`
- [x] `react-doc-gen` resolution
- [x] [Migrate
blocks](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#addon-docs-removed-deprecated-blocksjs-entry)
- [x] Migrate deprecated `addon-knobs` to
[addon-controls](https://www.npmjs.com/package/@storybook/addon-controls)
- [x] React Fast Refresh support
- [x] Watch flag callback
- [x] `canvas` webpack
- [x] Rerun CSF migrations for new stories
- [x] Handle ESM import for `addon-docs`
- [x] `'@storybook/addon-actions' should be listed in the project's
dependencies. Run 'npm i -S @storybook/addon-actions' to add
iteslint[import/no-extraneous-dependencies](https://github.com/import-js/eslint-plugin-import/blob/v2.28.0/docs/rules/no-extraneous-dependencies.md)`
- [x] `addDecorator` migration 
- [x] `addParameter` migration
- [x] static build
- [ ] determine if #176500 is solved or push to followup PR
  - This will need to be fixed separately
- [x] revert `.buildkite/pipelines/pull_request/base.yml` &
`.buildkite/scripts/pipelines/pull_request/pipeline.ts` to `main`

---------

Co-authored-by: Tiago Costa <tiago.costa@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Jacek Kolezynski <jacek.kolezynski@elastic.co>
Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
Co-authored-by: Clint Andrew Hall <clint@clintandrewhall.com>
This commit is contained in:
Brad White 2025-03-14 16:41:03 -06:00 committed by GitHub
parent eb9e817378
commit 403b5f2363
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
518 changed files with 12646 additions and 13425 deletions

View file

@ -73,4 +73,4 @@ RUN echo "source ${KBN_DIR}/.devcontainer/scripts/env.sh" >> ${HOME}/.bashrc &&
echo "source ${KBN_DIR}/.devcontainer/scripts/env.sh" >> ${HOME}/.zshrc
# This is for documentation. Ports are exposed via devcontainer.json
EXPOSE 9200 5601 9229 9230 9231
EXPOSE 9200 5601 9229 9230 9231 9001

View file

@ -24,7 +24,8 @@
5601,
9229,
9230,
9231
9231,
9001
],
"postStartCommand": "${containerWorkspaceFolder}/.devcontainer/scripts/post_start.sh",
"remoteUser": "vscode",

1
.github/CODEOWNERS vendored
View file

@ -1616,6 +1616,7 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql
/.devcontainer/ @elastic/kibana-operations
/.eslintrc.js @elastic/kibana-operations
/.eslintignore @elastic/kibana-operations
/.ci/.storybook @elastic/kibana-operations
# QA - Appex QA
/x-pack/test/.gitignore @elastic/appex-qa

View file

@ -60,7 +60,7 @@
"serverless-security": "node scripts/kibana --dev --serverless=security",
"spec_to_console": "node scripts/spec_to_console",
"start": "node scripts/kibana --dev",
"storybook": "node --no-deprecation scripts/storybook",
"storybook": "node scripts/storybook",
"test:ftr": "node scripts/functional_tests",
"test:ftr:runner": "node scripts/functional_test_runner",
"test:ftr:server": "node scripts/functional_tests_server",
@ -77,6 +77,7 @@
"yarn": "^1.22.19"
},
"resolutions": {
"**/@babel/parser": "7.24.7",
"**/@bazel/typescript/protobufjs": "6.11.4",
"**/@hello-pangea/dnd": "16.6.0",
"**/@langchain/core": "^0.3.40",
@ -92,6 +93,7 @@
"**/remark-parse/trim": "1.0.1",
"**/sharp": "0.32.6",
"**/typescript": "5.1.6",
"**/util": "^0.11.1",
"@aws-sdk/client-bedrock-agent-runtime": "^3.744.0",
"@aws-sdk/client-bedrock-runtime": "^3.744.0",
"@aws-sdk/client-kendra": "3.744.0",
@ -1545,28 +1547,26 @@
"@octokit/rest": "^21.1.1",
"@parcel/watcher": "^2.1.0",
"@playwright/test": "1.49.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@redocly/cli": "^1.33.0",
"@statoscope/webpack-plugin": "^5.28.2",
"@storybook/addon-a11y": "^6.5.16",
"@storybook/addon-actions": "^6.5.16",
"@storybook/addon-docs": "^6.5.16",
"@storybook/addon-essentials": "^6.5.16",
"@storybook/addon-knobs": "^6.4.0",
"@storybook/addon-storyshots": "^6.5.16",
"@storybook/addons": "^6.5.16",
"@storybook/api": "^6.5.16",
"@storybook/builder-webpack5": "^6.5.16",
"@storybook/client-api": "^6.5.16",
"@storybook/components": "^6.5.16",
"@storybook/core": "^6.5.16",
"@storybook/core-common": "^6.5.16",
"@storybook/core-events": "^6.5.16",
"@storybook/manager-webpack5": "^6.5.16",
"@storybook/node-logger": "^6.5.16",
"@storybook/preview-web": "^6.5.16",
"@storybook/react": "^6.5.16",
"@storybook/testing-react": "^1.3.0",
"@storybook/theming": "^6.5.16",
"@storybook/addon-a11y": "^8.6.3",
"@storybook/addon-actions": "^8.6.3",
"@storybook/addon-essentials": "^8.6.3",
"@storybook/addon-styling-webpack": "^1.0.1",
"@storybook/addon-webpack5-compiler-babel": "^3.0.5",
"@storybook/blocks": "^8.6.3",
"@storybook/components": "^8.6.3",
"@storybook/core-events": "^8.6.3",
"@storybook/core-server": "^8.6.3",
"@storybook/icons": "^1.3.2",
"@storybook/manager-api": "^8.6.3",
"@storybook/preview-api": "^8.6.3",
"@storybook/react": "^8.6.3",
"@storybook/react-webpack5": "^8.6.3",
"@storybook/test": "^8.6.3",
"@storybook/theming": "^8.6.3",
"@storybook/types": "^8.6.3",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
@ -1654,6 +1654,7 @@
"@types/picomatch": "^2.3.0",
"@types/pixelmatch": "^5.2.4",
"@types/pngjs": "^6.0.5",
"@types/prettier": "^2.7.3",
"@types/prop-types": "^15.7.5",
"@types/rbush": "^3.0.0",
"@types/react": "~18.2.0",
@ -1769,6 +1770,7 @@
"fetch-mock": "^10.1.0",
"file-loader": "^4.2.0",
"find-cypress-specs": "^1.41.4",
"fix-esm": "^1.0.1",
"form-data": "^4.0.2",
"geckodriver": "^5.0.0",
"gulp-brotli": "^3.0.0",
@ -1835,6 +1837,7 @@
"prettier": "^2.8.8",
"proxy": "^2.1.1",
"react-is": "~18.2.0",
"react-refresh": "^0.16.0",
"react-test-renderer": "~18.2.0",
"recast": "^0.23.9",
"regenerate": "^1.4.0",
@ -1848,6 +1851,7 @@
"sinon": "^7.4.2",
"sort-package-json": "^1.53.1",
"source-map": "^0.7.4",
"storybook": "^8.6.3",
"string-replace-loader": "^3.1.0",
"style-loader": "^4.0.0",
"stylelint": "^14.16.1",

View file

@ -44,10 +44,25 @@
},
{
"groupName": "webpack",
"matchDepNames": ["webpack", "@types/webpack", "webpack-cli", "webpack-dev-server", "webpack-merge"],
"reviewers": ["team:kibana-operations"],
"matchBaseBranches": ["main"],
"labels": ["Team:Operations", "backport:all-open", "release_note:skip", "ci:build-webpack-bundle-analyzer"],
"matchDepNames": [
"webpack",
"@types/webpack",
"webpack-cli",
"webpack-dev-server",
"webpack-merge"
],
"reviewers": [
"team:kibana-operations"
],
"matchBaseBranches": [
"main"
],
"labels": [
"Team:Operations",
"backport:all-open",
"release_note:skip",
"ci:build-webpack-bundle-analyzer"
],
"minimumReleaseAge": "60 days",
"enabled": true
},
@ -1419,7 +1434,8 @@
"matchDepNames": [
"prettier",
"eslint-plugin-prettier",
"eslint-config-prettier"
"eslint-config-prettier",
"@types/prettier"
],
"reviewers": [
"team:kibana-operations"
@ -3388,28 +3404,7 @@
"enabled": true
},
{
"groupName": "@storybook",
"reviewers": [
"team:kibana-operations"
],
"matchBaseBranches": [
"main"
],
"matchDepPatterns": [
"^@storybook"
],
"labels": [
"Team:Operations",
"release_note:skip",
"ci:build-storybooks",
"backport:skip"
],
"minimumReleaseAge": "7 days",
"allowedVersions": "<7.0",
"enabled": true
},
{
"groupName": "@storybook/testing-react",
"groupName": "storybook",
"reviewers": [
"team:kibana-operations"
],
@ -3417,16 +3412,20 @@
"main"
],
"matchDepNames": [
"@storybook/testing-react"
"@pmmmwh/react-refresh-webpack-plugin",
"fix-esm",
"react-refresh"
],
"matchDepPatterns": [
"storybook"
],
"labels": [
"Team:Operations",
"release_note:skip",
"ci:build-storybooks",
"backport:skip"
"backport:prev-minor"
],
"minimumReleaseAge": "7 days",
"allowedVersions": "<2.0",
"enabled": true
},
{

View file

@ -71,6 +71,7 @@ export const LICENSE_ALLOWED = [
'Python-2.0',
'(Apache-2.0 AND MIT)',
'BlueOak-1.0.0',
'WTFPL OR CC0-1.0',
];
// The following list only applies to licenses that

View file

@ -7,23 +7,17 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
// Please also add new aliases to .buildkite/scripts/steps/storybooks/build_and_upload.ts
//
// If you wish for your Storybook to be built and included in CI, also add your
// alias to .buildkite/scripts/steps/storybooks/build_and_upload.ts
export const storybookAliases = {
ai_assistant: 'x-pack/platform/packages/shared/kbn-ai-assistant/.storybook',
apm: 'x-pack/solutions/observability/plugins/apm/.storybook',
canvas: 'x-pack/platform/plugins/private/canvas/storybook',
cases: 'src/platform/packages/shared/kbn-cases-components/.storybook',
cell_actions: 'src/platform/packages/shared/kbn-cell-actions/.storybook',
chart_icons: 'src/platform/packages/shared/kbn-chart-icons/.storybook',
cloud_security_posture_graph:
'x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook',
cloud: 'src/platform/packages/shared/cloud/.storybook',
coloring: 'src/platform/packages/shared/kbn-coloring/.storybook',
language_documentation_popover:
'src/platform/packages/private/kbn-language-documentation/.storybook',
chart_icons: 'src/platform/packages/shared/kbn-chart-icons/.storybook',
content_management_examples: 'examples/content_management_examples/.storybook',
custom_icons: 'src/platform/packages/shared/kbn-custom-icons/.storybook',
custom_integrations: 'src/platform/plugins/shared/custom_integrations/storybook',
@ -31,8 +25,10 @@ export const storybookAliases = {
dashboard: 'src/platform/plugins/shared/dashboard/.storybook',
data: 'src/platform/plugins/shared/data/.storybook',
discover: 'src/platform/plugins/shared/discover/.storybook',
esql_ast_inspector: 'examples/esql_ast_inspector/.storybook',
es_ui_shared: 'src/platform/plugins/shared/es_ui_shared/.storybook',
esql_ast_inspector: 'examples/esql_ast_inspector/.storybook',
esql_editor: 'src/platform/packages/private/kbn-esql-editor/.storybook',
event_stacktrace: 'x-pack/platform/packages/shared/kbn-event-stacktrace/.storybook',
expandable_flyout: 'x-pack/solutions/security/packages/expandable-flyout/.storybook',
expression_error: 'src/platform/plugins/shared/expression_error/.storybook',
expression_image: 'src/platform/plugins/shared/expression_image/.storybook',
@ -53,21 +49,22 @@ export const storybookAliases = {
inventory: 'x-pack/solutions/observability/plugins/inventory/.storybook',
investigate: 'x-pack/solutions/observability/plugins/investigate_app/.storybook',
kibana_react: 'src/platform/plugins/shared/kibana_react/.storybook',
language_documentation_popover:
'src/platform/packages/private/kbn-language-documentation/.storybook',
lists: 'x-pack/solutions/security/plugins/lists/.storybook',
management: 'src/platform/packages/shared/kbn-management/storybook/config',
observability: 'x-pack/solutions/observability/plugins/observability/.storybook',
observability_ai_assistant:
'x-pack/platform/plugins/shared/observability_ai_assistant/.storybook',
observability_ai_assistant_app:
'x-pack/solutions/observability/plugins/observability_ai_assistant_app/.storybook',
observability_ai_assistant:
'x-pack/platform/plugins/shared/observability_ai_assistant/.storybook',
observability_inventory: 'x-pack/solutions/observability/plugins/inventory/.storybook',
observability_shared: 'x-pack/solutions/observability/plugins/observability_shared/.storybook',
observability_slo: 'x-pack/solutions/observability/plugins/slo/.storybook',
observability: 'x-pack/solutions/observability/plugins/observability/.storybook',
presentation: 'src/platform/plugins/shared/presentation_util/storybook',
profiling: 'x-pack/solutions/observability/plugins/profiling/.storybook',
random_sampling: 'x-pack/platform/packages/private/kbn-random-sampling/.storybook',
esql_editor: 'src/platform/packages/private/kbn-esql-editor/.storybook',
// Skipped, please check and fix https://github.com/elastic/kibana/issues/207227
// security_solution: 'x-pack/solutions/security/plugins/security_solution/.storybook',
security_solution: 'x-pack/solutions/security/plugins/security_solution/.storybook',
// security_solution_packages: 'x-pack/solutions/security/packages/storybook/config',
serverless: 'src/platform/packages/shared/serverless/storybook/config',
shared_ux: 'src/platform/packages/private/shared-ux/storybook/config',
@ -76,6 +73,4 @@ export const storybookAliases = {
ui_actions_enhanced: 'src/platform/plugins/shared/ui_actions_enhanced/.storybook',
unified_search: 'src/platform/plugins/shared/unified_search/.storybook',
unified_tabs: 'src/platform/packages/shared/kbn-unified-tabs/.storybook',
profiling: 'x-pack/solutions/observability/plugins/profiling/.storybook',
event_stacktrace: 'x-pack/platform/packages/shared/kbn-event-stacktrace/.storybook',
};

View file

@ -41,7 +41,6 @@ run(
log.verbose('Loading Storybook:', configDir);
// TODO: once storybook is upgraded into a newer version, --no-deprecation flag could be removed when invoking it through the package.json script
runStorybookCli({ configDir, name: alias });
},
{

View file

@ -11,7 +11,6 @@ const defaultConfig = require('@kbn/storybook').defaultConfig;
module.exports = {
...defaultConfig,
stories: ['../**/*.stories.+(tsx|mdx)'],
typescript: {
reactDocgen: 'react-docgen-typescript',
},

View file

@ -0,0 +1,20 @@
import { Canvas, Meta, Story, Controls } from '@storybook/blocks';
import * as EsqlEditorStories from './esql_editor.stories';
<Meta of={EsqlEditorStories} />
# Overview
The ESQLEditor component is a reusable component and can be used to support text based languages in your application (SQL, ESQL):
<Canvas of={EsqlEditorStories.ExpandedMode} />
When there are errors to the query the UI displays the errors to the editor:
<Canvas of={EsqlEditorStories.WithErrors} />
## Component props
The component exposes the following properties:
<Controls />../esql\_editor

View file

@ -1,74 +0,0 @@
import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs/blocks';
import { I18nProvider } from '@kbn/i18n-react';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { ESQLEditor } from '../esql_editor';
export const Template = (args) =>
<I18nProvider>
<KibanaContextProvider
services={{
settings: { client: { get: () => {} } },
uiSettings: { get: () => {} },
}}
>
<ESQLEditor {...args} />
</KibanaContextProvider>
</I18nProvider>;
<Meta
title="Text based languages editor"
component={ESQLEditor}
/>
# Overview
The ESQLEditor component is a reusable component and can be used to support text based languages in your application (SQL, ESQL):
<Canvas>
<Story
name='expanded mode'
args={
{
query: { esql: 'from dataview | keep field1, field2' },
'data-test-subj':'test-id'
}
}
argTypes={
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }}
}
>
{Template.bind({})}
</Story>
</Canvas>
When there are errors to the query the UI displays the errors to the editor:
<Canvas>
<Story
name='with errors'
args={
{
query: { esql: 'from dataview | keep field1, field2' },
'data-test-subj':'test-id',
errors: [
new Error(
'[essql] > Unexpected error from Elasticsearch: verification_exception - Found 1 problem line 1:16: Unknown column [field10]'
),
]
}
}
argTypes={
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }}
}
>
{Template.bind({})}
</Story>
</Canvas>
## Component props
The component exposes the following properties:
<ArgsTable story="expanded mode"/>../esql_editor

View file

@ -0,0 +1,81 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React from 'react';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { StoryObj } from '@storybook/react';
import { ESQLEditor } from '../esql_editor';
import type { ESQLEditorProps } from '../types';
const Template = (args: ESQLEditorProps) => (
<KibanaContextProvider
services={{
settings: { client: { get: () => {} } },
uiSettings: { get: () => {} },
data: { query: { timefilter: { timefilter: { getTime: () => {} } } } },
}}
>
<ESQLEditor {...args} />
</KibanaContextProvider>
);
export default {
title: 'Text based languages editor',
component: ESQLEditor,
};
export const ExpandedMode: StoryObj<typeof ESQLEditor> = {
render: Template,
name: 'expanded mode',
args: {
query: {
esql: 'from dataview | keep field1, field2',
},
},
argTypes: {
onTextLangQueryChange: {
action: 'changed',
},
onTextLangQuerySubmit: {
action: 'submitted',
},
},
};
export const WithErrors: StoryObj<typeof ESQLEditor> = {
render: Template,
name: 'with errors',
args: {
query: {
esql: 'from dataview | keep field1, field2',
},
dataTestSubj: 'test-id',
errors: [
new Error(
'[essql] > Unexpected error from Elasticsearch: verification_exception - Found 1 problem line 1:16: Unknown column [field10]'
),
],
},
argTypes: {
onTextLangQueryChange: {
action: 'changed',
},
onTextLangQuerySubmit: {
action: 'submitted',
},
},
};

View file

@ -8,7 +8,6 @@
*/
import React from 'react';
import { storiesOf } from '@storybook/react';
import { LanguageDocumentationPopover } from '../components/as_popover';
const sections = {
@ -62,12 +61,20 @@ const sections = {
),
};
storiesOf('Language documentation popover', module).add('default', () => (
<LanguageDocumentationPopover
language="Test"
sections={sections}
buttonProps={{ color: 'text' }}
isHelpMenuOpen={true}
onHelpMenuVisibilityChange={() => {}}
/>
));
export default {
title: 'Language documentation popover',
};
export const Default = {
render: () => (
<LanguageDocumentationPopover
language="Test"
sections={sections}
buttonProps={{ color: 'text' }}
isHelpMenuOpen={true}
onHelpMenuVisibilityChange={() => {}}
/>
),
name: 'default',
};

View file

@ -27,12 +27,14 @@ export default {
},
};
export const Analytics = (params: AnalyticsNoDataPageStorybookParams) => {
return (
<AnalyticsNoDataPageProvider {...mock.getProps(params)} {...mock.getServices(params)}>
<Component {...mock.getProps(params)} />
</AnalyticsNoDataPageProvider>
);
};
export const Analytics = {
render: (params: AnalyticsNoDataPageStorybookParams) => {
return (
<AnalyticsNoDataPageProvider {...mock.getServices(params)}>
<Component {...mock.getProps()} />
</AnalyticsNoDataPageProvider>
);
},
Analytics.argTypes = mock.getArgumentTypes();
argTypes: mock.getArgumentTypes(),
};

View file

@ -38,18 +38,18 @@ export class StorybookMock extends AbstractStorybookMock<
propArguments = {
// requires hasESData to be toggled to true
useCustomOnTryESQL: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
};
serviceArguments = {
kibanaGuideDocLink: {
control: 'text',
control: { control: 'text' },
defaultValue: 'Kibana guide',
},
customBranding: {
hasCustomBranding$: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
},
@ -70,10 +70,10 @@ export class StorybookMock extends AbstractStorybookMock<
};
}
getProps(params: Params) {
getProps(params?: Params) {
return {
onDataViewCreated: action('onDataViewCreated'),
onTryESQL: params.useCustomOnTryESQL ? action('onTryESQL-from-props') : undefined,
onTryESQL: params?.useCustomOnTryESQL ? action('onTryESQL-from-props') : undefined,
};
}
}

View file

@ -29,30 +29,34 @@ export default {
const mock = new KibanaNoDataPageStorybookMock();
export const Kibana = (params: KibanaNoDataPageStorybookParams) => {
return (
<KibanaNoDataPageProvider {...mock.getServices(params)}>
<Component {...mock.getProps(params)} />
</KibanaNoDataPageProvider>
);
export const Kibana = {
render: (params: KibanaNoDataPageStorybookParams) => {
return (
<KibanaNoDataPageProvider {...mock.getServices(params)}>
<Component {...mock.getProps(params)} />
</KibanaNoDataPageProvider>
);
},
argTypes: mock.getArgumentTypes(),
};
Kibana.argTypes = mock.getArgumentTypes();
export const LoadingState = {
render: (params: KibanaNoDataPageStorybookParams) => {
// Simulate loading with a Promise that doesn't resolve.
const dataCheck = () => new Promise<boolean>((resolve, reject) => {});
export const LoadingState = (params: KibanaNoDataPageStorybookParams) => {
// Simulate loading with a Promise that doesn't resolve.
const dataCheck = () => new Promise<boolean>((resolve, reject) => {});
const services = {
...mock.getServices(params),
hasESData: dataCheck,
hasUserDataView: dataCheck,
hasDataView: dataCheck,
};
const services = {
...mock.getServices(params),
hasESData: dataCheck,
hasUserDataView: dataCheck,
hasDataView: dataCheck,
};
return (
<KibanaNoDataPageProvider {...services}>
<Component {...mock.getProps(params)} />
</KibanaNoDataPageProvider>
);
return (
<KibanaNoDataPageProvider {...services}>
<Component {...mock.getProps(params)} />
</KibanaNoDataPageProvider>
);
},
};

View file

@ -44,11 +44,11 @@ export class StorybookMock extends AbstractStorybookMock<
> {
propArguments = {
solution: {
control: 'text',
control: { control: 'text' },
defaultValue: 'Observability',
},
logo: {
control: { type: 'radio' },
control: { control: 'radio' },
options: ['logoElastic', 'logoKibana', 'logoCloud', undefined],
defaultValue: undefined,
},
@ -56,11 +56,11 @@ export class StorybookMock extends AbstractStorybookMock<
serviceArguments = {
hasESData: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
hasUserDataView: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
};

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { addons } from '@storybook/addons';
import { addons } from '@storybook/manager-api';
import { create } from '@storybook/theming';
import { PANEL_ID as selectedPanel } from '@storybook/addon-actions';

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import type { SampleDataSet } from '@kbn/home-sample-data-types';
@ -27,24 +27,26 @@ export default {
},
},
decorators: [(Story) => <div style={{ width: '433px', padding: '25px' }}>{Story()}</div>],
} as ComponentMeta<typeof Component>;
} as Meta<typeof Component>;
const { description, ...argTypes } = getStoryArgTypes();
export const CardFooter = (params: Params) => {
const { includeAppLinks, status, ...rest } = params;
const sampleDataSet: SampleDataSet = {
...mockDataSet,
...rest,
status,
appLinks: includeAppLinks ? mockDataSet.appLinks : [],
};
export const CardFooter = {
render: (params: Params) => {
const { includeAppLinks, status, ...rest } = params;
const sampleDataSet: SampleDataSet = {
...mockDataSet,
...rest,
status,
appLinks: includeAppLinks ? mockDataSet.appLinks : [],
};
return (
<SampleDataCardProvider {...getStoryServices(params)}>
<Component sampleDataSet={sampleDataSet} onAction={action('onAction')} />
</SampleDataCardProvider>
);
return (
<SampleDataCardProvider {...getStoryServices(params)}>
<Component sampleDataSet={sampleDataSet} onAction={action('onAction')} />
</SampleDataCardProvider>
);
},
argTypes,
};
CardFooter.argTypes = argTypes;

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import type { SampleDataSet } from '@kbn/home-sample-data-types';
@ -28,23 +28,25 @@ export default {
},
},
decorators: [(Story) => <div style={{ width: '433px', padding: '25px' }}>{Story()}</div>],
} as ComponentMeta<typeof SampleDataCard>;
} as Meta<typeof SampleDataCard>;
const argTypes = getStoryArgTypes();
export const Card = (params: Params) => {
const { includeAppLinks, ...rest } = params;
const sampleDataSet: SampleDataSet = {
...mockDataSet,
...rest,
appLinks: includeAppLinks ? mockDataSet.appLinks : [],
};
export const Card = {
render: (params: Params) => {
const { includeAppLinks, ...rest } = params;
const sampleDataSet: SampleDataSet = {
...mockDataSet,
...rest,
appLinks: includeAppLinks ? mockDataSet.appLinks : [],
};
return (
<SampleDataCardProvider {...getStoryServices(params)}>
<SampleDataCard sampleDataSet={sampleDataSet} onStatusChange={action('onStatusChange')} />
</SampleDataCardProvider>
);
return (
<SampleDataCardProvider {...getStoryServices(params)}>
<SampleDataCard sampleDataSet={sampleDataSet} onStatusChange={action('onStatusChange')} />
</SampleDataCardProvider>
);
},
argTypes,
};
Card.argTypes = argTypes;

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Meta } from '@storybook/react';
import { DemoEnvironmentPanel } from './demo_env_panel';
@ -23,6 +23,6 @@ export default {
},
},
decorators: [(Story) => <div style={{ width: 1200 }}>{Story()}</div>],
} as ComponentMeta<typeof DemoEnvironmentPanel>;
} as Meta<typeof DemoEnvironmentPanel>;
export const DemoPanel = () => <DemoEnvironmentPanel demoUrl="https://google.com" />;

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Meta } from '@storybook/react';
import { SampleDataTab } from './sample_data_tab';
@ -25,12 +25,14 @@ export default {
},
},
decorators: [(Story) => <div style={{ width: 1200 }}>{Story()}</div>],
} as ComponentMeta<typeof SampleDataTab>;
} as Meta<typeof SampleDataTab>;
export const TabContent = (params: Params) => (
<SampleDataTabProvider {...getStoryServices(params)}>
<SampleDataTab />
</SampleDataTabProvider>
);
export const TabContent = {
render: (params: Params) => (
<SampleDataTabProvider {...getStoryServices(params)}>
<SampleDataTab />
</SampleDataTabProvider>
),
TabContent.argTypes = getStoryArgTypes();
argTypes: getStoryArgTypes(),
};

View file

@ -7,9 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
// Storybook react doesn't declare this in its typings, but it's there.
declare module '@storybook/react/standalone';
// Storybook uses this module and its types are defined in the source but not in the type output
declare module 'file-system-cache' {
interface Options {

View file

@ -9,7 +9,7 @@
import React from 'react';
import { I18nProvider } from '@kbn/i18n-react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { StoryObj, Meta } from '@storybook/react';
import { CaseStatuses } from '../status/types';
import { Tooltip } from '../tooltip/tooltip';
@ -59,46 +59,72 @@ const Template = (args: CaseTooltipProps) => (
export default {
title: 'CaseTooltip',
component: Template,
} as ComponentMeta<typeof Template>;
} as Meta<typeof Template>;
export const Default: ComponentStory<typeof Template> = Template.bind({});
Default.args = { ...tooltipProps };
export const LoadingState: ComponentStory<typeof Template> = Template.bind({});
LoadingState.args = { ...tooltipProps, loading: true };
export const LongTitle: ComponentStory<typeof Template> = Template.bind({});
LongTitle.args = { ...tooltipProps, content: { ...tooltipContent, title: longTitle } };
export const LongDescription: ComponentStory<typeof Template> = Template.bind({});
LongDescription.args = {
...tooltipProps,
content: { ...tooltipContent, description: longDescription },
export const Default: StoryObj<typeof Template> = {
render: Template,
args: { ...tooltipProps },
};
export const InProgressStatus: ComponentStory<typeof Template> = Template.bind({});
InProgressStatus.args = {
...tooltipProps,
content: { ...tooltipContent, status: CaseStatuses['in-progress'] },
export const LoadingState: StoryObj<typeof Template> = {
render: Template,
args: { ...tooltipProps, loading: true },
};
export const ClosedStatus: ComponentStory<typeof Template> = Template.bind({});
ClosedStatus.args = {
...tooltipProps,
content: { ...tooltipContent, status: CaseStatuses.closed },
export const LongTitle: StoryObj<typeof Template> = {
render: Template,
args: { ...tooltipProps, content: { ...tooltipContent, title: longTitle } },
};
export const NoUserInfo: ComponentStory<typeof Template> = Template.bind({});
NoUserInfo.args = { ...tooltipProps, content: { ...tooltipContent, createdBy: {} } };
export const LongDescription: StoryObj<typeof Template> = {
render: Template,
export const FullName: ComponentStory<typeof Template> = Template.bind({});
FullName.args = {
...tooltipProps,
content: { ...tooltipContent, createdBy: { fullName: 'Elastic User' } },
args: {
...tooltipProps,
content: { ...tooltipContent, description: longDescription },
},
};
export const LongUserName: ComponentStory<typeof Template> = Template.bind({});
LongUserName.args = {
...tooltipProps,
content: { ...tooltipContent, createdBy: { fullName: 'LoremIpsumElasticUser WithALongSurname' } },
export const InProgressStatus: StoryObj<typeof Template> = {
render: Template,
args: {
...tooltipProps,
content: { ...tooltipContent, status: CaseStatuses['in-progress'] },
},
};
export const ClosedStatus: StoryObj<typeof Template> = {
render: Template,
args: {
...tooltipProps,
content: { ...tooltipContent, status: CaseStatuses.closed },
},
};
export const NoUserInfo: StoryObj<typeof Template> = {
render: Template,
args: { ...tooltipProps, content: { ...tooltipContent, createdBy: {} } },
};
export const FullName: StoryObj<typeof Template> = {
render: Template,
args: {
...tooltipProps,
content: { ...tooltipContent, createdBy: { fullName: 'Elastic User' } },
},
};
export const LongUserName: StoryObj<typeof Template> = {
render: Template,
args: {
...tooltipProps,
content: {
...tooltipContent,
createdBy: { fullName: 'LoremIpsumElasticUser WithALongSurname' },
},
},
};

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import type { ComponentStory } from '@storybook/react';
import type { StoryFn } from '@storybook/react';
import type { FieldSpec } from '@kbn/data-views-plugin/common';
import { CellActionsProvider } from '../context/cell_actions_context';
import { makeAction } from '../mocks/helpers';
@ -50,33 +50,35 @@ export default {
],
};
const CellActionsTemplate: ComponentStory<React.FC<CellActionsProps>> = (args) => (
const CellActionsTemplate: StoryFn<React.FC<CellActionsProps>> = (args) => (
<CellActions {...args}>{'Field value'}</CellActions>
);
export const DefaultWithControls = CellActionsTemplate.bind({});
export const DefaultWithControls = {
render: CellActionsTemplate,
DefaultWithControls.argTypes = {
mode: {
options: [CellActionsMode.HOVER_DOWN, CellActionsMode.INLINE],
defaultValue: CellActionsMode.HOVER_DOWN,
control: {
type: 'radio',
argTypes: {
mode: {
options: [CellActionsMode.HOVER_DOWN, CellActionsMode.INLINE],
defaultValue: CellActionsMode.HOVER_DOWN,
control: {
type: 'radio',
},
},
},
};
DefaultWithControls.args = {
showActionTooltips: true,
mode: CellActionsMode.INLINE,
triggerId: TRIGGER_ID,
data: [
{
field: FIELD,
value: '',
},
],
visibleCellActions: 3,
args: {
showActionTooltips: true,
mode: CellActionsMode.INLINE,
triggerId: TRIGGER_ID,
data: [
{
field: FIELD,
value: '',
},
],
visibleCellActions: 3,
},
};
export const CellActionInline = () => (

View file

@ -9,7 +9,7 @@
import React, { FC, ComponentType } from 'react';
import { EuiFlexItem, EuiFlexGroup, EuiEmptyPrompt, EuiForm, IconType } from '@elastic/eui';
import { ComponentStory } from '@storybook/react';
import type { StoryFn } from '@storybook/react';
import {
IconCircle,
@ -224,10 +224,12 @@ function RootComponent(props: RootComponentProps) {
);
}
const Template: ComponentStory<FC<RootComponentProps>> = (args) => <RootComponent {...args} />;
const Template: StoryFn<FC<RootComponentProps>> = (args) => <RootComponent {...args} />;
export const Default = Template.bind({});
export const Default = {
render: Template,
Default.args = {
icons: IconsArray,
args: {
icons: IconsArray,
},
};

View file

@ -10,7 +10,7 @@
import React, { FC, useState } from 'react';
import { getKbnPalettes } from '@kbn/palettes';
import { EuiFlyout, EuiForm, EuiPage, isColorDark } from '@elastic/eui';
import { ComponentStory } from '@storybook/react';
import type { StoryFn } from '@storybook/react';
import { css } from '@emotion/react';
import { CategoricalColorMapping, ColorMappingProps } from '../categorical_color_mapping';
import { DEFAULT_COLOR_MAPPING_CONFIG } from '../config/default_color_mapping';
@ -25,7 +25,7 @@ export default {
decorators: [(story: Function) => story()],
};
const Template: ComponentStory<FC<ColorMappingProps>> = (args) => {
const Template: StoryFn<FC<ColorMappingProps>> = (args) => {
const [updatedModel, setUpdateModel] = useState<ColorMapping.Config>(
DEFAULT_COLOR_MAPPING_CONFIG
);
@ -76,50 +76,53 @@ const Template: ComponentStory<FC<ColorMappingProps>> = (args) => {
</EuiPage>
);
};
export const Default = Template.bind({});
Default.args = {
model: {
...DEFAULT_COLOR_MAPPING_CONFIG,
paletteId: 'eui_amsterdam',
export const Default = {
render: Template,
colorMode: {
type: 'categorical',
},
specialAssignments: [
{
rule: {
type: 'other',
},
color: {
type: 'loop',
},
touched: false,
args: {
model: {
...DEFAULT_COLOR_MAPPING_CONFIG,
paletteId: 'eui_amsterdam',
colorMode: {
type: 'categorical',
},
],
assignments: [],
},
isDarkMode: false,
data: {
type: 'categories',
categories: [
'US',
'Mexico',
'Brasil',
'Canada',
'Italy',
'Germany',
'France',
'Spain',
'UK',
'Portugal',
'Greece',
'Sweden',
'Finland',
],
},
specialAssignments: [
{
rule: {
type: 'other',
},
color: {
type: 'loop',
},
touched: false,
},
],
assignments: [],
},
isDarkMode: false,
data: {
type: 'categories',
categories: [
'US',
'Mexico',
'Brasil',
'Canada',
'Italy',
'Germany',
'France',
'Spain',
'UK',
'Portugal',
'Greece',
'Sweden',
'Finland',
],
},
specialTokens: new Map(),
// eslint-disable-next-line no-console
onModelUpdate: (model) => console.log(model),
specialTokens: new Map(),
// eslint-disable-next-line no-console
onModelUpdate: (model: any) => console.log(model),
},
};

View file

@ -7,10 +7,9 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React, { FC, PropsWithChildren } from 'react';
import React from 'react';
import { EuiForm } from '@elastic/eui';
import { ComponentStory } from '@storybook/react';
import { CustomizablePalette, CustomizablePaletteProps } from '../palette_configuration';
import { CustomizablePalette } from '../palette_configuration';
import { getPaletteRegistry } from './palettes';
export default {
@ -19,29 +18,25 @@ export default {
decorators: [(story: Function) => <EuiForm>{story()}</EuiForm>],
};
const Template: ComponentStory<FC<PropsWithChildren<CustomizablePaletteProps>>> = (args) => (
<CustomizablePalette {...args} />
);
export const Default = Template.bind({});
Default.args = {
palettes: getPaletteRegistry(),
activePalette: {
type: 'palette',
name: 'default',
params: {
steps: 1,
maxSteps: 10,
continuity: 'none',
export const Default = {
args: {
palettes: getPaletteRegistry(),
activePalette: {
type: 'palette',
name: 'default',
params: {
steps: 1,
maxSteps: 10,
continuity: 'none',
},
},
dataBounds: {
min: 0,
max: 100,
},
showExtraActions: true,
showRangeTypeSelector: true,
disableSwitchingContinuity: false,
setPalette: () => {},
},
dataBounds: {
min: 0,
max: 100,
},
showExtraActions: true,
showRangeTypeSelector: true,
disableSwitchingContinuity: false,
setPalette: () => {},
};

View file

@ -8,7 +8,7 @@
*/
import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiImage, EuiToolTip } from '@elastic/eui';
import type { Story } from '@storybook/react';
import type { StoryFn } from '@storybook/react';
import React from 'react';
import { AGENT_NAMES } from '@kbn/elastic-agent-utils';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
@ -20,7 +20,7 @@ export default {
component: AgentIcon,
};
export const List: Story = () => {
export const List: StoryFn = () => {
return (
<EuiThemeProvider darkMode={false}>
<EuiFlexGroup gutterSize="l" wrap={true}>

View file

@ -8,7 +8,7 @@
*/
import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
import type { Story } from '@storybook/react';
import type { StoryFn } from '@storybook/react';
import React from 'react';
import { CloudProviderIcon } from '.';
import { CloudProvider } from './get_cloud_provider_icon';
@ -20,7 +20,7 @@ export default {
const providers: CloudProvider[] = ['gcp', 'aws', 'azure', 'unknownProvider'];
export const List: Story = () => {
export const List: StoryFn = () => {
return (
<EuiFlexGroup gutterSize="l" wrap={true}>
{providers.map((cloudProvider) => {

View file

@ -61,7 +61,7 @@ export const esHitsMockWithSort = esHitsMock.map((hit) => ({
}));
const baseDate = new Date('2024-01-1').getTime();
const dateInc = 100_000_000;
const dateInc = 100000000;
const generateFieldValue = (field: DataViewField, index: number) => {
switch (field.type) {

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import type { Story } from '@storybook/react';
import type { StoryFn } from '@storybook/react';
import { mockGroupingProps } from './grouping.mock';
import { Grouping } from './grouping';
import readme from '../../README.mdx';
@ -24,6 +24,6 @@ export default {
},
};
export const Empty: Story<void> = () => {
export const Empty: StoryFn = () => {
return <Grouping {...mockGroupingProps} />;
};

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import type { ComponentMeta, Story } from '@storybook/react';
import type { StoryFn, Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { Subscription } from 'rxjs';
@ -29,7 +29,7 @@ export default {
default: 'ghost',
},
},
} as ComponentMeta<typeof Component>;
} as Meta<typeof Component>;
/**
* Props for a {@link SettinggApplication} Storybook story.
@ -69,12 +69,12 @@ const getSettingsApplicationStory = ({ hasGlobalSettings }: StoryProps) => (
</SettingsApplicationProvider>
);
export const SettingsApplicationWithGlobalSettings: Story = () =>
export const SettingsApplicationWithGlobalSettings: StoryFn = () =>
getSettingsApplicationStory({
hasGlobalSettings: true,
});
export const SettingsApplicationWithoutGlobal: Story = () =>
export const SettingsApplicationWithoutGlobal: StoryFn = () =>
getSettingsApplicationStory({
hasGlobalSettings: false,
});

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import type { ComponentMeta, Story } from '@storybook/react';
import type { StoryObj, Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { FieldCategories as Component } from '../categories';
import { Params, useCategoryStory } from './use_category_story';
@ -38,9 +38,9 @@ export default {
default: 'ghost',
},
},
} as ComponentMeta<typeof Component>;
} as Meta<typeof Component>;
export const Categories: Story<Params> = (params) => {
const CategoriesComponent = (params: Params) => {
const {
onClearQuery,
isSavingEnabled,
@ -75,3 +75,7 @@ export const Categories: Story<Params> = (params) => {
</FieldCategoryProvider>
);
};
export const Categories: StoryObj<Params> = {
render: (params) => <CategoriesComponent {...params} />,
};

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import type { ComponentMeta } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock';
@ -51,11 +51,11 @@ export default {
},
},
},
} as ComponentMeta<typeof Component>;
} as Meta<typeof Component>;
type FieldCategoryParams = Pick<ComponentProps, 'category'> & Params;
export const Category = ({ isFiltered, category, isSavingEnabled }: FieldCategoryParams) => {
const CategoryComponent = ({ isFiltered, category, isSavingEnabled }: FieldCategoryParams) => {
const { onClearQuery, onFieldChange, unsavedChanges } = useCategoryStory({
isFiltered,
isSavingEnabled,
@ -89,3 +89,7 @@ export const Category = ({ isFiltered, category, isSavingEnabled }: FieldCategor
</FieldCategoryProvider>
);
};
export const Category: StoryObj<FieldCategoryParams> = {
render: (params) => <CategoryComponent {...params} />,
};

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { useArgs } from '@storybook/client-api';
import { useArgs } from '@storybook/preview-api';
import { action } from '@storybook/addon-actions';
import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock';

View file

@ -9,5 +9,7 @@
import { getInputStory, getStory } from './common';
export default getStory('Array Input', 'An input with an array value.');
const Story = getStory('Array Input', 'An input with an array value.');
export const ArrayInput = getInputStory('array' as const);
export default { ...Story };

View file

@ -9,5 +9,9 @@
import { getInputStory, getStory } from './common';
export default getStory('Boolean Input', 'An input with a boolean value.');
const Story = getStory('Boolean Input', 'An input with a boolean value.');
export const BooleanInput = getInputStory('boolean' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getInputStory, getStory } from './common';
export default getStory('Color Input', 'An input with a color value.');
const Story = getStory('Color Input', 'An input with a color value.');
export const ColorInput = getInputStory('color' as const);
export default {
...Story,
};

View file

@ -8,7 +8,7 @@
*/
import React, { useState } from 'react';
import type { ComponentMeta } from '@storybook/react';
import type { Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { EuiPanel } from '@elastic/eui';
@ -23,7 +23,7 @@ import {
import { getFieldDefinition } from '@kbn/management-settings-field-definition';
import { getDefaultValue, getUserValue } from '@kbn/management-settings-utilities/storybook';
import { FieldInputProvider } from '../services';
import { FieldInput as Component, FieldInput } from '../field_input';
import { FieldInput } from '../field_input';
import { InputProps } from '../types';
/**
@ -92,7 +92,7 @@ export const getStory = (title: string, description: string) =>
</FieldInputProvider>
),
],
} as ComponentMeta<typeof Component>);
} as Meta<typeof FieldInput>);
/**
* Utility function for returning a {@link FieldInput} Storybook story.

View file

@ -9,5 +9,9 @@
import { getInputStory, getStory } from './common';
export default getStory('Image Input', 'An input with an image value.');
const Story = getStory('Image Input', 'An input with an image value.');
export const ImageInput = getInputStory('image' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getInputStory, getStory } from './common';
export default getStory('JSON Input', 'An input with a JSON value.');
const Story = getStory('JSON Input', 'An input with a JSON value.');
export const JSONInput = getInputStory('json' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getInputStory, getStory } from './common';
export default getStory('Markdown Input', 'An input with a markdown value.');
const Story = getStory('Markdown Input', 'An input with a markdown value.');
export const MarkdownInput = getInputStory('markdown' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getInputStory, getStory } from './common';
export default getStory('Number Input', 'An input with a number value.');
const Story = getStory('Number Input', 'An input with a number value.');
export const NumberInput = getInputStory('number' as const);
export default {
...Story,
};

View file

@ -31,7 +31,7 @@ const settingFields = {
options: ['option1', 'option2', 'option3'],
};
export default getStory('Select Input', 'An input with multiple values.');
const Story = getStory('Select Input', 'An input with multiple values.');
export const SelectInput = getInputStory('select' as const, { argTypes, settingFields });
SelectInput.args = {
@ -39,3 +39,7 @@ SelectInput.args = {
value: 'option1',
userValue: 'option2',
};
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getInputStory, getStory } from './common';
export default getStory('String Input', 'An input with a string value.');
const Story = getStory('String Input', 'An input with a string value.');
export const StringInput = getInputStory('string' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getFieldRowStory, getStory } from './common';
export default getStory('Array Row', 'A setting with an array of values.');
const Story = getStory('Array Row', 'A setting with an array of values.');
export const ArrayRow = getFieldRowStory('array' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getStory, getFieldRowStory } from './common';
export default getStory('Boolean Row', 'A setting with a boolean value.');
const Story = getStory('Boolean Row', 'A setting with a boolean value.');
export const BooleanRow = getFieldRowStory('boolean' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getFieldRowStory, getStory } from './common';
export default getStory('Color Row', 'A setting with an base64 image value.');
const Story = getStory('Color Row', 'A setting with an base64 image value.');
export const ColorRow = getFieldRowStory('color' as const);
export default {
...Story,
};

View file

@ -8,7 +8,7 @@
*/
import React, { useState } from 'react';
import type { ComponentMeta } from '@storybook/react';
import type { Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { EuiPanel } from '@elastic/eui';
import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types';
@ -93,7 +93,7 @@ export const getStory = (
</FieldRowProvider>
),
],
} as ComponentMeta<typeof Component>);
} as Meta<typeof Component>);
/**
* Default argument values for a {@link FieldInput} Storybook story.

View file

@ -9,5 +9,9 @@
import { getFieldRowStory, getStory } from './common';
export default getStory('Image Row', 'A setting with an base64 image value.');
const Story = getStory('Image Row', 'A setting with an base64 image value.');
export const ImageRow = getFieldRowStory('image' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getFieldRowStory, getStory } from './common';
export default getStory('JSON Row', 'A setting with a JSON value.');
const Story = getStory('JSON Row', 'A setting with a JSON value.');
export const JSONRow = getFieldRowStory('json' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getFieldRowStory, getStory } from './common';
export default getStory('Markdown Row', 'A setting with a Markdown value.');
const Story = getStory('Markdown Row', 'A setting with a Markdown value.');
export const MarkdownRow = getFieldRowStory('markdown' as const);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getFieldRowStory, getStory } from './common';
export default getStory('Number Row', 'A setting with a numeric value.');
const Story = getStory('Number Row', 'A setting with a numeric value.');
export const NumberRow = getFieldRowStory('number' as const);
export default {
...Story,
};

View file

@ -24,5 +24,9 @@ const settingFields = {
options: ['option1', 'option2', 'option3'],
};
export default getStory('Select Row', 'A setting with a boolean value.', argTypes);
const Story = getStory('Select Row', 'A setting with a boolean value.', argTypes);
export const SelectRow = getFieldRowStory('select' as const, settingFields);
export default {
...Story,
};

View file

@ -9,5 +9,9 @@
import { getFieldRowStory, getStory } from './common';
export default getStory('String Row', 'A setting with a string value.');
const Story = getStory('String Row', 'A setting with a string value.');
export const StringRow = getFieldRowStory('string' as const);
export default {
...Story,
};

View file

@ -9,7 +9,7 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import { ComponentMeta } from '@storybook/react';
import { Meta } from '@storybook/react';
import { FieldDefinition } from '@kbn/management-settings-types';
import { getFieldDefinitions } from '@kbn/management-settings-field-definition';
import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock';
@ -20,7 +20,7 @@ import { Form as Component } from '../form';
import { FormProvider } from '../services';
export default {
title: `Settings/Form/Form`,
title: 'Settings/Form/Form',
description: 'A form with field rows',
argTypes: {
isSavingEnabled: {
@ -57,7 +57,7 @@ export default {
default: 'ghost',
},
},
} as ComponentMeta<typeof Component>;
} as Meta<typeof Component>;
interface FormStoryProps {
/** True if saving settings is enabled, false otherwise. */

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { addons } from '@storybook/addons';
import { addons } from '@storybook/manager-api';
import { create } from '@storybook/theming';
import { PANEL_ID as selectedPanel } from '@storybook/addon-actions';

View file

@ -7,13 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
defaultConfig,
defaultConfigWebFinal,
mergeWebpackFinal,
StorybookConfig,
} from './src/lib/default_config';
export { defaultConfig, defaultConfigWebFinal, mergeWebpackFinal };
import { defaultConfig, StorybookConfig } from './src/lib/default_config';
export { defaultConfig };
export type { StorybookConfig };
export { runStorybookCli } from './src/lib/run_storybook_cli';
export { default as WebpackConfig } from './src/webpack.config';

View file

@ -16,7 +16,7 @@ module.exports = {
webpackFinal: (config) => {
return webpackConfig({ config });
},
config: (entry) => {
return [...entry, require.resolve('./src/lib/decorators')];
previewAnnotations: (entry) => {
return [...entry, require.resolve('./src/lib/preview')];
},
};

View file

@ -10,7 +10,7 @@
import { of, Subject } from 'rxjs';
import React, { useEffect } from 'react';
import { action } from '@storybook/addon-actions';
import type { DecoratorFn } from '@storybook/react';
import type { Decorator } from '@storybook/react';
import { I18nProvider } from '@kbn/i18n-react';
import 'core_styles';
@ -40,7 +40,7 @@ const analytics: AnalyticsServiceStart = {
* Storybook decorator using the `KibanaContextProvider`. Uses the value from
* `globals` provided by the Storybook theme switcher to set the `colorMode`.
*/
const KibanaContextDecorator: DecoratorFn = (storyFn, { globals }) => {
const KibanaContextDecorator: Decorator = (storyFn, { globals }) => {
// TODO: Add a switcher to see components in other locales or pseudo locale
i18n.init({ locale: 'en', messages: {} });
const { darkMode, name } = getKibanaTheme(globals.euiTheme);

View file

@ -9,18 +9,28 @@
import * as path from 'path';
import fs from 'fs';
import type { StorybookConfig } from '@storybook/core-common';
import webpack, { Configuration } from 'webpack';
import { merge as webpackMerge } from 'webpack-merge';
import type { StorybookConfig as BaseStorybookConfig } from '@storybook/react-webpack5';
import type { TypescriptOptions } from '@storybook/preset-react-webpack';
import webpack from 'webpack';
import { resolve } from 'path';
import UiSharedDepsNpm from '@kbn/ui-shared-deps-npm';
import * as UiSharedDepsSrc from '@kbn/ui-shared-deps-src';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import { REPO_ROOT } from './constants';
import { default as WebpackConfig } from '../webpack.config';
const MOCKS_DIRECTORY = '__storybook_mocks__';
const EXTENSIONS = ['.ts', '.js'];
export type { StorybookConfig };
/*
* false is a valid option for typescript.reactDocgen,
* but it is not in the type definition
*/
interface StorybookConfig extends BaseStorybookConfig {
typescript: Partial<TypescriptOptions>;
}
const toPath = (_path: string) => path.join(REPO_ROOT, _path);
export type { StorybookConfig };
// This ignore pattern excludes all of node_modules EXCEPT for `@kbn`. This allows for
// changes to packages to cause a refresh in Storybook.
@ -32,31 +42,101 @@ const IGNORE_GLOBS = [
];
export const defaultConfig: StorybookConfig = {
addons: ['@kbn/storybook/preset', '@storybook/addon-a11y', '@storybook/addon-essentials'],
core: {
builder: 'webpack5',
addons: [
'@kbn/storybook/preset',
'@storybook/addon-a11y',
'@storybook/addon-webpack5-compiler-babel',
// https://storybook.js.org/docs/essentials
'@storybook/addon-essentials',
'@storybook/addon-jest',
{
/**
* This addon replaces rules in the default SB webpack config
* to avoid duplicate rule issues caused by directly using the rules
* in the custom webpack config.
*/
name: '@storybook/addon-styling-webpack',
options: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.scss$/,
exclude: /\.module.(s(a|c)ss)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader', options: { importLoaders: 2 } },
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: require.resolve('@kbn/optimizer/postcss.config'),
},
},
},
{
loader: 'sass-loader',
options: {
additionalData(content: string, loaderContext: any) {
const req = JSON.stringify(
loaderContext.utils.contextify(
loaderContext.context || loaderContext.rootContext,
resolve(REPO_ROOT, 'src/core/public/styles/core_app/_globals_v8light.scss')
)
);
return `@import ${req};\n${content}`;
},
implementation: require('sass-embedded'),
sassOptions: {
includePaths: [resolve(REPO_ROOT, 'node_modules')],
quietDeps: true,
},
},
},
],
},
],
},
},
],
stories: ['../**/*.stories.tsx', '../**/*.mdx'],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
stories: ['../**/*.stories.tsx', '../**/*.stories.mdx'],
typescript: {
reactDocgen: false,
},
features: {
postcss: false,
core: {
disableTelemetry: true,
},
// @ts-expect-error StorybookConfig type is incomplete
// https://storybook.js.org/docs/react/configure/babel#custom-configuration
babel: async (options) => {
options.presets.push([
require.resolve('@emotion/babel-preset-css-prop'),
async babel(config: any, options: any) {
if (!config?.presets) {
config.presets = [];
}
config.presets.push(
require.resolve('@kbn/babel-preset/common_preset'),
[
require.resolve('@emotion/babel-preset-css-prop'),
{
// There's an issue where emotion classnames may be duplicated,
// (e.g. `[hash]-[filename]--[local]_[filename]--[local]`)
// https://github.com/emotion-js/emotion/issues/2417
autoLabel: 'always',
labelFormat: '[filename]--[local]',
},
],
{
// There's an issue where emotion classnames may be duplicated,
// (e.g. `[hash]-[filename]--[local]_[filename]--[local]`)
// https://github.com/emotion-js/emotion/issues/2417
autoLabel: 'always',
labelFormat: '[filename]--[local]',
},
]);
return options;
plugins: [
process.env.NODE_ENV !== 'production' && require.resolve('react-refresh/babel'),
].filter(Boolean),
}
);
return config;
},
webpackFinal: (config, options) => {
if (process.env.CI) {
@ -64,6 +144,9 @@ export const defaultConfig: StorybookConfig = {
config.cache = true;
}
// required for react refresh
config.target = 'web';
// This will go over every component which is imported and check its import statements.
// For every import which starts with ./ it will do a check to see if a file with the same name
// exists in the __storybook_mocks__ folder. If it does, use that import instead.
@ -106,6 +189,14 @@ export const defaultConfig: StorybookConfig = {
})
);
if (process.env.NODE_ENV !== 'production') {
config.plugins?.push(
new ReactRefreshWebpackPlugin({
overlay: false,
})
);
}
config.resolve = {
...config.resolve,
fallback: {
@ -113,43 +204,40 @@ export const defaultConfig: StorybookConfig = {
fs: false,
},
};
config.watch = true;
config.watchOptions = {
...config.watchOptions,
ignored: IGNORE_GLOBS,
};
// Remove when @storybook has moved to @emotion v11
// https://github.com/storybookjs/storybook/issues/13145
const emotion11CompatibleConfig = {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve?.alias,
'@emotion/core': toPath('node_modules/@emotion/react'),
'@emotion/styled': toPath('node_modules/@emotion/styled'),
'emotion-theming': toPath('node_modules/@emotion/react'),
},
},
};
return emotion11CompatibleConfig;
},
};
// defaultConfigWebFinal and mergeWebpackFinal have been moved here because webpackFinal usage in
// storybook main.ts somehow is causing issues with newly added dependency of ts-node most likely
// an issue with storybook typescript setup see this issue for more details
// https://github.com/storybookjs/storybook/issues/9610
export const defaultConfigWebFinal: StorybookConfig = {
...defaultConfig,
webpackFinal: (config: Configuration) => {
return WebpackConfig({ config });
},
};
previewHead: (head) => `
${head}
<meta name="eui-global" />
<meta name="emotion" />
<script>
window.__kbnPublicPath__ = { 'kbn-ui-shared-deps-npm': '', 'kbn-ui-shared-deps-src': '' };
window.__kbnHardenPrototypes__ = false;
</script>
<script src="kbn-ui-shared-deps-npm.dll.js"></script>
<script src="kbn-ui-shared-deps-src.js"></script>
<link href="kbn-ui-shared-deps-src.css" rel="stylesheet" />
export const mergeWebpackFinal = (extraConfig: Configuration) => {
return { webpackFinal: (config: Configuration) => webpackMerge(config, extraConfig) };
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&family=Roboto+Mono:ital,wght@0,400..700;1,400..700&display=swap"
rel="stylesheet">
<meta name="eui-utilities" />
`,
staticDirs: [
UiSharedDepsNpm.distDir,
UiSharedDepsSrc.distDir,
{
from: `${REPO_ROOT}/src/platform/plugins/shared/kibana_react/public/assets`,
to: 'plugins/kibanaReact/assets',
},
],
};

View file

@ -7,4 +7,17 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export const STORYBOOK_TITLE = 'Unified Tabs';
import type { Preview } from '@storybook/react';
import * as jest from 'jest-mock';
import { decorators } from './decorators';
// @ts-expect-error
window.jest = jest;
const preview: Preview = {
decorators,
initialGlobals: { euiTheme: 'v8.light' },
};
// eslint-disable-next-line import/no-default-export
export default preview;

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { addons } from '@storybook/addons';
import { addons } from '@storybook/manager-api';
import { create } from '@storybook/theming';
import { registerThemeSwitcherAddon } from './register_theme_switcher_addon';

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { addons, types } from '@storybook/addons';
import { addons, types } from '@storybook/manager-api';
import { ThemeSwitcher } from './theme_switcher';
export const THEME_SWITCHER_ADDON_ID = 'kibana/eui-theme-switcher';
@ -16,7 +16,7 @@ export function registerThemeSwitcherAddon() {
addons.register(THEME_SWITCHER_ADDON_ID, (api) => {
const channel = api.getChannel();
channel.on('globalsUpdated', ({ globals }) => {
channel?.on('globalsUpdated', ({ globals }) => {
const previewFrame = document.getElementById(
'storybook-preview-iframe'
) as HTMLIFrameElement | null;

View file

@ -7,17 +7,15 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
/* eslint-disable @typescript-eslint/no-var-requires */
import { join } from 'path';
import { logger } from '@storybook/node-logger';
import buildStandalone from '@storybook/react/standalone';
import { build } from '@storybook/core-server';
import type { CLIOptions, BuilderOptions, LoadOptions } from '@storybook/types';
import { Flags, run } from '@kbn/dev-cli-runner';
import UiSharedDepsNpm from '@kbn/ui-shared-deps-npm';
import * as UiSharedDepsSrc from '@kbn/ui-shared-deps-src';
// @ts-expect-error internal dep of storybook
import interpret from 'interpret'; // eslint-disable-line import/no-extraneous-dependencies
import * as constants from './constants';
type StorybookCliOptions = CLIOptions & BuilderOptions & LoadOptions & { mode: 'dev' | 'static' };
// Convert the flags to a Storybook loglevel
function getLogLevelFromFlags(flags: Flags) {
if (flags.debug) {
@ -40,29 +38,29 @@ export function runStorybookCli({ configDir, name }: { configDir: string; name:
async ({ flags, log }) => {
log.debug('Global config:\n', constants);
const staticDir = [
UiSharedDepsNpm.distDir,
UiSharedDepsSrc.distDir,
'src/platform/plugins/shared/kibana_react/public/assets:plugins/kibanaReact/assets',
];
const config: Record<string, any> = {
const config: StorybookCliOptions = {
configDir,
mode: flags.site ? 'static' : 'dev',
port: 9001,
staticDir,
loglevel: getLogLevelFromFlags(flags),
};
if (flags.site) {
config.outputDir = join(constants.ASSET_DIR, name);
process.env.NODE_ENV = 'production';
} else {
// required for react refresh
process.env.NODE_ENV = 'development';
}
logger.setLevel(getLogLevelFromFlags(flags));
// force storybook to use our transpilation rather than ts-node or anything else
interpret.extensions['.ts'] = [require.resolve('@kbn/babel-register/install')];
interpret.extensions['.tsx'] = [require.resolve('@kbn/babel-register/install')];
interpret.extensions['.jsx'] = [require.resolve('@kbn/babel-register/install')];
await buildStandalone(config);
try {
// Some transitive deps of addon-docs are ESM and not loading properly
// See: https://github.com/storybookjs/storybook/issues/29467
require('fix-esm').require('react-docgen');
await build(config);
} finally {
require('fix-esm').unregister();
}
// Line is only reached when building the static version
if (flags.site) process.exit();

View file

@ -8,8 +8,9 @@
*/
import React, { useCallback, useEffect } from 'react';
import { Icons, IconButton, TooltipLinkList, WithTooltip } from '@storybook/components';
import { useGlobals } from '@storybook/api';
import { IconButton, TooltipLinkList, WithTooltip } from '@storybook/components';
import { useGlobals } from '@storybook/manager-api';
import { HeartIcon, HeartHollowIcon } from '@storybook/icons';
import { DEFAULT_THEME, THEMES, THEME_TITLES } from './themes';
@ -37,7 +38,6 @@ export function ThemeSwitcher() {
<WithTooltip
placement="top"
trigger="click"
closeOnClick
tooltip={({ onHide }) => (
<ThemeSwitcherTooltip
onHide={onHide}
@ -46,9 +46,8 @@ export function ThemeSwitcher() {
/>
)}
>
{/* @ts-ignore Remove when @storybook has moved to @emotion v11 */}
<IconButton key="eui-theme" title="Change the EUI theme">
<Icons icon={selectedTheme?.includes('dark') ? 'heart' : 'hearthollow'} />
{selectedTheme?.includes('dark') ? <HeartIcon /> : <HeartHollowIcon />}
</IconButton>
</WithTooltip>
);
@ -78,6 +77,6 @@ const ThemeSwitcherTooltip = React.memo(
})
);
return <TooltipLinkList links={links} />;
return <TooltipLinkList links={links.flat()} />;
}
);

View file

@ -10,64 +10,13 @@
/* eslint-disable import/no-default-export */
import { externals } from '@kbn/ui-shared-deps-src';
import { resolve } from 'path';
import webpack, { Configuration } from 'webpack';
import { Configuration } from 'webpack';
import { merge as webpackMerge } from 'webpack-merge';
import { NodeLibsBrowserPlugin } from '@kbn/node-libs-browser-webpack-plugin';
import { REPO_ROOT } from './lib/constants';
import { IgnoreNotFoundExportPlugin } from './ignore_not_found_export_plugin';
import 'webpack-dev-server'; // Extends webpack configuration with `devServer` property
type Preset = string | [string, Record<string, unknown>] | Record<string, unknown>;
function isProgressPlugin(plugin: any) {
return 'handler' in plugin && plugin.showActiveModules && plugin.showModules;
}
function isHtmlPlugin(plugin: any): plugin is { options: { template: string } } {
return !!(typeof plugin.options?.template === 'string');
}
interface BabelLoaderRule extends webpack.RuleSetRule {
use: Array<{
loader: 'babel-loader';
[key: string]: unknown;
}>;
}
function isBabelLoaderRule(rule: webpack.RuleSetRule): rule is BabelLoaderRule {
return !!(
rule.use &&
Array.isArray(rule.use) &&
rule.use.some(
(l) =>
typeof l === 'object' && typeof l?.loader === 'string' && l?.loader.includes('babel-loader')
)
);
}
function getPresetPath(preset: Preset) {
if (typeof preset === 'string') return preset;
if (Array.isArray(preset)) return preset[0];
return undefined;
}
function getTsPreset(preset: Preset) {
if (getPresetPath(preset)?.includes('preset-typescript')) {
if (typeof preset === 'string') return [preset, {}];
if (Array.isArray(preset)) return preset;
throw new Error('unsupported preset-typescript format');
}
}
function isDesiredPreset(preset: Preset) {
return !getPresetPath(preset)?.includes('preset-flow');
}
// Extend the Storybook Webpack config with some customizations
/**
* @returns {import('webpack').Configuration}
*/
export default ({ config: storybookConfig }: { config: Configuration }) => {
const config: Configuration = {
devServer: {
@ -97,41 +46,6 @@ export default ({ config: storybookConfig }: { config: Configuration }) => {
loader: require.resolve('@kbn/peggy-loader'),
},
},
{
test: /\.scss$/,
exclude: /\.module.(s(a|c)ss)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader', options: { importLoaders: 2 } },
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: require.resolve('@kbn/optimizer/postcss.config'),
},
},
},
{
loader: 'sass-loader',
options: {
additionalData(content: string, loaderContext: any) {
const req = JSON.stringify(
loaderContext.utils.contextify(
loaderContext.context || loaderContext.rootContext,
resolve(REPO_ROOT, 'src/core/public/styles/core_app/_globals_v8light.scss')
)
);
return `@import ${req};\n${content}`;
},
implementation: require('sass-embedded'),
sassOptions: {
includePaths: [resolve(REPO_ROOT, 'node_modules')],
quietDeps: true,
},
},
},
],
},
],
},
plugins: [new NodeLibsBrowserPlugin(), new IgnoreNotFoundExportPlugin()],
@ -147,74 +61,5 @@ export default ({ config: storybookConfig }: { config: Configuration }) => {
stats: 'errors-only',
};
// Override storybookConfig mainFields instead of merging with config
delete storybookConfig.resolve?.mainFields;
const updatedModuleRules: webpack.RuleSetRule[] = [];
// clone and modify the module.rules config provided by storybook so that the default babel plugins run after the typescript preset
for (const originalRule of storybookConfig.module?.rules ?? []) {
const rule = typeof originalRule !== 'string' ? { ...originalRule } : {};
updatedModuleRules.push(rule);
if (isBabelLoaderRule(rule)) {
rule.use = [...rule.use];
const loader = (rule.use[0] = { ...rule.use[0] });
const options = (loader.options = { ...(loader.options as Record<string, any>) });
// capture the plugins defined at the root level
const plugins: string[] = options.plugins ?? [];
options.plugins = [];
// move the plugins to the top of the preset array so they will run after the typescript preset
options.presets = [
require.resolve('@kbn/babel-preset/common_preset'),
{ plugins },
...(options.presets as Preset[]).filter(isDesiredPreset).map((preset) => {
const tsPreset = getTsPreset(preset);
if (!tsPreset) {
return preset;
}
return [
tsPreset[0],
{
...tsPreset[1],
allowNamespaces: true,
allowDeclareFields: true,
},
];
}),
];
}
}
// copy and modify the webpack plugins added by storybook
const filteredStorybookPlugins = [];
for (const plugin of storybookConfig.plugins ?? []) {
// Remove the progress plugin
if (isProgressPlugin(plugin)) {
continue;
}
// This is the hacky part. We find something that looks like the
// HtmlWebpackPlugin and mutate its `options.template` to point at our
// revised template.
if (isHtmlPlugin(plugin)) {
plugin.options.template = require.resolve('../templates/index.ejs');
}
filteredStorybookPlugins.push(plugin);
}
return webpackMerge<object>(
{
...storybookConfig,
plugins: filteredStorybookPlugins,
module: {
...storybookConfig.module,
rules: updatedModuleRules,
},
},
config
);
return webpackMerge(storybookConfig, config);
};

View file

@ -1,77 +0,0 @@
<!DOCTYPE html>
<!-- This is a copy of the
[Storybook IFrame template](https://github.com/storybookjs/storybook/blob/337fdcd0fe7436b46bcca65145ff6db2affd780f/lib/core-common/src/templates/index.ejs).
We use this one instead because we want to add the @kbn/ui-shared-deps-* tags here.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<title>
<%= htmlWebpackPlugin.options.title || 'Storybook' %>
</title>
<% if (htmlWebpackPlugin.files.favicon) { %>
<link rel="shortcut icon" href="<%= htmlWebpackPlugin.files.favicon%>" />
<% } %>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="eui-global" />
<meta name="emotion" />
<!-- Added for Kibana shared dependencies -->
<script>
window.__kbnPublicPath__ = { 'kbn-ui-shared-deps-npm': '', 'kbn-ui-shared-deps-src': '' };
window.__kbnHardenPrototypes__ = false;
</script>
<script src="kbn-ui-shared-deps-npm.dll.js"></script>
<script src="kbn-ui-shared-deps-src.js"></script>
<link href="kbn-ui-shared-deps-src.css" rel="stylesheet" />
<!-- -->
<% if (typeof headHtmlSnippet !=='undefined' ) { %>
<%= headHtmlSnippet %>
<% } %>
<% htmlWebpackPlugin.files.css.forEach(file=> { %>
<link href="<%= file %>" rel="stylesheet" />
<% }); %>
<style>
#root[hidden],
#docs-root[hidden] {
display: none !important;
}
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&family=Roboto+Mono:ital,wght@0,400..700;1,400..700&display=swap"
rel="stylesheet">
<meta name="eui-utilities" />
</head>
<body>
<% if (typeof bodyHtmlSnippet !=='undefined' ) { %>
<%= bodyHtmlSnippet %>
<% } %>
<div id="root"></div>
<div id="docs-root"></div>
<% if (typeof globals !=='undefined' && Object.keys(globals).length) { %>
<script>
<% for (var varName in globals) { %>
<% if (globals[varName] != undefined) { %>
window['<%=varName%>'] = <%= JSON.stringify(globals[varName]) %>;
<% } %>
<% } %>
</script>
<% } %>
<% htmlWebpackPlugin.files.js.forEach(file=> { %>
<script src="<%= file %>"></script>
<% }); %>
</body>
</html>

View file

@ -7,7 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { storiesOf } from '@storybook/react';
import React from 'react';
import { EuiFlexGroup } from '@elastic/eui';
import { DataViewField } from '@kbn/data-views-plugin/public';
@ -36,13 +35,26 @@ const renderFieldName = (fldName: React.ReactNode) => {
);
};
storiesOf('components/FieldName/FieldNameStories', module)
.add('default', () => renderFieldName(<FieldName fieldName={'Discover test'} />))
.add('with field type', () =>
renderFieldName(<FieldName fieldName={'Discover test'} fieldType={'number'} />)
)
.add('with field mapping', () =>
export default {
title: 'components/FieldName/FieldNameStories',
};
export const Default = {
render: () => renderFieldName(<FieldName fieldName={'Discover test'} />),
name: 'default',
};
export const WithFieldType = {
render: () => renderFieldName(<FieldName fieldName={'Discover test'} fieldType={'number'} />),
name: 'with field type',
};
export const WithFieldMapping = {
render: () =>
renderFieldName(
<FieldName fieldName={'Discover test'} fieldMapping={field} fieldType={'number'} />
)
);
),
name: 'with field mapping',
};

View file

@ -8,10 +8,9 @@
*/
import React from 'react';
import type { ComponentStory } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { Tab, type TabProps } from '../tab';
import { STORYBOOK_TITLE } from './storybook_constants';
import { MAX_TAB_WIDTH, MIN_TAB_WIDTH } from '../../constants';
const asyncAction =
@ -21,14 +20,14 @@ const asyncAction =
};
export default {
title: `${STORYBOOK_TITLE}/Tab`,
title: 'Unified Tabs/Tab',
parameters: {
backgrounds: {
default: 'white',
values: [{ name: 'white', value: '#fff' }],
},
},
};
} as Meta;
const tabsSizeConfig = {
isScrollable: false,
@ -36,7 +35,7 @@ const tabsSizeConfig = {
regularTabMinWidth: MIN_TAB_WIDTH,
};
const TabTemplate: ComponentStory<React.FC<TabProps>> = (args) => (
const TabTemplate: StoryFn<TabProps> = (args) => (
<Tab
{...args}
tabsSizeConfig={tabsSizeConfig}

View file

@ -8,23 +8,22 @@
*/
import React from 'react';
import type { ComponentStory } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { TabbedContent, type TabbedContentProps } from '../tabbed_content';
import { useNewTabProps } from '../../hooks/use_new_tab_props';
import { STORYBOOK_TITLE } from './storybook_constants';
export default {
title: `${STORYBOOK_TITLE}/Tabs`,
title: 'Unified Tabs/Tabs',
parameters: {
backgrounds: {
default: 'white',
values: [{ name: 'white', value: '#fff' }],
},
},
};
} as Meta;
const TabbedContentTemplate: ComponentStory<React.FC<TabbedContentProps>> = (args) => {
const TabbedContentTemplate: StoryFn<TabbedContentProps> = (args) => {
const { getNewTabDefaultProps } = useNewTabProps({
numberOfInitialItems: args.initialItems.length,
});

View file

@ -21,7 +21,7 @@ import { I18nProvider } from '@kbn/i18n-react';
import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root';
import { action } from '@storybook/addon-actions';
import type { DecoratorFn } from '@storybook/react';
import type { Decorator } from '@storybook/react';
import type { CoreTheme } from '@kbn/core-theme-browser';
import type { I18nStart } from '@kbn/core-i18n-browser';
@ -35,7 +35,7 @@ const i18n: I18nStart = {
Context: ({ children }) => <I18nProvider>{children}</I18nProvider>,
};
export const KibanaContextDecorator: DecoratorFn = (storyFn, { globals }) => {
export const KibanaContextDecorator: Decorator (storyFn, { globals }) => {
const colorMode = globals.euiTheme === 'v8.dark' ? 'dark' : 'light';
useEffect(() => {

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import type { DecoratorFn } from '@storybook/react';
import type { Decorator } from '@storybook/react';
import React from 'react';
import * as styledComponents from 'styled-components';
@ -52,7 +52,7 @@ const KibanaStyledComponentsThemeProvider = <
*
* @deprecated All Kibana components need to migrate to Emotion.
*/
export const KibanaStyledComponentsThemeProviderDecorator: DecoratorFn = (storyFn, { globals }) => {
export const KibanaStyledComponentsThemeProviderDecorator: Decorator = (storyFn, { globals }) => {
const darkMode = globals.euiTheme === 'v8.dark' || globals.euiTheme === 'v7.dark';
return (

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { addons } from '@storybook/addons';
import { addons } from '@storybook/manager-api';
import { create } from '@storybook/theming';
import { PANEL_ID as selectedPanel } from '@storybook/addon-actions';

View file

@ -25,46 +25,50 @@ const argTypes = {
type KnownSolutionParams = Pick<KnownSolutionProps, 'size' | 'name'>;
export const SolutionType = (params: KnownSolutionParams) => {
return <KibanaSolutionAvatar {...params} />;
};
SolutionType.argTypes = {
name: {
control: 'select',
options: ['Cloud', 'Elastic', 'Kibana', 'Observability', 'Security', 'Enterprise Search'],
defaultValue: 'Elastic',
export const SolutionType = {
render: (params: KnownSolutionParams) => {
return <KibanaSolutionAvatar {...params} />;
},
argTypes: {
name: {
control: 'select',
options: ['Cloud', 'Elastic', 'Kibana', 'Observability', 'Security', 'Enterprise Search'],
defaultValue: 'Elastic',
},
...argTypes,
},
...argTypes,
};
type IconTypeParams = Pick<IconTypeProps, 'size' | 'name' | 'iconType'>;
export const IconType = (params: IconTypeParams) => {
return <KibanaSolutionAvatar {...params} />;
};
export const IconType = {
render: (params: IconTypeParams) => {
return <KibanaSolutionAvatar {...params} />;
},
IconType.argTypes = {
iconType: {
control: 'select',
options: [
'logoCloud',
'logoElastic',
'logoElasticsearch',
'logoElasticStack',
'logoKibana',
'logoObservability',
'logoSecurity',
'logoSiteSearch',
'logoWorkplaceSearch',
'machineLearningApp',
'managementApp',
],
defaultValue: 'logoElastic',
argTypes: {
iconType: {
control: 'select',
options: [
'logoCloud',
'logoElastic',
'logoElasticsearch',
'logoElasticStack',
'logoKibana',
'logoObservability',
'logoSecurity',
'logoSiteSearch',
'logoWorkplaceSearch',
'machineLearningApp',
'managementApp',
],
defaultValue: 'logoElastic',
},
name: {
control: 'text',
defaultValue: 'Solution Name',
},
...argTypes,
},
name: {
control: 'text',
defaultValue: 'Solution Name',
},
...argTypes,
};

View file

@ -30,7 +30,7 @@ export class StorybookMock extends AbstractStorybookMock<
> {
propArguments = {
toggleChrome: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: true,
},
};

View file

@ -31,12 +31,14 @@ export default {
const mock = new ExitFullScreenButtonStorybookMock();
export const ExitFullScreenButton = (params: ExitFullScreenButtonStorybookParams) => {
return (
<ExitFullScreenButtonProvider {...mock.getServices()}>
<Component {...mock.getProps(params)} />
</ExitFullScreenButtonProvider>
);
};
export const ExitFullScreenButton = {
render: (params: ExitFullScreenButtonStorybookParams) => {
return (
<ExitFullScreenButtonProvider {...mock.getServices()}>
<Component {...mock.getProps(params)} />
</ExitFullScreenButtonProvider>
);
},
ExitFullScreenButton.argTypes = mock.getArgumentTypes();
argTypes: mock.getArgumentTypes(),
};

View file

@ -65,8 +65,10 @@ const argTypes = {
type Params = Record<keyof typeof argTypes, any>;
export const IconButtonGroup = ({ buttonCount }: Params) => {
return <Component legend="Example icon group" buttons={iconButtons.slice(0, buttonCount)} />;
};
export const IconButtonGroup = {
render: ({ buttonCount }: Params) => {
return <Component legend="Example icon group" buttons={iconButtons.slice(0, buttonCount)} />;
},
IconButtonGroup.argTypes = argTypes;
argTypes,
};

View file

@ -47,16 +47,18 @@ const argTypes = {
type Params = Record<keyof typeof argTypes, any>;
export const ToolbarButton = ({ buttonStyle, buttonType, iconSide }: Params) => {
return (
<Component
as={buttonStyle}
label="Toolbar button"
iconType="lensApp"
type={buttonType}
iconSide={iconSide}
/>
);
};
export const ToolbarButton = {
render: ({ buttonStyle, buttonType, iconSide }: Params) => {
return (
<Component
as={buttonStyle}
label="Toolbar button"
iconType="lensApp"
type={buttonType}
iconSide={iconSide}
/>
);
},
ToolbarButton.argTypes = argTypes;
argTypes,
};

View file

@ -43,41 +43,43 @@ export default {
argTypes,
};
export const Popover = ({ showIcon, buttonType }: Params) => {
return (
<Component
type={buttonType}
label="Add element"
iconType={showIcon ? 'plusInCircle' : undefined}
panelPaddingSize="none"
>
{() => (
<EuiContextMenu
initialPanelId={0}
panels={[
{
id: 0,
title: 'Open editor',
items: [
{
name: 'Lens',
icon: 'lensApp',
},
{
name: 'Maps',
icon: 'logoMaps',
},
{
name: 'TSVB',
icon: 'visVisualBuilder',
},
],
},
]}
/>
)}
</Component>
);
};
export const Popover = {
render: ({ showIcon, buttonType }: Params) => {
return (
<Component
type={buttonType}
label="Add element"
iconType={showIcon ? 'plusInCircle' : undefined}
panelPaddingSize="none"
>
{() => (
<EuiContextMenu
initialPanelId={0}
panels={[
{
id: 0,
title: 'Open editor',
items: [
{
name: 'Lens',
icon: 'lensApp',
},
{
name: 'Maps',
icon: 'logoMaps',
},
{
name: 'TSVB',
icon: 'visVisualBuilder',
},
],
},
]}
/>
)}
</Component>
);
},
Popover.argTypes = argTypes;
argTypes,
};

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { Story } from '@storybook/react';
import type { StoryFn } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { EuiContextMenu } from '@elastic/eui';
@ -174,7 +174,7 @@ export default {
},
};
const Template: Story<{
const Template: StoryFn<{
solution: 'Generic' | 'Canvas' | 'Dashboard';
iconButtonCount: number;
showAddFromLibraryButton: boolean;
@ -205,20 +205,29 @@ const Template: Story<{
);
};
export const Generic = Template.bind({});
Generic.args = {
...Template.args,
solution: 'Generic',
export const Generic = {
render: Template,
args: {
...Template.args,
solution: 'Generic',
},
};
export const Canvas = Template.bind({});
Canvas.args = {
...Template.args,
solution: 'Canvas',
export const Canvas = {
render: Template,
args: {
...Template.args,
solution: 'Canvas',
},
};
export const Dashboard = Template.bind({});
Dashboard.args = {
...Template.args,
solution: 'Dashboard',
export const Dashboard = {
render: Template,
args: {
...Template.args,
solution: 'Dashboard',
},
};

View file

@ -30,12 +30,14 @@ export default {
const mock = new NoDataCardStorybookMock();
const argTypes = mock.getArgumentTypes();
export const Card = (params: NoDataCardStorybookParams) => {
return (
<NoDataCardProvider {...mock.getServices(params)}>
<NoDataCard {...params} />
</NoDataCardProvider>
);
};
export const Card = {
render: (params: NoDataCardStorybookParams) => {
return (
<NoDataCardProvider {...mock.getServices(params)}>
<NoDataCard {...params} />
</NoDataCardProvider>
);
},
Card.argTypes = argTypes;
argTypes,
};

View file

@ -35,25 +35,25 @@ export class StorybookMock extends AbstractStorybookMock<
propArguments = {
category: {
control: {
type: 'text',
control: 'text',
},
defaultValue: '',
},
title: {
control: {
type: 'text',
control: 'text',
},
defaultValue: '',
},
description: {
control: {
type: 'text',
control: 'text',
},
defaultValue: '',
},
button: {
control: {
type: 'text',
control: 'text',
},
defaultValue: '',
},
@ -61,7 +61,7 @@ export class StorybookMock extends AbstractStorybookMock<
serviceArguments = {
canAccessFleet: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: true,
},
};

View file

@ -21,7 +21,7 @@ export class StorybookMock extends AbstractStorybookMock<{}, NavigationServices>
serviceArguments = {
isSideNavCollapsed: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
};

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { ComponentMeta } from '@storybook/react';
import { Meta } from '@storybook/react';
import React, { EventHandler, FC, MouseEvent, useState, useEffect } from 'react';
import { of } from 'rxjs';
@ -530,4 +530,4 @@ export default {
},
},
component: GeneralLayoutStructure,
} as ComponentMeta<typeof GeneralLayoutStructure>;
} as Meta<typeof GeneralLayoutStructure>;

View file

@ -31,19 +31,21 @@ export default {
const mock = new CodeEditorStorybookMock();
const argTypes = mock.getArgumentTypes();
export const Basic = (params: CodeEditorStorybookParams) => {
return (
<CodeEditor
{...params}
languageId="plainText"
onChange={action('on change')}
value="Hello!"
height={200}
/>
);
};
export const Basic = {
render: (params: CodeEditorStorybookParams) => {
return (
<CodeEditor
{...params}
languageId="plainText"
onChange={action('on change')}
value="Hello!"
height={200}
/>
);
},
Basic.argTypes = argTypes;
argTypes,
};
// A sample language definition with a few example tokens
// Taken from https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages
@ -66,25 +68,27 @@ const logs = `[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
`;
export const CustomLogLanguage = (params: CodeEditorStorybookParams) => {
return (
<div>
<CodeEditor
{...params}
languageId="loglang"
height={250}
value={logs}
options={{
minimap: {
enabled: false,
},
}}
/>
</div>
);
};
export const CustomLogLanguage = {
render: (params: CodeEditorStorybookParams) => {
return (
<div>
<CodeEditor
{...params}
languageId="loglang"
height={250}
value={logs}
options={{
minimap: {
enabled: false,
},
}}
/>
</div>
);
},
CustomLogLanguage.argTypes = argTypes;
argTypes,
};
export const JSONSupport = () => {
return (
@ -207,24 +211,26 @@ export const HoverProvider = () => {
);
};
export const AutomaticResize = (params: CodeEditorStorybookParams) => {
return (
<div style={{ height: `calc(100vh - 30px)` }}>
<CodeEditor
{...params}
languageId="plainText"
onChange={action('on change')}
value="Hello!"
height={'100%'}
options={{ automaticLayout: true }}
/>
</div>
);
export const AutomaticResize = {
render: (params: CodeEditorStorybookParams) => {
return (
<div style={{ height: `calc(100vh - 30px)` }}>
<CodeEditor
{...params}
languageId="plainText"
onChange={action('on change')}
value="Hello!"
height={'100%'}
options={{ automaticLayout: true }}
/>
</div>
);
},
argTypes,
};
AutomaticResize.argTypes = argTypes;
export const FitToContent = (params: CodeEditorStorybookParams) => {
const FitToContentComponent = (params: CodeEditorStorybookParams) => {
const [value, setValue] = useState('hello');
return (
<CodeEditor
@ -241,4 +247,8 @@ export const FitToContent = (params: CodeEditorStorybookParams) => {
);
};
FitToContent.argTypes = argTypes;
export const FitToContent = {
render: (params: CodeEditorStorybookParams) => <FitToContentComponent {...params} />,
argTypes,
};

View file

@ -35,38 +35,38 @@ export class CodeEditorStorybookMock extends AbstractStorybookMock<
propArguments = {
languageId: {
control: {
type: 'radio',
control: 'radio',
},
options: ['json', 'loglang', 'plaintext'],
defaultValue: 'json',
},
value: {
control: {
type: 'text',
control: 'text',
},
defaultValue: '',
},
'aria-label': {
control: {
type: 'text',
control: 'text',
},
defaultValue: 'code editor',
},
allowFullScreen: {
control: {
type: 'boolean',
control: 'boolean',
},
defaultValue: false,
},
transparentBackground: {
control: {
type: 'boolean',
control: 'boolean',
},
defaultValue: false,
},
placeholder: {
control: {
type: 'text',
control: 'text',
},
defaultValue: 'myplaceholder',
},

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Meta, Story } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/react';
import React from 'react';
import { EuiFormFieldset } from '@elastic/eui';
@ -32,7 +32,7 @@ export default {
},
} as Meta;
export const ErrorInCallout: Story = () => {
export const ErrorInCallout: StoryFn = () => {
const services = storybookMock.getServices();
return (
@ -46,7 +46,7 @@ export const ErrorInCallout: Story = () => {
);
};
export const SectionErrorInCallout: Story = () => {
export const SectionErrorInCallout: StoryFn = () => {
const services = storybookMock.getServices();
return (

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Meta, Story } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/react';
import React from 'react';
import { EuiFormFieldset } from '@elastic/eui';
@ -34,7 +34,7 @@ export default {
},
} as Meta;
export const ErrorInCallout: Story = () => {
export const ErrorInCallout: StoryFn = () => {
const services = storybookMock.getServices();
return (
@ -48,7 +48,7 @@ export const ErrorInCallout: Story = () => {
);
};
export const SectionErrorInCallout: Story = () => {
export const SectionErrorInCallout: StoryFn = () => {
const services = storybookMock.getServices();
return (

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { base64dLogo } from '@kbn/shared-ux-file-image-mocks';
import type { FileImageMetadata, FileKindBrowser } from '@kbn/shared-ux-file-types';
@ -17,6 +17,7 @@ import { FilesContext } from '@kbn/shared-ux-file-context';
import { FilePicker, Props as FilePickerProps } from './file_picker';
type ListResponse = ReturnType<FilesClient['list']>;
type MetaDecorators = Pick<Meta, 'decorators'>;
const kind = 'filepicker';
const getFileKind = (id: string) =>
@ -58,11 +59,9 @@ export default {
</FilesContext>
),
],
} as ComponentMeta<typeof FilePicker>;
} as Meta<typeof FilePicker>;
const Template: ComponentStory<typeof FilePicker> = (props) => <FilePicker {...props} />;
export const Empty = Template.bind({});
export const Empty = {};
const d = new Date();
let id = 0;
@ -85,47 +84,17 @@ function createFileJSON(file?: Partial<FileJSON<FileImageMetadata>>): FileJSON<F
...file,
};
}
export const BasicOne = Template.bind({});
BasicOne.decorators = [
(Story) => (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async (): ListResponse => ({
files: [createFileJSON()],
total: 1,
}),
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
];
export const BasicMany = Template.bind({});
BasicMany.decorators = [
(Story) => {
const files = [
createFileJSON({ name: 'abc' }),
createFileJSON({ name: 'def' }),
createFileJSON({ name: 'efg' }),
createFileJSON({ name: 'foo' }),
createFileJSON({ name: 'bar' }),
createFileJSON(),
createFileJSON(),
];
return (
export const BasicOne: MetaDecorators = {
decorators: [
(Story) => (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async (): ListResponse => ({
files,
total: files.length,
files: [createFileJSON()],
total: 1,
}),
getFileKind,
} as unknown as FilesClient
@ -133,73 +102,80 @@ BasicMany.decorators = [
>
<Story />
</FilesContext>
);
},
];
),
],
};
export const BasicManyMany = Template.bind({});
BasicManyMany.decorators = [
(Story) => {
const array = new Array(102);
array.fill(null);
return (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async (): ListResponse => ({
files: array.map((_, idx) => createFileJSON({ id: String(idx) })),
total: array.length,
}),
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
);
},
];
export const BasicMany: MetaDecorators = {
decorators: [
(Story) => {
const files = [
createFileJSON({ name: 'abc' }),
createFileJSON({ name: 'def' }),
createFileJSON({ name: 'efg' }),
createFileJSON({ name: 'foo' }),
createFileJSON({ name: 'bar' }),
createFileJSON(),
createFileJSON(),
];
export const ErrorLoading = Template.bind({});
ErrorLoading.decorators = [
(Story) => {
const array = new Array(102);
array.fill(createFileJSON());
return (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async () => {
throw new Error('stop');
},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
);
},
];
export const TryFilter = Template.bind({});
TryFilter.decorators = [
(Story) => {
const array = { files: [createFileJSON()], total: 1 };
return (
<>
<h2>Try entering a filter!</h2>
return (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async ({ name }: { name: string[] }) => {
if (name) {
return { files: [], total: 0 };
}
return array;
list: async (): ListResponse => ({
files,
total: files.length,
}),
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
);
},
],
};
export const BasicManyMany: MetaDecorators = {
decorators: [
(Story) => {
const array = new Array(102);
array.fill(null);
return (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async (): ListResponse => ({
files: array.map((_, idx) => createFileJSON({ id: String(idx) })),
total: array.length,
}),
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
);
},
],
};
export const ErrorLoading: MetaDecorators = {
decorators: [
(Story) => {
const array = new Array(102);
array.fill(createFileJSON());
return (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async () => {
throw new Error('stop');
},
getFileKind,
} as unknown as FilesClient
@ -207,30 +183,61 @@ TryFilter.decorators = [
>
<Story />
</FilesContext>
</>
);
},
];
export const SingleSelect = Template.bind({});
SingleSelect.decorators = [
(Story) => (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async (): ListResponse => ({
files: [createFileJSON(), createFileJSON(), createFileJSON()],
total: 1,
}),
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
];
SingleSelect.args = {
multiple: undefined,
);
},
],
};
export const TryFilter: MetaDecorators = {
decorators: [
(Story) => {
const array = { files: [createFileJSON()], total: 1 };
return (
<>
<h2>Try entering a filter!</h2>
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async ({ name }: { name: string[] }) => {
if (name) {
return { files: [], total: 0 };
}
return array;
},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
</>
);
},
],
};
export const SingleSelect: Partial<Meta> = {
decorators: [
(Story) => (
<FilesContext
client={
{
getDownloadHref: () => `data:image/png;base64,${base64dLogo}`,
list: async (): ListResponse => ({
files: [createFileJSON(), createFileJSON(), createFileJSON()],
total: 1,
}),
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
],
args: {
multiple: undefined,
},
};

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { FileKindBrowser, BaseFilesClient as FilesClient } from '@kbn/shared-ux-file-types';
import { FilesContext } from '@kbn/shared-ux-file-context';
@ -65,150 +65,162 @@ export default {
</FilesContext>
),
],
} as ComponentMeta<typeof FileUpload>;
} as Meta<typeof FileUpload>;
const Template: ComponentStory<typeof FileUpload> = (props: Props) => <FileUpload {...props} />;
export const Basic = {};
export const Basic = Template.bind({});
export const AllowRepeatedUploads = Template.bind({});
AllowRepeatedUploads.args = {
allowRepeatedUploads: true,
export const AllowRepeatedUploads = {
args: {
allowRepeatedUploads: true,
},
};
export const LongErrorUX = Template.bind({});
LongErrorUX.decorators = [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(1000);
throw new Error('Something went wrong while uploading! '.repeat(10).trim());
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
];
export const Abort = Template.bind({});
Abort.decorators = [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(60000);
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
];
export const MaxSize = Template.bind({});
MaxSize.args = {
kind: miniFile,
export const LongErrorUX: StoryObj = {
decorators: [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(1000);
throw new Error('Something went wrong while uploading! '.repeat(10).trim());
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
],
};
export const ZipOnly = Template.bind({});
ZipOnly.args = {
kind: zipOnly,
export const Abort: StoryObj = {
decorators: [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(60000);
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
],
};
export const AllowClearAfterUpload = Template.bind({});
AllowClearAfterUpload.args = {
allowClear: true,
export const MaxSize = {
args: {
kind: miniFile,
},
};
export const ImmediateUpload = Template.bind({});
ImmediateUpload.args = {
immediate: true,
export const ZipOnly = {
args: {
kind: zipOnly,
},
};
export const ImmediateUploadError = Template.bind({});
ImmediateUploadError.args = {
immediate: true,
};
ImmediateUploadError.decorators = [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(1000);
throw new Error('Something went wrong while uploading!');
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
];
export const ImmediateUploadAbort = Template.bind({});
ImmediateUploadAbort.decorators = [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(60000);
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
];
ImmediateUploadAbort.args = {
immediate: true,
export const AllowClearAfterUpload = {
args: {
allowClear: true,
},
};
export const Compressed = Template.bind({});
Compressed.args = {
compressed: true,
export const ImmediateUpload = {
args: {
immediate: true,
},
};
export const CompressedError = Template.bind({});
CompressedError.args = {
compressed: true,
export const ImmediateUploadError: StoryObj = {
args: {
immediate: true,
},
decorators: [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(1000);
throw new Error('Something went wrong while uploading!');
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
],
};
export const ImmediateUploadAbort: StoryObj = {
decorators: [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(60000);
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
],
args: {
immediate: true,
},
};
export const Compressed = {
args: {
compressed: true,
},
};
export const CompressedError: StoryObj = {
args: {
compressed: true,
},
decorators: [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(1000);
throw new Error('Something went wrong while uploading! '.repeat(10).trim());
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
],
};
CompressedError.decorators = [
(Story) => (
<FilesContext
client={
{
create: async () => ({ file: { id: 'test' } }),
upload: async () => {
await sleep(1000);
throw new Error('Something went wrong while uploading! '.repeat(10).trim());
},
delete: async () => {},
getFileKind,
} as unknown as FilesClient
}
>
<Story />
</FilesContext>
),
];

View file

@ -8,7 +8,7 @@
*/
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/react';
import { getImageMetadata } from '@kbn/shared-ux-file-util';
import { getImageData as getBlob, base64dLogo } from '@kbn/shared-ux-file-image-mocks';
@ -38,47 +38,62 @@ export default {
);
},
],
} as ComponentMeta<typeof Image>;
} as Meta<typeof Image>;
const Template: ComponentStory<typeof Image> = (props: Props, { loaded: { meta } }) => (
const Template: StoryFn<typeof Image> = (props: Props, { loaded: { meta } }) => (
<Image {...props} meta={meta} />
);
export const Basic = Template.bind({});
export const WithBlurhash = Template.bind({});
WithBlurhash.storyName = 'With blurhash';
WithBlurhash.loaders = [
async () => ({
meta: await getImageMetadata(getBlob()),
}),
];
export const BrokenSrc = Template.bind({});
BrokenSrc.storyName = 'Broken src';
BrokenSrc.args = {
src: 'foo',
export const Basic = {
render: Template,
};
export const WithBlurhashAndBrokenSrc = Template.bind({});
WithBlurhashAndBrokenSrc.storyName = 'With blurhash and broken src';
WithBlurhashAndBrokenSrc.args = {
src: 'foo',
export const WithBlurhash = {
render: Template,
name: 'With blurhash',
loaders: [
async () => ({
meta: await getImageMetadata(getBlob()),
}),
],
};
WithBlurhashAndBrokenSrc.loaders = [
async () => ({
blurhash: await getImageMetadata(getBlob()),
}),
];
export const BrokenSrc = {
render: Template,
name: 'Broken src',
export const WithCustomSizing = Template.bind({});
WithCustomSizing.storyName = 'With custom sizing';
WithCustomSizing.loaders = [
async () => ({
meta: await getImageMetadata(getBlob()),
}),
];
WithCustomSizing.args = {
css: `width: 100px; height: 500px; object-fit: fill`,
args: {
src: 'foo',
},
};
export const WithBlurhashAndBrokenSrc = {
render: Template,
name: 'With blurhash and broken src',
args: {
src: 'foo',
},
loaders: [
async () => ({
blurhash: await getImageMetadata(getBlob()),
}),
],
};
export const WithCustomSizing = {
render: Template,
name: 'With custom sizing',
loaders: [
async () => ({
meta: await getImageMetadata(getBlob()),
}),
],
args: {
css: `width: 100px; height: 500px; object-fit: fill`,
},
};

View file

@ -28,46 +28,48 @@ export default {
const mock = new RedirectAppLinksStorybookMock();
export const RedirectAppLinks = () => {
return (
<EuiFlexGroup direction="column">
<EuiFlexItem>
<Component {...mock.getProps()}>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="storybookButton"
iconType="plusInCircle"
href="/some-test-url"
>
Button with URL
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="storybookButton"
iconType="plusInCircle"
onClick={action('onClick')}
>
Button without URL
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</Component>
</EuiFlexItem>
<EuiFlexItem>
<div>
<EuiButton
data-test-subj="storybookButton"
iconType="plusInCircle"
href="/?path=/story/redirect-app-links--component"
>
Button outside RedirectAppLinks
</EuiButton>
</div>
</EuiFlexItem>
</EuiFlexGroup>
);
};
export const RedirectAppLinks = {
render: () => {
return (
<EuiFlexGroup direction="column">
<EuiFlexItem>
<Component {...mock.getProps()}>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="storybookButton"
iconType="plusInCircle"
href="/some-test-url"
>
Button with URL
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="storybookButton"
iconType="plusInCircle"
onClick={action('onClick')}
>
Button without URL
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</Component>
</EuiFlexItem>
<EuiFlexItem>
<div>
<EuiButton
data-test-subj="storybookButton"
iconType="plusInCircle"
href="/?path=/story/redirect-app-links--component"
>
Button outside RedirectAppLinks
</EuiButton>
</div>
</EuiFlexItem>
</EuiFlexGroup>
);
},
RedirectAppLinks.argTypes = mock.getArgumentTypes();
argTypes: mock.getArgumentTypes(),
};

View file

@ -28,15 +28,17 @@ export default {
const mock = new MarkdownStorybookMock();
const argTypes = mock.getArgumentTypes();
export const MarkdownStoryComponent = (params: MarkdownStorybookParams) => {
return (
// The markdown component is wrapped in the EuiFlexItem with width set to 50%
// Height can be set for the markdown component
<EuiFlexItem style={{ width: '400px' }}>
{/* readOnly is set to false because the Markdown component editor will error if set to true without markdown content or children */}
<Markdown {...params} readOnly={false} />
</EuiFlexItem>
);
};
export const MarkdownStoryComponent = {
render: (params: MarkdownStorybookParams) => {
return (
// The markdown component is wrapped in the EuiFlexItem with width set to 50%
// Height can be set for the markdown component
<EuiFlexItem style={{ width: '400px' }}>
{/* readOnly is set to false because the Markdown component editor will error if set to true without markdown content or children */}
<Markdown {...params} readOnly={false} />
</EuiFlexItem>
);
},
MarkdownStoryComponent.argTypes = argTypes;
argTypes,
};

View file

@ -28,25 +28,27 @@ export default {
const mock = new MarkdownStorybookMock();
const argTypes = mock.getArgumentTypes();
export const MarkdownStoryComponent = (params: MarkdownStorybookParams) => {
return (
<EuiFlexGroup>
<EuiFlexItem>
<Markdown
{...params}
readOnly={true}
markdownContent={'My content in **markdown** format set as the *markdownContent prop*'}
/>
<Markdown {...params} readOnly={true}>
{`My content in **markdown** format passed as *children*
\`openLinksInNewTab\` [test link to open in new tab or not](https://www.elastic.co)
\`enableTooltipSupport\` !{tooltip[anchor text](Tooltip content)}
\`validateLinks\` [link with non-standard scheme](testing-testing-this-is-a-non-standatd-scheme://)
`}
</Markdown>
</EuiFlexItem>
</EuiFlexGroup>
);
};
export const MarkdownStoryComponent = {
render: (params: MarkdownStorybookParams) => {
return (
<EuiFlexGroup>
<EuiFlexItem>
<Markdown
{...params}
readOnly={true}
markdownContent={'My content in **markdown** format set as the *markdownContent prop*'}
/>
<Markdown {...params} readOnly={true}>
{`My content in **markdown** format passed as *children*
\`openLinksInNewTab\` [test link to open in new tab or not](https://www.elastic.co)
\`enableTooltipSupport\` !{tooltip[anchor text](Tooltip content)}
\`validateLinks\` [link with non-standard scheme](testing-testing-this-is-a-non-standatd-scheme://)
`}
</Markdown>
</EuiFlexItem>
</EuiFlexGroup>
);
},
MarkdownStoryComponent.argTypes = argTypes;
argTypes,
};

View file

@ -35,49 +35,49 @@ export class MarkdownStorybookMock extends AbstractStorybookMock<
> {
propArguments = {
readOnly: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
openLinksInNewTab: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: true,
},
placeholder: {
control: {
type: 'text',
control: 'text',
},
defaultValue: '',
},
markdownContent: {
control: {
type: 'text',
control: 'text',
},
defaultValue: '',
},
ariaLabelContent: {
control: {
type: 'text',
control: 'text',
},
defaultValue: 'markdown component',
},
height: {
control: {
type: 'select',
control: 'select',
defaultValue: 'full',
label: 'height',
options: [0, 20, 50, 'full'],
},
},
enableTooltipSupport: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
validateLinks: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
enableSoftLineBreaks: {
control: 'boolean',
control: { control: 'boolean' },
defaultValue: false,
},
};

View file

@ -25,47 +25,49 @@ export default {
const mock = new TabbedModalStorybookMock();
const argTypes = mock.getArgumentTypes();
export const TrivialExample = (params: TabbedModalStorybookParams) => {
return (
<TabbedModal
{...params}
modalTitle="Trivial Example"
tabs={[
{
id: 'hello',
name: 'Hello',
content: () => {
return (
<Fragment>
<EuiSpacer size="m" />
<EuiText>
<p>Click the button to send a message into the void</p>
</EuiText>
</Fragment>
);
},
initialState: {
message: 'Hello World!!',
},
modalActionBtn: {
id: 'wave',
label: 'Say Hi 👋🏾',
dataTestSubj: 'wave',
handler: ({ state }) => {
alert(state.message);
export const TrivialExample = {
render: (params: TabbedModalStorybookParams) => {
return (
<TabbedModal
{...params}
modalTitle="Trivial Example"
tabs={[
{
id: 'hello',
name: 'Hello',
content: () => {
return (
<Fragment>
<EuiSpacer size="m" />
<EuiText>
<p>Click the button to send a message into the void</p>
</EuiText>
</Fragment>
);
},
initialState: {
message: 'Hello World!!',
},
modalActionBtn: {
id: 'wave',
label: 'Say Hi 👋🏾',
dataTestSubj: 'wave',
handler: ({ state }) => {
alert(state.message);
},
},
},
},
]}
defaultSelectedTabId="hello"
onClose={() => {}}
/>
);
]}
defaultSelectedTabId="hello"
onClose={() => {}}
/>
);
},
argTypes,
};
TrivialExample.argTypes = argTypes;
export const NonTrivialExample = (params: TabbedModalStorybookParams) => {
const NonTrivialExampleComponent = (params: TabbedModalStorybookParams) => {
const checkboxGroupItemId1 = useGeneratedHtmlId({
prefix: 'checkboxGroupItem',
suffix: 'first',
@ -177,4 +179,8 @@ export const NonTrivialExample = (params: TabbedModalStorybookParams) => {
);
};
NonTrivialExample.argTypes = argTypes;
export const NonTrivialExample = {
render: (params: TabbedModalStorybookParams) => <NonTrivialExampleComponent {...params} />,
argTypes,
};

View file

@ -31,19 +31,19 @@ export class StorybookMock extends AbstractStorybookMock<
propArguments = {
tabs: {
control: {
type: 'array',
control: 'array',
},
defaultValue: [],
},
defaultSelectedTabId: {
control: {
type: 'array',
control: 'array',
},
defaultValue: [],
},
onClose: {
control: {
type: 'array',
control: 'array',
},
defaultValue: [],
},

View file

@ -42,42 +42,50 @@ const solutionNavMock = new SolutionNavStorybookMock();
const noDataConfigMock = new NoDataConfigStorybookMock();
const innerMock = new InnerPageTemplateStorybookMock();
export const WithNoDataConfig = (params: NoDataConfigStorybookParams) => {
return (
<KibanaPageTemplateProvider {...noDataConfigMock.getServices(params)}>
<Component {...noDataConfigMock.getProps(params)} />
</KibanaPageTemplateProvider>
);
export const WithNoDataConfig = {
render: (params: NoDataConfigStorybookParams) => {
return (
<KibanaPageTemplateProvider {...noDataConfigMock.getServices(params)}>
<Component {...noDataConfigMock.getProps(params)} />
</KibanaPageTemplateProvider>
);
},
argTypes: noDataConfigMock.getArgumentTypes(),
};
WithNoDataConfig.argTypes = noDataConfigMock.getArgumentTypes();
export const WithSolutionNav = {
render: (params: SolutionNavStorybookParams) => {
return (
<KibanaPageTemplateProvider {...solutionNavMock.getServices(params)}>
<Component {...solutionNavMock.getProps(params)} />
</KibanaPageTemplateProvider>
);
},
export const WithSolutionNav = (params: SolutionNavStorybookParams) => {
return (
<KibanaPageTemplateProvider {...solutionNavMock.getServices(params)}>
<Component {...solutionNavMock.getProps(params)} />
</KibanaPageTemplateProvider>
);
argTypes: solutionNavMock.getArgumentTypes(),
};
WithSolutionNav.argTypes = solutionNavMock.getArgumentTypes();
export const WithBoth = {
render: (params: KibanaPageTemplateStorybookParams) => {
return (
<KibanaPageTemplateProvider {...templateMock.getServices(params)}>
<Component {...templateMock.getProps(params)} />
</KibanaPageTemplateProvider>
);
},
export const WithBoth = (params: KibanaPageTemplateStorybookParams) => {
return (
<KibanaPageTemplateProvider {...templateMock.getServices(params)}>
<Component {...templateMock.getProps(params)} />
</KibanaPageTemplateProvider>
);
argTypes: templateMock.getArgumentTypes(),
};
WithBoth.argTypes = templateMock.getArgumentTypes();
export const WithNeither = {
render: (params: InnerPageTemplateStorybookParams) => {
return (
<KibanaPageTemplateProvider {...innerMock.getServices(params)}>
<Component {...innerMock.getProps(params)} />
</KibanaPageTemplateProvider>
);
},
export const WithNeither = (params: InnerPageTemplateStorybookParams) => {
return (
<KibanaPageTemplateProvider {...innerMock.getServices(params)}>
<Component {...innerMock.getProps(params)} />
</KibanaPageTemplateProvider>
);
argTypes: innerMock.getArgumentTypes(),
};
WithNeither.argTypes = innerMock.getArgumentTypes();

Some files were not shown because too many files have changed in this diff Show more