diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index e2ba33fff7d..af501c4be44 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -38,6 +38,13 @@ import { writingModeStyles } from './writing_mode.styles'; // once all EUI components are converted to Emotion import '../dist/eui_theme_light.css'; +/** + * Prop controls + */ + +import type { CommonProps } from '../src/components/common'; +import { hideStorybookControls } from './utils'; + const preview: Preview = { decorators: [ (Story, context) => ( @@ -86,6 +93,7 @@ const preview: Preview = { parameters: { actions: { argTypesRegex: '^on[A-Z].*' }, backgrounds: { disable: true }, // Use colorMode instead + options: { showPanel: true }, // default to showing the controls panel controls: { expanded: true, sort: 'requiredFirst', @@ -100,12 +108,13 @@ const preview: Preview = { }, // Due to CommonProps, these props appear on almost every Story, but generally // aren't super useful to test - let's disable them by default and (if needed) - // individual stories can re-enable them - argTypes: { - css: { table: { disable: true } }, - className: { table: { disable: true } }, - 'data-test-subj': { table: { disable: true } }, - }, + // individual stories can re-enable them, e.g. by passing + // `argTypes: { 'data-test-subj': { table: { disable: false } } }` + argTypes: hideStorybookControls([ + 'css', + 'className', + 'data-test-subj', + ]), }; export default preview; diff --git a/.storybook/utils.test.ts b/.storybook/utils.test.ts new file mode 100644 index 00000000000..d570363a393 --- /dev/null +++ b/.storybook/utils.test.ts @@ -0,0 +1,49 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { hideStorybookControls, disableStorybookControls } from './utils'; + +describe('hideStorybookControls', () => { + it('outputs the expected `argTypes` object when passed prop name strings', () => { + expect( + hideStorybookControls(['isDisabled', 'isLoading', 'isInvalid']) + ).toEqual({ + isDisabled: { table: { disable: true } }, + isLoading: { table: { disable: true } }, + isInvalid: { table: { disable: true } }, + }); + }); + + it('throws a typescript error if a generic is passed and the prop names do not match', () => { + type TestComponentProps = { hello: boolean; world: boolean }; + // No typescript error + hideStorybookControls(['hello', 'world']); + // @ts-expect-error - will fail `yarn lint` if a TS error is *not* produced + hideStorybookControls(['hello', 'world', 'error']); + }); +}); + +describe('disableStorybookControls', () => { + it('outputs the expected `argTypes` object when passed prop name strings', () => { + expect( + disableStorybookControls(['isDisabled', 'isLoading', 'isInvalid']) + ).toEqual({ + isDisabled: { control: false }, + isLoading: { control: false }, + isInvalid: { control: false }, + }); + }); + + it('throws a typescript error if a generic is passed and the prop names do not match', () => { + type TestComponentProps = { hello: boolean; world: boolean }; + // No typescript error + disableStorybookControls(['hello', 'world']); + // @ts-expect-error - will fail `yarn lint` if a TS error is *not* produced + disableStorybookControls(['hello', 'world', 'error']); + }); +}); diff --git a/.storybook/utils.ts b/.storybook/utils.ts new file mode 100644 index 00000000000..517b93421c2 --- /dev/null +++ b/.storybook/utils.ts @@ -0,0 +1,64 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +/** + * argTypes configurations + */ + +/** + * Completely hide props from Storybook's controls panel. + * Should be passed or spread to `argTypes` + */ +export const hideStorybookControls = ( + propNames: Array +): Record | {} => { + return propNames.reduce( + (obj, name) => ({ ...obj, [name]: HIDE_CONTROL }), + {} + ); +}; +const HIDE_CONTROL = { table: { disable: true } }; + +/** + * Leave props visible in Storybook's controls panel, but disable them + * from being controllable (renders a `-`). + * + * Should be passed or spread to `argTypes` + */ +export const disableStorybookControls = ( + propNames: Array +): Record | {} => { + return propNames.reduce( + (obj, name) => ({ ...obj, [name]: DISABLE_CONTROL }), + {} + ); +}; +const DISABLE_CONTROL = { control: false }; + +/** + * parameters configurations + */ + +/** + * Will hide all props/controls. Pass to `parameters` + * + * TODO: Figure out some way to not show Storybook's "setup" text? + */ +export const hideAllStorybookControls = { + controls: { exclude: /.*/g }, +}; + +/** + * Will hide the control/addon panel entirely for a specific story. + * Should be passed or spread to to `parameters`. + * + * Note that users can choose to re-show the panel in the UI + */ +export const hidePanel = { + options: { showPanel: false }, +}; diff --git a/scripts/jest/config.js b/scripts/jest/config.js index 3174d93b6cb..84006d237aa 100644 --- a/scripts/jest/config.js +++ b/scripts/jest/config.js @@ -19,6 +19,7 @@ const config = { '/scripts/babel', '/scripts/tests', '/scripts/eslint-plugin', + '/.storybook', ], collectCoverageFrom: [ 'src/{components,services,global_styling}/**/*.{ts,tsx,js,jsx}', diff --git a/src/components/button/button_empty/button_empty.stories.tsx b/src/components/button/button_empty/button_empty.stories.tsx index fcbfdc29e62..ccf9cd2c12a 100644 --- a/src/components/button/button_empty/button_empty.stories.tsx +++ b/src/components/button/button_empty/button_empty.stories.tsx @@ -20,14 +20,8 @@ const meta: Meta = { }, iconType: { control: 'text' }, }, -}; - -export default meta; -type Story = StoryObj; - -export const Playground: Story = { args: { - children: 'Tertiary action', + // Component defaults color: 'primary', size: 'm', iconSize: 'm', @@ -37,3 +31,12 @@ export const Playground: Story = { isSelected: false, }, }; + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + children: 'Tertiary action', + }, +}; diff --git a/src/components/button/button_group/button_group.stories.tsx b/src/components/button/button_group/button_group.stories.tsx index 9e3c8cdd3cc..d4f0eef2928 100644 --- a/src/components/button/button_group/button_group.stories.tsx +++ b/src/components/button/button_group/button_group.stories.tsx @@ -8,6 +8,7 @@ import React, { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { disableStorybookControls } from '../../../../.storybook/utils'; import { EuiButtonGroup, @@ -19,11 +20,6 @@ const meta: Meta = { title: 'EuiButtonGroup', // @ts-ignore This still works for Storybook controls, even though Typescript complains component: EuiButtonGroup, - parameters: { - controls: { - exclude: ['data-test-subj'], - }, - }, argTypes: { type: { options: ['single', 'multi'], @@ -44,6 +40,15 @@ const meta: Meta = { control: 'select', }, }, + args: { + // Component defaults + type: 'single', + buttonSize: 's', + color: 'text', + isDisabled: false, + isFullWidth: false, + isIconOnly: false, + }, }; export default meta; @@ -76,6 +81,17 @@ const EuiButtonGroupSingle = (props: any) => { ); }; +export const SingleSelection: Story = { + render: ({ ...args }) => , + args: { + legend: 'EuiButtonGroup - single selection', + options, + type: 'single', + idSelected: 'button1', + }, + argTypes: disableStorybookControls(['type']), +}; + const EuiButtonGroupMulti = (props: any) => { const [idToSelectedMap, setIdToSelectedMap] = useState< Record @@ -100,24 +116,13 @@ const EuiButtonGroupMulti = (props: any) => { ); }; -export const Playground: Story = { - render: ({ ...args }) => { - if (args.type === 'multi') { - return ; - } else { - return ; - } - }, +export const MultiSelection: Story = { + render: ({ ...args }) => , args: { - legend: 'EuiButtonGroup demo', - type: 'single', + legend: 'EuiButtonGroup - multiple selections', options, - idSelected: 'button1', + type: 'multi', idToSelectedMap: { button1: true }, - buttonSize: 's', - color: 'text', - isDisabled: false, - isFullWidth: false, - isIconOnly: false, - } as any, + }, + argTypes: disableStorybookControls(['type']), }; diff --git a/src/components/button/button_icon/button_icon.stories.tsx b/src/components/button/button_icon/button_icon.stories.tsx index 29d2e49cf62..8ef364ab30a 100644 --- a/src/components/button/button_icon/button_icon.stories.tsx +++ b/src/components/button/button_icon/button_icon.stories.tsx @@ -13,14 +13,8 @@ import { EuiButtonIcon, EuiButtonIconProps } from './button_icon'; const meta: Meta = { title: 'EuiButtonIcon', component: EuiButtonIcon, -}; - -export default meta; -type Story = StoryObj; - -export const Playground: Story = { args: { - iconType: 'faceHappy', + // Component defaults color: 'primary', display: 'empty', size: 'xs', @@ -30,3 +24,12 @@ export const Playground: Story = { isSelected: false, }, }; + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + iconType: 'faceHappy', + }, +}; diff --git a/src/components/collapsible_nav/collapsible_nav.stories.tsx b/src/components/collapsible_nav/collapsible_nav.stories.tsx index 77a89909ea2..efefaf47e94 100644 --- a/src/components/collapsible_nav/collapsible_nav.stories.tsx +++ b/src/components/collapsible_nav/collapsible_nav.stories.tsx @@ -15,6 +15,13 @@ import { EuiCollapsibleNav, EuiCollapsibleNavProps } from './collapsible_nav'; const meta: Meta = { title: 'EuiCollapsibleNav', component: EuiCollapsibleNav, + args: { + // Component defaults + isDocked: false, + dockedBreakpoint: 'l', + showButtonIfDocked: false, + size: 320, + }, // TODO: Improve props inherited from EuiFlyout, ideally through // a DRY import from `flyout.stories.tsx` once that's created }; @@ -43,9 +50,5 @@ export const Playground: Story = { args: { children: 'Collapsible nav content', isOpen: true, - isDocked: false, - dockedBreakpoint: 'l', - showButtonIfDocked: false, - size: 240, }, }; diff --git a/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.stories.tsx b/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.stories.tsx index 4f761dc65a0..ae4d317761d 100644 --- a/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.stories.tsx +++ b/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.stories.tsx @@ -43,6 +43,14 @@ const meta: Meta = { isDisabled: { if: { arg: 'isCollapsible' } }, element: { if: { arg: 'isCollapsible' } }, }, + args: { + iconType: 'logoElastic', + // Component defaults + iconSize: 'l', + titleSize: 'xxs', + titleElement: 'h3', + background: 'none', + }, }; export default meta; @@ -51,12 +59,7 @@ type Story = StoryObj; export const Accordion: Story = { args: { children: 'This is an accordion group with a title', - background: 'none', title: 'Nav group - accordion', - iconType: 'logoElastic', - iconSize: 'l', - titleElement: 'h3', - titleSize: 'xxs', initialIsOpen: true, isCollapsible: true, }, @@ -65,12 +68,6 @@ export const Accordion: Story = { export const NonAccordion: StoryObj = { args: { children: 'This is a group with a title', - background: 'none', - title: 'Nav group - non-accordion', - iconType: 'logoElastic', - iconSize: 'l', - titleElement: 'h3', - titleSize: 'xxs', isCollapsible: false, }, }; @@ -78,6 +75,5 @@ export const NonAccordion: StoryObj = { export const NoTitle: Story = { args: { children: 'This is a group without a title', - background: 'none', }, }; diff --git a/src/components/collapsible_nav_beta/collapsible_nav_beta.stories.tsx b/src/components/collapsible_nav_beta/collapsible_nav_beta.stories.tsx index 15c1b1148f7..dc3bc82410b 100644 --- a/src/components/collapsible_nav_beta/collapsible_nav_beta.stories.tsx +++ b/src/components/collapsible_nav_beta/collapsible_nav_beta.stories.tsx @@ -8,6 +8,10 @@ import React, { FunctionComponent, PropsWithChildren, useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { + hideStorybookControls, + hideAllStorybookControls, +} from '../../../.storybook/utils'; import { EuiHeader, EuiHeaderSection, EuiHeaderSectionItem } from '../header'; import { EuiPageTemplate } from '../page_template'; @@ -35,6 +39,7 @@ const meta: Meta = { }, }, args: { + // Component defaults side: 'left', initialIsCollapsed: false, width: 248, @@ -417,21 +422,57 @@ export const SecurityExample: Story = { ), }; -export const MultipleFixedHeaders: Story = { - render: ({ ...args }) => ( +export const CollapsedStateInLocalStorage: Story = { + render: () => { + const key = 'EuiCollapsibleNav__isCollapsed'; + const initialIsCollapsed = window.localStorage.getItem(key) === 'true'; + const onCollapseToggle = (isCollapsed: boolean) => + window.localStorage.setItem(key, String(isCollapsed)); + + return ( + <> + + + + + + + + Toggle the collapsed state and refresh the page. The collapsed state + should have been saved/remembered + + + + ); + }, + argTypes: hideStorybookControls(['aria-label', 'side', 'width']), +}; + +export const GlobalCSSVariable: Story = { + render: ({ side, ...args }) => ( <> - First header - - - This story tests that EuiCollapsibleNav automatically adjusts its - position & height for multiple fixed headers + + + This story tests the global `--euiCollapsibleNavOffset` CSS variable - Second header + {/* In production, would just be `left="var(--euiCollapsibleNavOffset, 0)"` if the nav isn't changing sides */} + + This text should be visible at all times and the bar position should + update dynamically based on the nav width (including on mobile) + ), + argTypes: hideStorybookControls([ + 'aria-label', + 'initialIsCollapsed', + 'onCollapseToggle', + ]), }; const MockConsumerFlyout: FunctionComponent = () => { @@ -439,13 +480,13 @@ const MockConsumerFlyout: FunctionComponent = () => { return ( <> setFlyoutOpen(!flyoutIsOpen)}> - Toggle a flyout + Toggle flyout {flyoutIsOpen && ( setFlyoutOpen(false)}> - Some other mock consumer flyout that should overlap - EuiCollapsibleNav + This flyout's mask should overlay / sit on top of the collapsible + nav, on both desktop and mobile )} @@ -453,12 +494,14 @@ const MockConsumerFlyout: FunctionComponent = () => { ); }; -export const FlyoutInFixedHeaders: Story = { - render: ({ ...args }) => { +export const FlyoutOverlay: Story = { + render: (_) => { return ( - Nav content + + Click the "Toggle flyout" button in the top right hand corner + @@ -468,50 +511,5 @@ export const FlyoutInFixedHeaders: Story = { ); }, -}; - -export const GlobalCSSVariable: Story = { - render: ({ ...args }) => ( - <> - - - - This story tests the global `--euiCollapsibleNavOffset` CSS variable - - - - - This text should be visible at all times and the bar position should - update dynamically based on the nav width (including on mobile) - - - ), -}; - -export const CollapsedStateInLocalStorage: Story = { - render: () => { - const key = 'EuiCollapsibleNav__isCollapsed'; - const initialIsCollapsed = window.localStorage.getItem(key) === 'true'; - const onCollapseToggle = (isCollapsed: boolean) => - window.localStorage.setItem(key, String(isCollapsed)); - - return ( - <> - - - - - - - - Toggle the collapsed state and refresh the page. The collapsed state - should have been saved/remembered - - - - ); - }, + parameters: hideAllStorybookControls, }; diff --git a/src/components/empty_prompt/empty_prompt.stories.tsx b/src/components/empty_prompt/empty_prompt.stories.tsx index b71e1f06a46..dcb64852d34 100644 --- a/src/components/empty_prompt/empty_prompt.stories.tsx +++ b/src/components/empty_prompt/empty_prompt.stories.tsx @@ -21,25 +21,24 @@ import { EuiEmptyPrompt, EuiEmptyPromptProps } from './empty_prompt'; const meta: Meta = { title: 'EuiEmptyPrompt', component: EuiEmptyPrompt, + args: { + // Component defaults + titleSize: 'm', + paddingSize: 'l', + color: 'plain', // Default is actually 'transparent', but for the purposes of easier testing in Storybook we'll set it to plain + layout: 'vertical', + hasBorder: false, + hasShadow: false, + }, }; export default meta; type Story = StoryObj; -const componentDefaults: EuiEmptyPromptProps = { - titleSize: 'm', - paddingSize: 'l', - color: 'plain', // The component default is actually 'transparent', but for the purposes of easier testing in Storybook we'll set it to plain - layout: 'vertical', -}; - export const Playground: Story = { args: { - ...componentDefaults, title:

Start adding cases

, iconType: 'logoSecurity', - hasBorder: false, - hasShadow: false, body: 'Add a new case or change your filter settings.', // Should be wrapped in a `

` in production usage, but using a string makes this easier to edit in Storybook controls actions: [ @@ -67,7 +66,6 @@ export const PageTemplate: Story = { ), args: { - ...componentDefaults, title:

Create your first visualization

, layout: 'horizontal', icon: , diff --git a/src/components/error_boundary/error_boundary.stories.tsx b/src/components/error_boundary/error_boundary.stories.tsx index 54b011ba4a7..f62dffba499 100644 --- a/src/components/error_boundary/error_boundary.stories.tsx +++ b/src/components/error_boundary/error_boundary.stories.tsx @@ -11,31 +11,26 @@ import type { Meta, StoryObj } from '@storybook/react'; import { EuiErrorBoundary, EuiErrorBoundaryProps } from './error_boundary'; -const ErrorContent = () => { - throw new Error( - "I'm here to kick butt and chew bubblegum.\n\nAnd I'm all out of gum." - ); -}; - const meta: Meta = { title: 'EuiErrorBoundary', - component: () => ( - - - - ), + component: EuiErrorBoundary, parameters: { layout: 'fullscreen', }, - argTypes: { - onError: { - description: - 'TODO: extract prop descriptions, defaults, and types from Typescript ', - }, - }, }; export default meta; type Story = StoryObj; -export const Default: Story = {}; +const ErrorContent = () => { + throw new Error( + "I'm here to kick butt and chew bubblegum.\n\nAnd I'm all out of gum." + ); +}; + +export const Playground: Story = { + args: { + children: , + onError: console.log, + }, +}; diff --git a/src/components/filter_group/filter_button.stories.tsx b/src/components/filter_group/filter_button.stories.tsx index 49a2f3802ca..03eb27542b5 100644 --- a/src/components/filter_group/filter_button.stories.tsx +++ b/src/components/filter_group/filter_button.stories.tsx @@ -9,20 +9,26 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { BUTTON_COLORS } from '../../themes/amsterdam/global_styling/mixins'; + import { EuiFilterGroup } from './filter_group'; import { EuiFilterButton, EuiFilterButtonProps } from './filter_button'; const meta: Meta = { title: 'EuiFilterButton', component: EuiFilterButton as any, -}; - -const defaultProps = { - hasActiveFilters: true, - numFilters: 5, - iconType: 'arrowDown', - iconSide: 'right' as const, - isDisabled: false, + argTypes: { + color: { control: 'select', options: BUTTON_COLORS }, + }, + args: { + // Component defaults + iconSide: 'right', + color: 'text', + badgeColor: 'accent', + grow: true, + isSelected: false, + isDisabled: false, + }, }; export default meta; @@ -34,7 +40,11 @@ export const Playground: Story = { Filter ), - args: defaultProps, + args: { + hasActiveFilters: true, + numFilters: 5, + iconType: 'arrowDown', + }, }; export const MultipleButtons: Story = { @@ -45,7 +55,11 @@ export const MultipleButtons: Story = { Filter three ), - args: defaultProps, + args: { + hasActiveFilters: true, + numFilters: 5, + iconType: 'arrowDown', + }, }; export const FullWidthAndGrow: Story = { @@ -78,5 +92,4 @@ export const FullWidthAndGrow: Story = { ), - args: { isDisabled: false }, }; diff --git a/src/components/header/header.stories.tsx b/src/components/header/header.stories.tsx index 0e458880861..bb0b4a5bae8 100644 --- a/src/components/header/header.stories.tsx +++ b/src/components/header/header.stories.tsx @@ -26,18 +26,18 @@ import { EuiHeader, EuiHeaderProps } from './header'; const meta: Meta = { title: 'EuiHeader', component: EuiHeader, -}; - -export default meta; -type Story = StoryObj; - -export const Playground: Story = { args: { + // Component defaults position: 'static', theme: 'default', }, }; +export default meta; +type Story = StoryObj; + +export const Playground: Story = {}; + export const Sections: Story = { args: { position: 'fixed', diff --git a/src/components/header/header_alert/header_alert.stories.tsx b/src/components/header/header_alert/header_alert.stories.tsx index 1ffe3335566..91749eee728 100644 --- a/src/components/header/header_alert/header_alert.stories.tsx +++ b/src/components/header/header_alert/header_alert.stories.tsx @@ -40,26 +40,24 @@ import { EuiHeaderAlert, EuiHeaderAlertProps } from './header_alert'; const meta: Meta = { title: 'EuiHeaderAlert', component: EuiHeaderAlert, + args: { + // Not default props, set for demo purposes + title: '7.0 release notes', + date: 'January 1st 1970', + text: 'Stay up-to-date on the latest and greatest features.', + action: ( + + Check out the docs + + ), + badge: 7.0, + }, }; export default meta; type Story = StoryObj; -const defaultProps = { - title: '7.0 release notes', - date: 'January 1st 1970', - text: 'Stay up-to-date on the latest and greatest features.', - action: ( - - Check out the docs - - ), - badge: 7.0, -}; - -export const Playground: Story = { - args: defaultProps, -}; +export const Playground: Story = {}; /** * Flyout example @@ -121,7 +119,6 @@ const Flyout = (props: EuiHeaderAlertProps) => { }; export const FlyoutExample: Story = { render: ({ ...args }) => , - args: defaultProps, }; /** @@ -182,5 +179,4 @@ const Popover = (props: any) => { }; export const PopoverExample: Story = { render: ({ ...args }) => , - args: defaultProps, }; diff --git a/src/components/header/header_links/header_links.stories.tsx b/src/components/header/header_links/header_links.stories.tsx index 4a07d685a53..35ae96b895b 100644 --- a/src/components/header/header_links/header_links.stories.tsx +++ b/src/components/header/header_links/header_links.stories.tsx @@ -17,6 +17,11 @@ import { EuiHeaderLinks, EuiHeaderLinksProps } from './header_links'; const meta: Meta = { title: 'EuiHeaderLinks', component: EuiHeaderLinks, + args: { + // Component defaults + gutterSize: 's', + popoverBreakpoints: ['xs', 's'], + }, }; export default meta; @@ -36,8 +41,4 @@ export const Playground: Story = { ), - args: { - gutterSize: 's', - popoverBreakpoints: ['xs', 's'], - }, }; diff --git a/src/components/header/header_logo/header_logo.stories.tsx b/src/components/header/header_logo/header_logo.stories.tsx index 32eb29bf3ca..2ba258a2b84 100644 --- a/src/components/header/header_logo/header_logo.stories.tsx +++ b/src/components/header/header_logo/header_logo.stories.tsx @@ -15,6 +15,11 @@ import { EuiHeaderLogo, EuiHeaderLogoProps } from './header_logo'; const meta: Meta = { title: 'EuiHeaderLogo', component: EuiHeaderLogo, + args: { + // Not default props, set for demo purposes + iconType: 'logoElastic', + children: 'Elastic', + }, }; export default meta; @@ -28,10 +33,6 @@ export const Playground: Story = { ), - args: { - iconType: 'logoElastic', - iconTitle: 'Elastic', - }, }; export const WithText: Story = { @@ -42,8 +43,4 @@ export const WithText: Story = { ), - args: { - iconType: 'logoElastic', - children: 'Elastic', - }, }; diff --git a/src/components/key_pad_menu/key_pad_menu_item.stories.tsx b/src/components/key_pad_menu/key_pad_menu_item.stories.tsx index 051c15ad95f..c6e829f7057 100644 --- a/src/components/key_pad_menu/key_pad_menu_item.stories.tsx +++ b/src/components/key_pad_menu/key_pad_menu_item.stories.tsx @@ -19,6 +19,12 @@ const meta: Meta = { argTypes: { checkable: { options: [undefined, 'multi', 'single'] }, }, + args: { + label: 'Hello world', // String makes prop easier to change/toggle + // Component defaults + isDisabled: false, + isSelected: false, + }, }; export default meta; @@ -27,9 +33,5 @@ type Story = StoryObj; export const Playground: Story = { args: { children: , - // Make these props easier to change/toggle - label: 'Hello world', - isDisabled: false, - isSelected: false, }, }; diff --git a/src/components/page/page.stories.tsx b/src/components/page/page.stories.tsx index 7da4e143873..7b1415eb09a 100644 --- a/src/components/page/page.stories.tsx +++ b/src/components/page/page.stories.tsx @@ -8,6 +8,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { hideStorybookControls } from '../../../.storybook/utils'; import { EuiFlexGroup } from '../flex'; import { EuiSkeletonText } from '../skeleton'; @@ -19,23 +20,22 @@ import { EuiPage, EuiPageProps } from './page'; const meta: Meta = { title: 'EuiPage', component: EuiPage, + argTypes: { + restrictWidth: { control: 'boolean' }, + }, + args: { + // Component defaults + paddingSize: 'none', + grow: true, + direction: 'row', + restrictWidth: false, + }, }; export default meta; type Story = StoryObj; -const componentDefaults: EuiPageProps = { - paddingSize: 'none', - grow: true, - direction: 'row', - restrictWidth: false, -}; - export const Playground: Story = { - args: componentDefaults, - argTypes: { - restrictWidth: { control: 'boolean' }, - }, render: ({ ...args }) => ( ([ + 'grow', + 'direction', + 'paddingSize', + ]), render: ({ ...args }) => {_pageContent}, }; diff --git a/src/components/page/page_body/page_body.stories.tsx b/src/components/page/page_body/page_body.stories.tsx index 9c0229a919c..4e9621b5107 100644 --- a/src/components/page/page_body/page_body.stories.tsx +++ b/src/components/page/page_body/page_body.stories.tsx @@ -9,28 +9,31 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { BORDER_RADII, EuiPanelProps } from '../../panel/panel'; import { EuiSkeletonText } from '../../skeleton'; import { EuiPage } from '../page'; import { EuiPageBody, EuiPageBodyProps } from './page_body'; -const meta: Meta = { +const meta: Meta> = { title: 'EuiPageBody', component: EuiPageBody, + argTypes: { + borderRadius: { control: 'radio', options: BORDER_RADII }, + }, + args: { + // Component defaults + restrictWidth: false, + paddingSize: 'none', + borderRadius: 'none', + component: 'main', // This is not a component default, but for the purposes of easier testing in the DOM in Storybook we'll set it to main + }, }; export default meta; type Story = StoryObj; -const componentDefaults: EuiPageBodyProps = { - panelled: true, - restrictWidth: false, - paddingSize: 'm', // The component default is actually 'none', but for nicer visuals in Storybook we'll set it to 'm' - component: 'main', // This is not a component default, but for the purposes of easier testing in the DOM in Storybook we'll set it to main -}; - export const Playground: Story = { - args: componentDefaults, render: ({ ...args }) => ( @@ -43,4 +46,8 @@ export const Playground: Story = { ), + args: { + panelled: true, + paddingSize: 'm', + }, }; diff --git a/src/components/page/page_header/page_header.stories.tsx b/src/components/page/page_header/page_header.stories.tsx index aa0ec8a28d4..d692656150f 100644 --- a/src/components/page/page_header/page_header.stories.tsx +++ b/src/components/page/page_header/page_header.stories.tsx @@ -24,21 +24,20 @@ const meta: Meta = { breadcrumbProps: { control: 'object' }, tabsProps: { control: 'object' }, }, + args: { + // Component defaults + paddingSize: 'none', + responsive: true, + restrictWidth: false, + alignItems: undefined, + }, }; export default meta; type Story = StoryObj; -const componentDefaults: EuiPageHeaderProps = { - paddingSize: 'none', - responsive: true, - restrictWidth: false, - alignItems: undefined, -}; - export const Playground: Story = { args: { - ...componentDefaults, pageTitle: 'Page title', iconType: 'logoKibana', description: 'Example of a description.', diff --git a/src/components/page/page_section/page_section.stories.tsx b/src/components/page/page_section/page_section.stories.tsx index 13a939dc511..89559bc065c 100644 --- a/src/components/page/page_section/page_section.stories.tsx +++ b/src/components/page/page_section/page_section.stories.tsx @@ -19,22 +19,21 @@ const meta: Meta = { argTypes: { bottomBorder: { control: 'select', options: [true, false, 'extended'] }, }, + args: { + // Component defaults + restrictWidth: false, + color: 'plain', // The component default is actually 'transparent', but for the purposes of easier testing in Storybook we'll set it to plain + paddingSize: 'l', + alignment: 'top', + grow: false, + component: 'section', // This is not a component default, but for the purposes of easier testing in the DOM in Storybook we'll set it to section + }, }; export default meta; type Story = StoryObj; -const componentDefaults: EuiPageSectionProps = { - restrictWidth: false, - color: 'plain', // The component default is actually 'transparent', but for the purposes of easier testing in Storybook we'll set it to plain - paddingSize: 'l', - alignment: 'top', - grow: false, - component: 'section', // This is not a component default, but for the purposes of easier testing in the DOM in Storybook we'll set it to section -}; - export const Playground: Story = { - args: componentDefaults, render: ({ ...args }) => ( // Block size demos the grow prop diff --git a/src/components/page/page_sidebar/page_sidebar.stories.tsx b/src/components/page/page_sidebar/page_sidebar.stories.tsx index ec1096e72d7..5b0d1d7959c 100644 --- a/src/components/page/page_sidebar/page_sidebar.stories.tsx +++ b/src/components/page/page_sidebar/page_sidebar.stories.tsx @@ -8,6 +8,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { hideStorybookControls } from '../../../../.storybook/utils'; import { EuiSkeletonText } from '../../skeleton'; import { EuiPageSection } from '../page_section'; @@ -21,23 +22,22 @@ const meta: Meta = { parameters: { layout: 'fullscreen', }, + argTypes: { + sticky: { control: 'boolean' }, + }, + args: { + // Component defaults + paddingSize: 'm', // The component default is actually 'none', but for nicer visuals in Storybook we'll set it to 'm' + sticky: false, + minWidth: 248, + responsive: ['xs', 's'], + }, }; export default meta; type Story = StoryObj; -const componentDefaults: EuiPageSidebarProps = { - paddingSize: 'm', // The component default is actually 'none', but for nicer visuals in Storybook we'll set it to 'm' - sticky: false, - minWidth: 248, - responsive: ['xs', 's'], -}; - export const Playground: Story = { - args: componentDefaults, - argTypes: { - sticky: { control: 'boolean' }, - }, render: ({ ...args }) => ( ({ @@ -60,14 +60,18 @@ export const Playground: Story = { export const StickyOffset: Story = { args: { - ...componentDefaults, sticky: { offset: 50 }, }, argTypes: { + sticky: { + control: 'object', + }, // This story demos the sticky functionality; removing other props to prevent confusion - minWidth: { table: { disable: true } }, - paddingSize: { table: { disable: true } }, - responsive: { table: { disable: true } }, + ...hideStorybookControls([ + 'minWidth', + 'paddingSize', + 'responsive', + ]), }, render: ({ ...args }) => ( = { component: EuiSplitPanel.Inner, argTypes: { color: { control: 'select', options: COLORS }, - panelRef: { control: false }, + ...disableStorybookControls<_EuiSplitPanelInnerProps>(['panelRef']), + }, + args: { + // Component defaults + color: 'transparent', + paddingSize: 'm', + grow: true, }, }; @@ -25,12 +32,6 @@ export default meta; type Story = StoryObj<_EuiSplitPanelInnerProps>; export const SplitPanelInner: Story = { - args: { - // Default props - color: 'transparent', - paddingSize: 'm', - grow: true, - }, render: ({ ...args }) => ( Top panel diff --git a/src/components/panel/split_panel/split_panel_outer.stories.tsx b/src/components/panel/split_panel/split_panel_outer.stories.tsx index b612dfc8c3c..e6b89dc0254 100644 --- a/src/components/panel/split_panel/split_panel_outer.stories.tsx +++ b/src/components/panel/split_panel/split_panel_outer.stories.tsx @@ -14,17 +14,17 @@ import { EuiSplitPanel, _EuiSplitPanelOuterProps } from './split_panel'; const meta: Meta<_EuiSplitPanelOuterProps> = { title: 'EuiSplitPanel', component: EuiSplitPanel.Outer, + args: { + // Component defaults + direction: 'column', + responsive: ['xs', 's'], + }, }; export default meta; type Story = StoryObj<_EuiSplitPanelOuterProps>; export const SplitPanelOuter: Story = { - args: { - // Default props - direction: 'column', - responsive: ['xs', 's'], - }, render: ({ ...args }) => ( Top or left panel diff --git a/src/components/resizable_container/resizable_button.stories.tsx b/src/components/resizable_container/resizable_button.stories.tsx index c3a2c33fddf..4cd4b2261b0 100644 --- a/src/components/resizable_container/resizable_button.stories.tsx +++ b/src/components/resizable_container/resizable_button.stories.tsx @@ -19,6 +19,12 @@ import { const meta: Meta = { title: 'EuiResizableButton', component: EuiResizableButton, + args: { + // Component defaults + alignIndicator: 'center', + disabled: false, + isHorizontal: false, + }, }; export default meta; @@ -30,9 +36,4 @@ export const Playground: Story = { ), - args: { - isHorizontal: true, - alignIndicator: 'center', - disabled: false, - }, }; diff --git a/src/components/resizable_container/resizable_collapse_button.stories.tsx b/src/components/resizable_container/resizable_collapse_button.stories.tsx index 9b318e4291c..efc09176eff 100644 --- a/src/components/resizable_container/resizable_collapse_button.stories.tsx +++ b/src/components/resizable_container/resizable_collapse_button.stories.tsx @@ -8,6 +8,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { hideStorybookControls } from '../../../.storybook/utils'; import { EuiPanel } from '../panel'; import { EuiResizableContainer } from './resizable_container'; @@ -19,19 +20,20 @@ import { const meta: Meta = { title: 'EuiResizableCollapseButton', component: EuiResizableCollapseButton, -}; - -export default meta; -type Story = StoryObj; - -export const Playground: Story = { args: { + isVisible: true, + // Component defaults direction: 'horizontal', externalPosition: 'before', internalPosition: 'middle', - isVisible: true, isCollapsed: false, }, +}; + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { render: ({ isCollapsed, direction, ...args }) => ( ([ + 'externalPosition', + 'isVisible', + 'isCollapsed', + ]), render: ({ direction, internalPosition }) => ( {(EuiResizablePanel, EuiResizableButton) => ( diff --git a/src/components/side_nav/side_nav.stories.tsx b/src/components/side_nav/side_nav.stories.tsx index fdbfae7c9e1..9baf22f8336 100644 --- a/src/components/side_nav/side_nav.stories.tsx +++ b/src/components/side_nav/side_nav.stories.tsx @@ -8,6 +8,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { hideStorybookControls } from '../../../.storybook/utils'; import { EuiText } from '../text'; @@ -16,6 +17,12 @@ import { EuiSideNav, EuiSideNavProps } from './side_nav'; const meta: Meta = { title: 'EuiSideNav', component: EuiSideNav, + args: { + // Component defaults + mobileBreakpoints: ['xs', 's'], + items: [], + isOpenOnMobile: false, + }, decorators: [ (Story) => (
@@ -29,14 +36,6 @@ const meta: Meta = { export default meta; type Story = StoryObj; -const componentDefaults: EuiSideNavProps = { - mobileBreakpoints: ['xs', 's'], - items: [], - // mobileTitle does not have defaults; they are being set here as they are shared between examples - mobileTitle: 'Mobile navigation header', - isOpenOnMobile: false, -}; - const sharedSideNavItems = [ { name: 'Has nested children', @@ -83,29 +82,28 @@ const sharedSideNavItems = [ export const Playground: Story = { args: { - ...componentDefaults, heading: 'Elastic', headingProps: { element: 'h1', screenReaderOnly: false }, items: sharedSideNavItems, + mobileTitle: 'Mobile navigation header', }, }; export const MobileSideNav: Story = { args: { - ...componentDefaults, isOpenOnMobile: true, items: sharedSideNavItems, mobileTitle: 'Toggle isOpenOnMobile in the controls panel', }, - argTypes: { - // This story demos the side nav on smaller screens; removing other props to streamline controls - 'aria-label': { table: { disable: true } }, - heading: { table: { disable: true } }, - headingProps: { table: { disable: true } }, - items: { table: { disable: true } }, - renderItem: { table: { disable: true } }, - truncate: { table: { disable: true } }, - }, + // This story demos the side nav on smaller screens; removing other props to streamline controls + argTypes: hideStorybookControls([ + 'aria-label', + 'heading', + 'headingProps', + 'items', + 'renderItem', + 'truncate', + ]), parameters: { viewport: { defaultViewport: 'mobile1', @@ -115,7 +113,6 @@ export const MobileSideNav: Story = { export const RenderItem: Story = { args: { - ...componentDefaults, renderItem: ({ children }) => {children}, items: [ { @@ -135,15 +132,15 @@ export const RenderItem: Story = { }, ], }, - argTypes: { - // This story demos the renderItem prop; removing other props to streamline controls - 'aria-label': { table: { disable: true } }, - heading: { table: { disable: true } }, - headingProps: { table: { disable: true } }, - toggleOpenOnMobile: { table: { disable: true } }, - isOpenOnMobile: { table: { disable: true } }, - mobileBreakpoints: { table: { disable: true } }, - mobileTitle: { table: { disable: true } }, - truncate: { table: { disable: true } }, - }, + // This story demos the renderItem prop; removing other props to streamline controls + argTypes: hideStorybookControls([ + 'aria-label', + 'heading', + 'headingProps', + 'toggleOpenOnMobile', + 'isOpenOnMobile', + 'mobileBreakpoints', + 'mobileTitle', + 'truncate', + ]), }; diff --git a/src/components/text_truncate/text_truncate.stories.tsx b/src/components/text_truncate/text_truncate.stories.tsx index 82efe8a7755..d72d8f8cd90 100644 --- a/src/components/text_truncate/text_truncate.stories.tsx +++ b/src/components/text_truncate/text_truncate.stories.tsx @@ -9,6 +9,10 @@ import React, { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { css } from '@emotion/react'; +import { + hideStorybookControls, + disableStorybookControls, +} from '../../../.storybook/utils'; import { EuiHighlight, EuiMark } from '../../components'; @@ -21,17 +25,17 @@ const meta: Meta = { truncationOffset: { if: { arg: 'truncation', neq: 'startEnd' } }, // Should also not show on `middle`, but Storybook doesn't currently support multiple if conditions :( truncationPosition: { if: { arg: 'truncation', eq: 'startEnd' } }, }, + args: { + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', + // Component defaults + truncation: 'end', + ellipsis: '…', + }, }; export default meta; type Story = StoryObj; -const componentDefaults = { - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', - truncation: 'end', - ellipsis: '…', -} as const; - export const Playground: Story = { render: (props) => (
@@ -39,7 +43,6 @@ export const Playground: Story = {
), args: { - ...componentDefaults, width: 200, }, }; @@ -66,12 +69,9 @@ export const ResizeObserver: Story = { ), args: { - ...componentDefaults, onResize: console.log, }, - argTypes: { - width: { control: false }, - }, + argTypes: disableStorybookControls(['width']), }; export const StartEndAnchorForSearch: Story = { @@ -116,18 +116,17 @@ export const StartEndAnchorForSearch: Story = { ); }, args: { - ...componentDefaults, width: 200, truncation: 'startEnd', truncationPosition: 30, }, - argTypes: { + argTypes: hideStorybookControls([ // Disable uncontrollable props - truncation: { table: { disable: true } }, - truncationPosition: { table: { disable: true } }, + 'truncation', + 'truncationPosition', // Disable props that aren't useful for this this demo - truncationOffset: { table: { disable: true } }, - children: { table: { disable: true } }, - onResize: { table: { disable: true } }, - }, + 'truncationOffset', + 'children', + 'onResize', + ]), };