diff --git a/.storybook/utils.test.ts b/.storybook/utils.test.ts index c8d8ee9db33..20201423aeb 100644 --- a/.storybook/utils.test.ts +++ b/.storybook/utils.test.ts @@ -6,11 +6,14 @@ * Side Public License, v 1. */ -import { +import * as utils from './utils'; + +const { hideStorybookControls, disableStorybookControls, moveStorybookControlsToCategory, -} from './utils'; + enableFunctionToggleControls, +} = utils; describe('hideStorybookControls', () => { it('updates the provided config with the expected `argTypes` object when passed prop name strings', () => { @@ -198,3 +201,67 @@ describe('moveStorybookControlsToCategory', () => { ]); }); }); + +describe('enableFunctionToggleControls', () => { + it('updates the provided config with the expected `argTypes` object when passed function prop name strings', () => { + expect(enableFunctionToggleControls({ argTypes: {} }, ['onClick'])).toEqual( + { + args: { + onClick: true, + }, + argTypes: { + onClick: { + control: 'boolean', + mapping: { false: undefined, true: expect.any(Function) }, + }, + }, + parameters: { actions: { argTypesRegex: null } }, + } + ); + }); + + it('merges existing and new `argTypes` objects correctly', () => { + type TestProps = { hello: boolean; onHello: () => {} }; + + expect( + enableFunctionToggleControls( + { + args: { hello: true }, + argTypes: { + isDisabled: { control: { type: 'boolean' } }, + }, + }, + ['onHello'] + ) + ).toEqual({ + args: { + hello: true, + onHello: true, + }, + argTypes: { + isDisabled: { control: { type: 'boolean' } }, + onHello: { + control: 'boolean', + mapping: { false: undefined, true: expect.any(Function) }, + }, + }, + parameters: { actions: { argTypesRegex: null } }, + }); + }); + + it('throws a typescript error if a generic is passed and the prop names do not match', () => { + type TestProps = { hello: boolean; onHello: () => {} }; + + // No typescript error + enableFunctionToggleControls({ argTypes: {} }, [ + 'hello', + 'onHello', + ]); + enableFunctionToggleControls({ argTypes: {} }, [ + 'hello', + 'onHello', + // @ts-expect-error - will fail `yarn lint` if a TS error is *not* produced + 'error', + ]); + }); +}); diff --git a/.storybook/utils.ts b/.storybook/utils.ts index 2c1bfa6336f..ecb91fa4cb0 100644 --- a/.storybook/utils.ts +++ b/.storybook/utils.ts @@ -11,6 +11,7 @@ */ import type { Args, ArgTypes, Meta, Preview, StoryObj } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; type StorybookConfig = Meta | StoryObj | Preview; @@ -27,10 +28,12 @@ export const hideStorybookControls = ( config: StorybookConfig, propNames: Array ): StorybookConfig => { - const updatedConfig = _updateArgTypes(config, propNames, { - key: 'table', - value: { disable: true }, - }); + const updatedConfig = _updateArgTypes(config, propNames, [ + { + key: 'table', + value: { disable: true }, + }, + ]); return updatedConfig; }; @@ -49,10 +52,12 @@ export const disableStorybookControls = ( config: StorybookConfig, propNames: Array ): StorybookConfig => { - const updatedConfig = _updateArgTypes(config, propNames, { - key: 'control', - value: false, - }); + const updatedConfig = _updateArgTypes(config, propNames, [ + { + key: 'control', + value: false, + }, + ]); return updatedConfig; }; @@ -72,11 +77,70 @@ export const moveStorybookControlsToCategory = ( propNames: Array, category = 'Additional' ): StorybookConfig => { - const updatedConfig = _updateArgTypes(config, propNames, { - key: 'table', - value: { category }, + const updatedConfig = _updateArgTypes(config, propNames, [ + { + key: 'table', + value: { category }, + }, + ]); + + return updatedConfig; +}; + +/** + * Configures passed argTypes to be setup as toggle control + * which fires a Storybook action when enabled. + * Should be used for function props only. + * + * Can be used for preview (Preview), component (Meta) or story (Story) + * context by passing the config object for either. Use after defining + * the specific config to be able to pass the config to this util. + * + * @returns the mutated config + */ +export const enableFunctionToggleControls = ( + config: StorybookConfig, + propNames: Array +) => { + const setAction = (propName: string | number) => ({ + true: action(propName.toString()), + false: undefined, }); + /* Sets the default value for the passed function prop. + This is needed to ensure the coolean control is set and + to prevent additional clicks. + NOTE: This has to happen before the argTypes are updated */ + config.args = propNames.reduce( + (acc, propName) => ({ + ...acc, + [propName]: true, + }), + config.args + ); + + let updatedConfig = _updateArgTypes(config, propNames, [ + { key: 'control', value: 'boolean' }, + { + key: 'mapping', + value: setAction, + }, + ]); + + updatedConfig = { + ...updatedConfig, + /* Overwrites global parameters.actions setting in preview.tsx which enables + actions on function props starting with "on[Name]" by default. This is needed + to ensure the default "false" state is actually false. */ + parameters: { + ...updatedConfig.parameters, + actions: { + ...updatedConfig.parameters?.actions, + argTypesRegex: null, + }, + }, + }; + return updatedConfig; }; @@ -112,29 +176,43 @@ export const hidePanel = { const _updateArgTypes = ( config: StorybookConfig, propNames: Array, - { - key, - value, - }: { key: string; value: Record | boolean } + controls: Array<{ + key: string; + value: + | Record + | boolean + | string + | ((propName: any) => Record); + }> ): StorybookConfig => { const currentArgTypes = config.argTypes as Partial>; const newArgTypes = { ...currentArgTypes }; for (const propName of propNames) { - const currentArgTypeValue = newArgTypes?.[propName] ?? ({} as Args); - const currentControlValue = currentArgTypeValue.hasOwnProperty(key) - ? currentArgTypeValue[key] - : ({} as Record); - - const newValue = - typeof value === 'object' && typeof currentArgTypeValue[key] === 'object' - ? { ...currentControlValue, ...value } - : value; - - newArgTypes[propName] = { - ...currentArgTypeValue, - [key]: newValue, - }; + for (const { key, value } of controls) { + const currentArgTypeValue = newArgTypes?.[propName] ?? ({} as Args); + const currentControlValue = currentArgTypeValue.hasOwnProperty(key) + ? currentArgTypeValue[key] + : ({} as Record); + + let newValue = value; + + if (typeof value === 'function') { + newValue = value(propName); + } + + if ( + typeof value === 'object' && + typeof currentArgTypeValue[key] === 'object' + ) { + newValue = { ...currentControlValue, ...value }; + } + + newArgTypes[propName] = { + ...currentArgTypeValue, + [key]: newValue, + }; + } } config.argTypes = newArgTypes; diff --git a/changelogs/upcoming/7648.md b/changelogs/upcoming/7648.md new file mode 100644 index 00000000000..4159ce0d2b2 --- /dev/null +++ b/changelogs/upcoming/7648.md @@ -0,0 +1,4 @@ +**Bug fixes** + +- Fixed an `EuiPageTemplate` bug where prop updates would not cascade down to child sections + - To cascade props down to the sidebar, `EuiPageTemplate` now explicitly requires using the `EuiPageTemplate.Sidebar` rather than `EuiPageSidebar` diff --git a/src/components/page/page_header/page_header.stories.tsx b/src/components/page/page_header/page_header.stories.tsx index f48ad74436d..20dfd51d79d 100644 --- a/src/components/page/page_header/page_header.stories.tsx +++ b/src/components/page/page_header/page_header.stories.tsx @@ -9,6 +9,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { moveStorybookControlsToCategory } from '../../../../.storybook/utils'; import { EuiButton } from '../../button'; import { EuiPageHeader, EuiPageHeaderProps } from '../page_header'; @@ -23,6 +24,10 @@ const meta: Meta = { pageTitleProps: { control: 'object' }, breadcrumbProps: { control: 'object' }, tabsProps: { control: 'object' }, + restrictWidth: { + control: 'select', + options: [true, false, 500, 900, 1800, '25%', '50%', '75%'], + }, }, args: { // Component defaults @@ -33,9 +38,55 @@ const meta: Meta = { }, }; +moveStorybookControlsToCategory( + meta, + [ + 'pageTitle', + 'pageTitleProps', + 'iconType', + 'iconProps', + 'breadcrumbs', + 'breadcrumbProps', + 'tabs', + 'tabsProps', + 'description', + 'responsive', + 'alignItems', + 'rightSideItems', + 'rightSideGroupProps', + 'children', + ], + 'EuiPageHeaderContent props' +); + export default meta; type Story = StoryObj; +const tabs = [ + { + label: 'Tab 1', + isSelected: true, + }, + { + label: 'Tab 2', + }, +]; + +const breadcrumbs = [ + { + text: 'Breadcrumb 1', + href: '#', + }, + { + text: 'Breadcrumb 2', + href: '#', + }, + { + text: 'Current', + href: '#', + }, +]; + export const Playground: Story = { args: { pageTitle: 'Page title', @@ -46,28 +97,7 @@ export const Playground: Story = { Add something, Do something, ], - tabs: [ - { - label: 'Tab 1', - isSelected: true, - }, - { - label: 'Tab 2', - }, - ], - breadcrumbs: [ - { - text: 'Breadcrumb 1', - href: '#', - }, - { - text: 'Breadcrumb 2', - href: '#', - }, - { - text: 'Current', - href: '#', - }, - ], + tabs, + breadcrumbs, }, }; diff --git a/src/components/page/page_header/page_header_content.stories.tsx b/src/components/page/page_header/page_header_content.stories.tsx new file mode 100644 index 00000000000..cd305101d04 --- /dev/null +++ b/src/components/page/page_header/page_header_content.stories.tsx @@ -0,0 +1,76 @@ +/* + * 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 React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { hideStorybookControls } from '../../../../.storybook/utils'; +import { EuiButton } from '../../button'; +import { + EuiPageHeaderContent, + EuiPageHeaderContentProps, +} from './page_header_content'; + +const meta: Meta = { + title: 'Layout/EuiPage/EuiPageHeader/EuiPageHeaderContent', + component: EuiPageHeaderContent, + argTypes: { + alignItems: { + control: 'select', + options: ['center', 'bottom', 'top', 'stretch', undefined], + }, + }, + args: { + // Component defaults + paddingSize: 'none', + responsive: true, + restrictWidth: false, + alignItems: undefined, + bottomBorder: false, + }, +}; +hideStorybookControls(meta, ['aria-label']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + pageTitle: 'Page title', + iconType: 'logoKibana', + description: 'Example of a description.', + bottomBorder: false, + rightSideItems: [ + Add something, + Do something, + ], + tabs: [ + { + label: 'Tab 1', + isSelected: true, + }, + { + label: 'Tab 2', + }, + ], + breadcrumbs: [ + { + text: 'Breadcrumb 1', + href: '#', + }, + { + text: 'Breadcrumb 2', + href: '#', + }, + { + text: 'Current', + href: '#', + }, + ], + }, +}; diff --git a/src/components/page_template/outer/page_outer.tsx b/src/components/page_template/outer/page_outer.tsx index b61aad83ad3..05e7a325ef5 100644 --- a/src/components/page_template/outer/page_outer.tsx +++ b/src/components/page_template/outer/page_outer.tsx @@ -12,6 +12,9 @@ import { useEuiTheme, useIsWithinBreakpoints } from '../../../services'; import { _EuiThemeBreakpoint } from '../../../global_styling'; import { euiPageOuterStyles } from './page_outer.styles'; +export const PAGE_DIRECTIONS = ['row', 'column'] as const; +type PageDirections = (typeof PAGE_DIRECTIONS)[number]; + export interface _EuiPageOuterProps extends CommonProps, HTMLAttributes { @@ -24,7 +27,7 @@ export interface _EuiPageOuterProps * Changes the `flex-direction` property. * Flip to `column` when not including a sidebar. */ - direction?: 'row' | 'column'; + direction?: PageDirections; /** * When direction is `row`, it will flip to `column` when within these breakpoints. */ diff --git a/src/components/page_template/page_template.stories.tsx b/src/components/page_template/page_template.stories.tsx new file mode 100644 index 00000000000..b97f43be46b --- /dev/null +++ b/src/components/page_template/page_template.stories.tsx @@ -0,0 +1,154 @@ +/* + * 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 React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { + hideStorybookControls, + moveStorybookControlsToCategory, +} from '../../../.storybook/utils'; +import { EuiSkeletonText } from '../skeleton'; +import { EuiButton } from '../button'; +import { EuiText } from '../text'; + +import { EuiPageTemplate, EuiPageTemplateProps } from './page_template'; +import { PAGE_DIRECTIONS } from './outer/page_outer'; + +const headerContent = ( + Button]} + description="Page header example description" + tabs={[{ label: 'Tab 1', isSelected: true }, { label: 'Tab 2' }]} + /> +); +const sectionContent = ( + <> + + + + Stack EuiPageTemplate sections and headers to create your custom + content order. + + + + + + + +); +const sidebarContent = ( + + + +); +const bottomBarContent = ( + + + +); +const emptyPromptContent = ( + Empty prompt!} + footer={Button} + > + + +); + +const meta: Meta = { + title: 'Templates/EuiPageTemplate', + component: EuiPageTemplate, + parameters: { + layout: 'fullscreen', + }, + argTypes: { + bottomBorder: { + control: 'radio', + options: [undefined, true, false, 'extended'], + }, + panelled: { control: 'radio', options: [undefined, true, false] }, + direction: { + control: 'radio', + options: [undefined, ...PAGE_DIRECTIONS], + }, + component: { control: 'text' }, + contentBorder: { control: 'radio', options: [undefined, true, false] }, + }, + args: { + minHeight: '460px', + responsive: ['xs', 's'], + paddingSize: 'l', + grow: true, + restrictWidth: true, + component: 'main', + }, +}; +moveStorybookControlsToCategory( + meta, + ['minHeight', 'grow', 'direction', 'responsive'], + 'Outer props' +); +moveStorybookControlsToCategory( + meta, + ['contentBorder', 'component', 'mainProps'], + 'Inner props' +); +hideStorybookControls(meta, ['aria-label']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + children: 'With everything', + }, + argTypes: { + restrictWidth: { + control: 'select', + options: [true, false, 500, 900, 1800, '25%', '50%', '75%'], + }, + children: { + control: 'select', + description: + 'For quicker testing, use the selection control to the right to select several examples of common EuiPageTemplate layouts', + options: [ + 'With everything', + 'Without sidebar', + 'Without header', + 'Without bottom bar', + 'With empty prompt content', + ], + mapping: { + 'With everything': [ + sidebarContent, + headerContent, + sectionContent, + bottomBarContent, + ], + 'Without sidebar': [headerContent, sectionContent, bottomBarContent], + 'Without header': [sidebarContent, sectionContent, bottomBarContent], + 'Without bottom bar': [sidebarContent, headerContent, sectionContent], + 'With empty prompt content': [ + sidebarContent, + headerContent, + emptyPromptContent, + bottomBarContent, + ], + }, + }, + }, + // using render() over args to ensure dynamic update on prop changes + // Cee TODO: This doesn't appear to work for the `paddingSize` and `bottomBorder` props + // render: ({ ...args }) => , +}; diff --git a/src/components/page_template/page_template.tsx b/src/components/page_template/page_template.tsx index 9f9b4b959f4..1c8819da37f 100644 --- a/src/components/page_template/page_template.tsx +++ b/src/components/page_template/page_template.tsx @@ -12,6 +12,7 @@ import React, { FunctionComponent, HTMLAttributes, useContext, + useMemo, } from 'react'; import classNames from 'classnames'; @@ -32,14 +33,16 @@ import { EuiPageSection, EuiPageSectionProps, EuiPageSidebar, + EuiPageSidebarProps, } from '../page'; import { _EuiPageRestrictWidth } from '../page/_restrict_width'; import { _EuiPageBottomBorder } from '../page/_bottom_border'; import { useGeneratedHtmlId } from '../../services'; -import { logicalStyle } from '../../global_styling'; +import { logicalStyles } from '../../global_styling'; import { CommonProps } from '../common'; export const TemplateContext = createContext({ + sidebar: {}, section: {}, header: {}, emptyPrompt: {}, @@ -98,10 +101,9 @@ export const _EuiPageTemplate: FunctionComponent = ({ // Outer props className, minHeight = '460px', + style, ...rest }) => { - const templateContext = useContext(TemplateContext); - // Used as a target to insert the bottom bar component const pageInnerId = useGeneratedHtmlId({ prefix: 'EuiPageTemplateInner', @@ -109,82 +111,79 @@ export const _EuiPageTemplate: FunctionComponent = ({ }); // Sections include page header - const sections: React.ReactElement[] = []; - const sidebar: React.ReactElement[] = []; - - const getBottomBorder = () => { - if (bottomBorder !== undefined) { - return bottomBorder; - } else { - return sidebar.length ? true : 'extended'; - } - }; + const [sidebar, sections] = useMemo(() => { + const sidebar: React.ReactElement[] = []; + const sections: React.ReactElement[] = []; + + React.Children.toArray(children).forEach((child) => { + if (!React.isValidElement(child)) return; // Skip non-components + + if ( + child.type === _EuiPageSidebar || + child.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__ === _EuiPageSidebar + ) { + sidebar.push(child); + } else { + sections.push(child); + } + }); + + return [sidebar, sections]; + }, [children]); - const getHeaderProps = () => ({ - restrictWidth, - paddingSize, - bottomBorder: getBottomBorder(), - }); + const classes = classNames('euiPageTemplate', className); + const pageStyle = useMemo( + () => + logicalStyles({ + minHeight: grow ? `max(${minHeight}, 100vh)` : minHeight, + paddingTop: offset ?? 'var(--euiFixedHeadersOffset, 0)', + ...style, + }), + [minHeight, grow, offset, style] + ); - const getSectionProps = (): EuiPageSectionProps => ({ + const innerPanelled = panelled ?? Boolean(sidebar.length > 0); + const innerBordered = contentBorder ?? Boolean(sidebar.length > 0); + const headerBottomBorder = + bottomBorder ?? (sidebar.length ? true : 'extended'); + + const templateContext = useMemo(() => { + return { + sidebar: { + paddingSize, + responsive, + }, + header: { + restrictWidth, + paddingSize, + bottomBorder: headerBottomBorder, + }, + section: { + restrictWidth, + paddingSize, + color: panelled === false ? 'transparent' : 'plain', + grow: true, + }, + emptyPrompt: { + panelled: innerPanelled ? true : panelled, + grow: true, + }, + bottomBar: { + restrictWidth, + paddingSize, + // pageInnerId may contain colons that are parsed as pseudo-elements if not escaped + parent: `#${pageInnerId.replaceAll(':', '\\:')}`, + }, + }; + }, [ + pageInnerId, restrictWidth, - paddingSize, - color: panelled === false ? 'transparent' : 'plain', - }); - - const getSideBarProps = () => ({ - paddingSize, responsive, - }); - - const getBottomBarProps = () => ({ - restrictWidth, paddingSize, - // pageInnerId may contain colons that are parsed as pseudo-elements if not escaped - parent: `#${pageInnerId.replaceAll(':', '\\:')}`, - }); - - const innerPanelled = () => panelled ?? Boolean(sidebar.length > 0); - - const innerBordered = () => - contentBorder !== undefined ? contentBorder : Boolean(sidebar.length > 0); - - React.Children.toArray(children).forEach((child, index) => { - if (!React.isValidElement(child)) return; // Skip non-components - - if ( - child.type === EuiPageSidebar || - child.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__ === EuiPageSidebar - ) { - sidebar.push( - React.cloneElement(child, { - key: `sidebar${index}`, - ...getSideBarProps(), - // Allow their props overridden by appending the child props spread at the end - ...child.props, - }) - ); - } else { - sections.push(child); - } - }); - - const _minHeight = grow ? `max(${minHeight}, 100vh)` : minHeight; - - const classes = classNames('euiPageTemplate', className); - const pageStyle = { - ...logicalStyle('min-height', _minHeight), - ...logicalStyle('padding-top', offset ?? 'var(--euiFixedHeadersOffset, 0)'), - ...rest.style, - }; - - templateContext.header = getHeaderProps(); - templateContext.section = getSectionProps(); - templateContext.emptyPrompt = { - panelled: innerPanelled() ? true : panelled, - grow: true, - }; - templateContext.bottomBar = getBottomBarProps(); + panelled, + innerPanelled, + headerBottomBorder, + ]); return ( @@ -200,8 +199,8 @@ export const _EuiPageTemplate: FunctionComponent = ({ {...mainProps} component={component} id={pageInnerId} - border={innerBordered()} - panelled={innerPanelled()} + border={innerBordered} + panelled={innerPanelled} responsive={responsive} > {sections} @@ -211,24 +210,30 @@ export const _EuiPageTemplate: FunctionComponent = ({ ); }; +const _EuiPageSidebar: FunctionComponent = (props) => { + const { sidebar } = useContext(TemplateContext); + + return ; +}; + const _EuiPageSection: FunctionComponent = (props) => { - const templateContext = useContext(TemplateContext); + const { section } = useContext(TemplateContext); - return ; + return ; }; const _EuiPageHeader: FunctionComponent = (props) => { - const templateContext = useContext(TemplateContext); + const { header } = useContext(TemplateContext); - return ; + return ; }; const _EuiPageEmptyPrompt: FunctionComponent<_EuiPageEmptyPromptProps> = ( props ) => { - const templateContext = useContext(TemplateContext); + const { emptyPrompt } = useContext(TemplateContext); - return ; + return ; }; const _EuiPageBottomBar: FunctionComponent<_EuiPageBottomBarProps> = ( @@ -240,7 +245,7 @@ const _EuiPageBottomBar: FunctionComponent<_EuiPageBottomBarProps> = ( }; export const EuiPageTemplate = Object.assign(_EuiPageTemplate, { - Sidebar: EuiPageSidebar, + Sidebar: _EuiPageSidebar, Header: _EuiPageHeader, Section: _EuiPageSection, BottomBar: _EuiPageBottomBar, diff --git a/src/components/pagination/pagination.stories.tsx b/src/components/pagination/pagination.stories.tsx new file mode 100644 index 00000000000..b9c061daaaa --- /dev/null +++ b/src/components/pagination/pagination.stories.tsx @@ -0,0 +1,34 @@ +/* + * 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 type { Meta, StoryObj } from '@storybook/react'; + +import { enableFunctionToggleControls } from '../../../.storybook/utils'; +import { EuiPagination, EuiPaginationProps } from './pagination'; + +const meta: Meta = { + title: 'Navigation/EuiPagination/EuiPagination', + component: EuiPagination, + args: { + activePage: 0, + pageCount: 1, + responsive: ['xs', 's'], + compressed: false, + }, +}; +enableFunctionToggleControls(meta, ['onPageClick']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + activePage: 5, + pageCount: 10, + }, +}; diff --git a/src/components/pagination/pagination_button.stories.tsx b/src/components/pagination/pagination_button.stories.tsx new file mode 100644 index 00000000000..c0fa9f1d15b --- /dev/null +++ b/src/components/pagination/pagination_button.stories.tsx @@ -0,0 +1,69 @@ +/* + * 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 type { Meta, StoryObj } from '@storybook/react'; + +import { + disableStorybookControls, + enableFunctionToggleControls, + hideStorybookControls, + moveStorybookControlsToCategory, +} from '../../../.storybook/utils'; +import { + EuiPaginationButton, + EuiPaginationButtonProps, +} from './pagination_button'; + +const meta: Meta = { + title: 'Navigation/EuiPagination/EuiPaginationButton', + component: EuiPaginationButton, + argTypes: { + iconType: { control: 'text' }, + target: { control: 'text' }, + }, +}; +enableFunctionToggleControls(meta, ['onClick']); +moveStorybookControlsToCategory( + meta, + [ + 'buttonRef', + 'contentProps', + 'color', + 'flush', + 'href', + 'iconSide', + 'iconSize', + 'iconType', + 'isActive', + 'isDisabled', + 'isLoading', + 'isSelected', + 'onClick', + 'rel', + 'size', + 'target', + 'textProps', + 'type', + ], + 'EuiButtonEmpty props' +); +disableStorybookControls(meta, ['buttonRef']); +hideStorybookControls(meta, ['aria-label']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + pageIndex: 0, + isActive: false, + isDisabled: false, + isSelected: false, + isLoading: false, + }, +}; diff --git a/src/components/pagination/pagination_button_arrow.stories.tsx b/src/components/pagination/pagination_button_arrow.stories.tsx new file mode 100644 index 00000000000..b2ceadd0c67 --- /dev/null +++ b/src/components/pagination/pagination_button_arrow.stories.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 type { Meta, StoryObj } from '@storybook/react'; + +import { + disableStorybookControls, + enableFunctionToggleControls, + hideStorybookControls, + moveStorybookControlsToCategory, +} from '../../../.storybook/utils'; +import { + EuiPaginationButtonArrow, + Props as EuiPaginationButtonArrowProps, +} from './pagination_button_arrow'; + +const meta: Meta = { + title: 'Navigation/EuiPagination/EuiPaginationButtonArrow', + component: EuiPaginationButtonArrow, +}; +enableFunctionToggleControls(meta, ['onClick']); +moveStorybookControlsToCategory(meta, ['buttonRef'], 'EuiButtonEmpty props'); +disableStorybookControls(meta, ['buttonRef']); +hideStorybookControls(meta, ['aria-label']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + type: 'next', + disabled: false, + }, +}; diff --git a/src/components/panel/panel.stories.tsx b/src/components/panel/panel.stories.tsx new file mode 100644 index 00000000000..03e3296a637 --- /dev/null +++ b/src/components/panel/panel.stories.tsx @@ -0,0 +1,47 @@ +/* + * 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 type { Meta, StoryObj } from '@storybook/react'; + +import { + disableStorybookControls, + enableFunctionToggleControls, +} from '../../../.storybook/utils'; +import { EuiPanel, EuiPanelProps } from './panel'; + +const meta: Meta = { + title: 'Layout/EuiPanel', + component: EuiPanel, + argTypes: { + element: { + options: [undefined, 'div', 'button'], + }, + ['aria-label']: { + if: { arg: 'onClick', eq: true }, + }, + }, + args: { + paddingSize: 'm', + borderRadius: 'm', + color: 'plain', + hasShadow: true, + hasBorder: false, + grow: true, + }, +}; +enableFunctionToggleControls(meta, ['onClick']); +disableStorybookControls(meta, ['panelRef']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + children: 'Panel content', + }, +};