From c4bd1b10c3b6aee26697a35f30c4ea9f427ac5be Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Fri, 14 Jul 2023 08:40:37 -0700 Subject: [PATCH 01/13] [setup] Misc EuiPortal cleanup - we're going to need this extra specificity shortly for when we add internal context defaults --- .../component_defaults/component_defaults.test.tsx | 10 ++++------ .../provider/component_defaults/component_defaults.tsx | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/provider/component_defaults/component_defaults.test.tsx b/src/components/provider/component_defaults/component_defaults.test.tsx index f35187ed0e3..bb85b633c6c 100644 --- a/src/components/provider/component_defaults/component_defaults.test.tsx +++ b/src/components/provider/component_defaults/component_defaults.test.tsx @@ -32,13 +32,11 @@ describe('EuiComponentDefaultsProvider', () => { ); const { result } = renderHook(useEuiComponentDefaults, { wrapper }); - expect(result.current).toMatchInlineSnapshot(` + expect(result.current.EuiPortal).toMatchInlineSnapshot(` Object { - "EuiPortal": Object { - "insert": Object { - "position": "before", - "sibling":
, - }, + "insert": Object { + "position": "before", + "sibling":
, }, } `); diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx index 752fdbd8f3e..882171ff552 100644 --- a/src/components/provider/component_defaults/component_defaults.tsx +++ b/src/components/provider/component_defaults/component_defaults.tsx @@ -8,14 +8,14 @@ import React, { createContext, useContext, FunctionComponent } from 'react'; -import { EuiPortalProps } from '../../portal'; -import { EuiFocusTrapProps } from '../../focus_trap'; +import type { EuiPortalProps } from '../../portal'; +import type { EuiFocusTrapProps } from '../../focus_trap'; export type EuiComponentDefaults = { /** * Provide a global configuration for EuiPortal's default insertion position. */ - EuiPortal?: { insert: EuiPortalProps['insert'] }; + EuiPortal?: Pick; /** * Provide a global configuration for EuiFocusTrap's `gapMode` and `crossFrame` props */ From fbcbd291dfe891b1a8bf013e9f7869d324ffaf68 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Fri, 14 Jul 2023 08:44:50 -0700 Subject: [PATCH 02/13] Set up opinionated EUI baseline component defaults in the context - I played around with it, but unfortunately it doesn't makse sense to define the defaults in the component itself still. You either run into dependency issues (if you try to import them) or you have a lot of extra `??` repeated code if you try to make an extra layer of fallbacks --- .../component_defaults.test.tsx | 14 ++++++- .../component_defaults/component_defaults.tsx | 38 +++++++++++++++---- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/components/provider/component_defaults/component_defaults.test.tsx b/src/components/provider/component_defaults/component_defaults.test.tsx index bb85b633c6c..8154666c43f 100644 --- a/src/components/provider/component_defaults/component_defaults.test.tsx +++ b/src/components/provider/component_defaults/component_defaults.test.tsx @@ -12,10 +12,22 @@ import { renderHook } from '@testing-library/react-hooks'; import { EuiComponentDefaultsProvider, useEuiComponentDefaults, + EUI_COMPONENT_DEFAULTS, } from './component_defaults'; describe('EuiComponentDefaultsProvider', () => { - it('sets up context that allows accessing the passed `componentDefaults` from anywhere', () => { + it('returns baseline EUI defaults if no consumer configurations are passed', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + const { result } = renderHook(useEuiComponentDefaults, { wrapper }); + + expect(result.current).toEqual(EUI_COMPONENT_DEFAULTS); + }); + + it('overrides undefined EUI defaults with consumer defaults', () => { const wrapper = ({ children }: PropsWithChildren<{}>) => ( ; /** - * TODO + * Provide global settings for EuiTablePagination's props that affect page size + * / the rows per page selection. + * + * These defaults will be inherited all table and grid components that utilize EuiTablePagination. */ - EuiPagination?: unknown; + EuiTablePagination?: Pick< + EuiTablePaginationProps, + 'itemsPerPage' | 'itemsPerPageOptions' | 'showPerPageOptions' + >; }; -// Declaring as a static const for reference integrity/reducing rerenders -const emptyDefaults = {}; +/** + * The above types are external/consumer facing and have many optional props + * The below types reflect props EUI provides defaults for and should always + * be present within the internal context + */ +type _EuiComponentDefaults = Required & { + EuiTablePagination: Required; +}; + +export const EUI_COMPONENT_DEFAULTS: _EuiComponentDefaults = { + EuiPortal: {}, + EuiFocusTrap: {}, + EuiTablePagination: { + itemsPerPage: 50, + itemsPerPageOptions: [10, 20, 50, 100], + showPerPageOptions: true, + }, +}; /* * Context */ -export const EuiComponentDefaultsContext = - createContext(emptyDefaults); +export const EuiComponentDefaultsContext = createContext<_EuiComponentDefaults>( + EUI_COMPONENT_DEFAULTS +); /* * Component */ export const EuiComponentDefaultsProvider: FunctionComponent<{ componentDefaults?: EuiComponentDefaults; -}> = ({ componentDefaults = emptyDefaults, children }) => { +}> = ({ componentDefaults = EUI_COMPONENT_DEFAULTS, children }) => { return ( {children} From b21664ad367ee57443f67caf70f9434c683ff59d Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Fri, 14 Jul 2023 08:49:58 -0700 Subject: [PATCH 03/13] Add logic for merging/overriding consumer defaults on top of EUI defaults --- .../component_defaults.test.tsx | 43 ++++++++++++++++++- .../component_defaults/component_defaults.tsx | 31 ++++++++++++- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/components/provider/component_defaults/component_defaults.test.tsx b/src/components/provider/component_defaults/component_defaults.test.tsx index 8154666c43f..ec186b89695 100644 --- a/src/components/provider/component_defaults/component_defaults.test.tsx +++ b/src/components/provider/component_defaults/component_defaults.test.tsx @@ -54,8 +54,47 @@ describe('EuiComponentDefaultsProvider', () => { `); }); + it('does not override existing EUI defaults if a consumer does not configure the component', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + const { result } = renderHook(useEuiComponentDefaults, { wrapper }); + + // Should not override other existing EUI component defaults + expect(result.current.EuiTablePagination).toEqual( + EUI_COMPONENT_DEFAULTS.EuiTablePagination + ); + // Should set/override the passed defaults + expect(result.current.EuiFocusTrap).toEqual({ gapMode: 'margin' }); + }); + + it('does not override all other existing EUI default props if a consumer only configures a subset of the component props', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + const { result } = renderHook(useEuiComponentDefaults, { wrapper }); + + expect(result.current.EuiTablePagination).toEqual({ + ...EUI_COMPONENT_DEFAULTS.EuiTablePagination, + itemsPerPageOptions: [0, 10], + }); + }); + // NOTE: Components are in charge of their own testing to ensure that the props - // coming from `useEuiComponentDefaults()` were properly applied. This file - // is simply a very light wrapper that carries prop data. + // coming from `useEuiComponentDefaults()` were properly applied. // @see `src/components/portal/portal.spec.tsx` as an example }); diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx index 2c3247659ed..af1d75a8370 100644 --- a/src/components/provider/component_defaults/component_defaults.tsx +++ b/src/components/provider/component_defaults/component_defaults.tsx @@ -6,7 +6,12 @@ * Side Public License, v 1. */ -import React, { createContext, useContext, FunctionComponent } from 'react'; +import React, { + createContext, + useContext, + useMemo, + FunctionComponent, +} from 'react'; import type { EuiPortalProps } from '../../portal'; import type { EuiFocusTrapProps } from '../../focus_trap'; @@ -64,7 +69,29 @@ export const EuiComponentDefaultsContext = createContext<_EuiComponentDefaults>( */ export const EuiComponentDefaultsProvider: FunctionComponent<{ componentDefaults?: EuiComponentDefaults; -}> = ({ componentDefaults = EUI_COMPONENT_DEFAULTS, children }) => { +}> = ({ componentDefaults: configuredDefaults, children }) => { + // Merge consumer configured component props with baseline EUI component props + const componentDefaults: _EuiComponentDefaults = useMemo(() => { + if (!configuredDefaults) return EUI_COMPONENT_DEFAULTS; + + const mergedDefaults: _EuiComponentDefaults = { ...EUI_COMPONENT_DEFAULTS }; + + Object.entries(configuredDefaults).forEach( + ([componentName, componentProps]) => { + Object.entries(componentProps as any).forEach( + ([propName, propValue]) => { + if (propValue !== undefined) { + // @ts-ignore Object.entries is inherently untyped, but we don't need it to be here + mergedDefaults[componentName][propName] = propValue; + } + } + ); + } + ); + + return mergedDefaults; + }, [configuredDefaults]); + return ( {children} From b1fda8cbdaf6839040107bd9dc41a40310d4f6a6 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Fri, 14 Jul 2023 08:54:13 -0700 Subject: [PATCH 04/13] [setup] Misc EuiTablePaginationCleanup - convert tests to RTL - remove unnecessary `() => {}` callback default (since prop is not required), use optional chaining instead --- .../table_pagination.test.tsx.snap | 66 ++++++++++--------- .../table_pagination.test.tsx | 21 +++--- .../table_pagination/table_pagination.tsx | 4 +- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap b/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap index 248fcb82aad..5b43a3ccdc7 100644 --- a/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap +++ b/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap @@ -1,40 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiTablePagination is rendered 1`] = ` +exports[`EuiTablePagination hides the per page options 1`] = `
-
-
- -
-
-
+ />
@@ -191,13 +163,43 @@ exports[`EuiTablePagination is rendered 1`] = `
`; -exports[`EuiTablePagination is rendered when hiding the per page options 1`] = ` +exports[`EuiTablePagination renders 1`] = `
+ > +
+
+ +
+
+
diff --git a/src/components/table/table_pagination/table_pagination.test.tsx b/src/components/table/table_pagination/table_pagination.test.tsx index 26e5821960f..04e8419bc2b 100644 --- a/src/components/table/table_pagination/table_pagination.test.tsx +++ b/src/components/table/table_pagination/table_pagination.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { render } from 'enzyme'; +import { render } from '../../../test/rtl'; import { requiredProps } from '../../../test/required_props'; import { EuiTablePagination } from './table_pagination'; @@ -18,16 +18,17 @@ describe('EuiTablePagination', () => { pageCount: 5, onChangePage: jest.fn(), }; - test('is rendered', () => { - const component = render( + + it('renders', () => { + const { container } = render( ); - expect(component).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); - test('is rendered when hiding the per page options', () => { - const component = render( + it('hides the per page options', () => { + const { container } = render( { /> ); - expect(component).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); - test('renders a "show all" itemsPerPage option', () => { - const component = render( + it('renders a "show all" itemsPerPage option', () => { + const { container } = render( { /> ); - expect(component).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); }); diff --git a/src/components/table/table_pagination/table_pagination.tsx b/src/components/table/table_pagination/table_pagination.tsx index 3c94efc0cce..19f944d8023 100644 --- a/src/components/table/table_pagination/table_pagination.tsx +++ b/src/components/table/table_pagination/table_pagination.tsx @@ -56,7 +56,7 @@ export const EuiTablePagination: FunctionComponent = ({ itemsPerPage = 50, itemsPerPageOptions = [10, 20, 50, 100], showPerPageOptions = true, - onChangeItemsPerPage = () => {}, + onChangeItemsPerPage, onChangePage, pageCount, ...rest @@ -105,7 +105,7 @@ export const EuiTablePagination: FunctionComponent = ({ icon={itemsPerPageOption === itemsPerPage ? 'check' : 'empty'} onClick={() => { closePopover(); - onChangeItemsPerPage(itemsPerPageOption); + onChangeItemsPerPage?.(itemsPerPageOption); }} data-test-subj={`tablePagination-${itemsPerPageOption}-rows`} > From d722be16746991d84328a47d29533c6447eb6710 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Fri, 14 Jul 2023 08:57:51 -0700 Subject: [PATCH 05/13] Update EuiTablePagination to use defaults context - default props must be manually declared using jsdocs now, since react docgen can no longer use `=` fallbacks to figure them out - note: setting `EuiTablePagination.defaultProps` unfortunately does not work, React will automatically populate them and they don't come in as undefined --- .../component_defaults.test.tsx | 4 +- .../table_pagination.test.tsx | 51 +++++++++++++++++++ .../table_pagination/table_pagination.tsx | 38 ++++++++++---- 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/components/provider/component_defaults/component_defaults.test.tsx b/src/components/provider/component_defaults/component_defaults.test.tsx index ec186b89695..e61b4f1522c 100644 --- a/src/components/provider/component_defaults/component_defaults.test.tsx +++ b/src/components/provider/component_defaults/component_defaults.test.tsx @@ -96,5 +96,7 @@ describe('EuiComponentDefaultsProvider', () => { // NOTE: Components are in charge of their own testing to ensure that the props // coming from `useEuiComponentDefaults()` were properly applied. - // @see `src/components/portal/portal.spec.tsx` as an example + // Examples: + // @see src/components/portal/portal.spec.tsx + // @see src/components/table/table_pagination/table_pagination.test.tsx }); diff --git a/src/components/table/table_pagination/table_pagination.test.tsx b/src/components/table/table_pagination/table_pagination.test.tsx index 04e8419bc2b..a11a049f00d 100644 --- a/src/components/table/table_pagination/table_pagination.test.tsx +++ b/src/components/table/table_pagination/table_pagination.test.tsx @@ -7,9 +7,12 @@ */ import React from 'react'; +import { fireEvent } from '@testing-library/react'; import { render } from '../../../test/rtl'; import { requiredProps } from '../../../test/required_props'; +import { EuiProvider } from '../../provider'; + import { EuiTablePagination } from './table_pagination'; describe('EuiTablePagination', () => { @@ -51,4 +54,52 @@ describe('EuiTablePagination', () => { expect(container.firstChild).toMatchSnapshot(); }); + + describe('configurable defaults', () => { + test('itemsPerPage', () => { + const { getByText } = render( + + + , + { wrapper: undefined } + ); + + expect(getByText('Rows per page: 20')).toBeTruthy(); + }); + + test('itemsPerPageOptions', () => { + const { getByTestSubject } = render( + + + , + { wrapper: undefined } + ); + + fireEvent.click(getByTestSubject('tablePaginationPopoverButton')); + expect(getByTestSubject('tablePaginationRowOptions').textContent).toEqual( + '5 rows10 rows15 rows' + ); + }); + + test('showPerPageOptions', () => { + const { queryByTestSubject } = render( + + + , + { wrapper: undefined } + ); + + expect(queryByTestSubject('tablePaginationPopoverButton')).toBe(null); + }); + }); }); diff --git a/src/components/table/table_pagination/table_pagination.tsx b/src/components/table/table_pagination/table_pagination.tsx index 19f944d8023..b0f38c473f2 100644 --- a/src/components/table/table_pagination/table_pagination.tsx +++ b/src/components/table/table_pagination/table_pagination.tsx @@ -20,6 +20,8 @@ import { EuiPagination, EuiPaginationProps } from '../../pagination'; import { EuiPopover } from '../../popover'; import { EuiI18n } from '../../i18n'; +import { useEuiComponentDefaults } from '../../provider/component_defaults'; + export type PageChangeHandler = EuiPaginationProps['onPageClick']; export type ItemsPerPageChangeHandler = (pageSize: number) => void; @@ -27,16 +29,22 @@ export interface EuiTablePaginationProps extends Omit { /** * Option to completely hide the "Rows per page" selector. + * + * @default true */ showPerPageOptions?: boolean; /** * Current selection for "Rows per page". * Pass `0` to display the selected "Show all" option and hide the pagination. + * + * @default 50 */ itemsPerPage?: number; /** * Custom array of options for "Rows per page". * Pass `0` as one of the options to create a "Show all" option. + * + * @default [10, 20, 50, 100] */ itemsPerPageOptions?: number[]; /** @@ -51,16 +59,21 @@ export interface EuiTablePaginationProps 'aria-label'?: string; } -export const EuiTablePagination: FunctionComponent = ({ - activePage, - itemsPerPage = 50, - itemsPerPageOptions = [10, 20, 50, 100], - showPerPageOptions = true, - onChangeItemsPerPage, - onChangePage, - pageCount, - ...rest -}) => { +export const EuiTablePagination: FunctionComponent = ( + props +) => { + const { EuiTablePagination: defaults } = useEuiComponentDefaults(); + const { + activePage, + itemsPerPage = defaults.itemsPerPage, + itemsPerPageOptions = defaults.itemsPerPageOptions, + showPerPageOptions = defaults.showPerPageOptions, + onChangeItemsPerPage, + onChangePage, + pageCount, + ...rest + } = props; + const [isPopoverOpen, setIsPopoverOpen] = useState(false); const togglePopover = useCallback(() => { @@ -134,7 +147,10 @@ export const EuiTablePagination: FunctionComponent = ({ panelPaddingSize="none" anchorPosition="upRight" > - + ); From 37ff1c33eec591b89867bccb125c33740d03a197 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Fri, 14 Jul 2023 09:26:47 -0700 Subject: [PATCH 06/13] [tests][cleanup] De-dupe EuiTablePagination and PaginationBar tests - PaginationBar should not be testing props that are basic pass-throughs - EuiTablePagination should (the whole point of unit tests) + add test for PaginationBar logic that's actually there (onPageChange) - Move said missing tests to EuiTablePagination and improve tests to use specific assertions instead of noisy snapshots --- .../pagination_bar.test.tsx.snap | 67 -- .../basic_table/pagination_bar.test.tsx | 88 +-- .../table_pagination.test.tsx.snap | 635 ++++++++---------- .../table_pagination.test.tsx | 42 +- 4 files changed, 346 insertions(+), 486 deletions(-) diff --git a/src/components/basic_table/__snapshots__/pagination_bar.test.tsx.snap b/src/components/basic_table/__snapshots__/pagination_bar.test.tsx.snap index 2366e0d0f51..5afd7642a41 100644 --- a/src/components/basic_table/__snapshots__/pagination_bar.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/pagination_bar.test.tsx.snap @@ -1,72 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PaginationBar render - custom page size options 1`] = ` -
- - -
-`; - -exports[`PaginationBar render - hiding per page options 1`] = ` -
- - -
-`; - -exports[`PaginationBar render - show all pageSize 1`] = ` -
- - -
-`; - exports[`PaginationBar renders 1`] = `
{ - it('renders', () => { - const props = { - ...requiredProps, - pagination: { - pageIndex: 0, - pageSize: 5, - totalItemCount: 0, - }, - onPageSizeChange: () => {}, - onPageChange: () => {}, - }; + const props = { + ...requiredProps, + pagination: { + pageIndex: 0, + pageSize: 5, + totalItemCount: 0, + }, + onPageSizeChange: () => {}, + onPageChange: () => {}, + }; + it('renders', () => { const { container } = render(); expect(container.firstChild).toMatchSnapshot(); }); - test('render - custom page size options', () => { - const props = { - pagination: { - pageIndex: 0, - pageSize: 5, - totalItemCount: 0, - pageSizeOptions: [1, 2, 3], - }, - onPageSizeChange: () => {}, - onPageChange: () => {}, - }; - - const component = shallow(); - - expect(component).toMatchSnapshot(); - }); - - test('render - hiding per page options', () => { - const props = { - pagination: { - pageIndex: 0, - pageSize: 5, - totalItemCount: 0, - showPerPageOptions: false, - }, - onPageSizeChange: () => {}, - onPageChange: () => {}, - }; - - const component = shallow(); - - expect(component).toMatchSnapshot(); - }); - - test('render - show all pageSize', () => { - const props = { - pagination: { - pageIndex: 0, - pageSize: 0, - pageSizeOptions: [1, 5, 0], - totalItemCount: 5, - }, - onPageSizeChange: () => {}, - onPageChange: () => {}, - }; - - const component = shallow(); - - expect(component).toMatchSnapshot(); + it('calls onPageChange with the correct off-by-one offset', () => { + const onPageChange = jest.fn(); + const { getByLabelText } = render( + + ); + + fireEvent.click(getByLabelText('Page 2 of 2')); + expect(onPageChange).toHaveBeenCalledWith(1); }); }); diff --git a/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap b/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap index 5b43a3ccdc7..0186f489793 100644 --- a/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap +++ b/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap @@ -1,398 +1,329 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiTablePagination hides the per page options 1`] = ` -
-
-
- -
-
-`; - -exports[`EuiTablePagination renders 1`] = ` -
-
-
+ +
+
+
- -
-
-
-
- + +
+
-
-`; - -exports[`EuiTablePagination renders a "show all" itemsPerPage option 1`] = ` -
+
+
+
-
-
+ `; diff --git a/src/components/table/table_pagination/table_pagination.test.tsx b/src/components/table/table_pagination/table_pagination.test.tsx index a11a049f00d..42ad1c6b9df 100644 --- a/src/components/table/table_pagination/table_pagination.test.tsx +++ b/src/components/table/table_pagination/table_pagination.test.tsx @@ -23,15 +23,43 @@ describe('EuiTablePagination', () => { }; it('renders', () => { - const { container } = render( + const { getByTestSubject, baseElement } = render( ); - expect(container.firstChild).toMatchSnapshot(); + fireEvent.click(getByTestSubject('tablePaginationPopoverButton')); + expect(baseElement).toMatchSnapshot(); + }); + + it('renders custom items per page / page sizes', () => { + const { getByText } = render( + + ); + + expect(getByText('Rows per page: 10')).toBeTruthy(); + }); + + it('renders custom items per page size options', () => { + const { getByTestSubject } = render( + + ); + + fireEvent.click(getByTestSubject('tablePaginationPopoverButton')); + expect(getByTestSubject('tablePagination-1-rows')).toBeTruthy(); + expect(getByTestSubject('tablePagination-2-rows')).toBeTruthy(); + expect(getByTestSubject('tablePagination-3-rows')).toBeTruthy(); }); it('hides the per page options', () => { - const { container } = render( + const { queryByTestSubject } = render( { /> ); - expect(container.firstChild).toMatchSnapshot(); + expect(queryByTestSubject('tablePaginationPopoverButton')).toBe(null); }); it('renders a "show all" itemsPerPage option', () => { - const { container } = render( + const { getByText, getByTestSubject } = render( { /> ); - expect(container.firstChild).toMatchSnapshot(); + expect(getByText('Showing all rows')).toBeTruthy(); + fireEvent.click(getByTestSubject('tablePaginationPopoverButton')); + expect(getByText('Show all rows')).toBeTruthy(); }); describe('configurable defaults', () => { From 8695bb379a22befe9ff57c4e2b263a27b4f29ea7 Mon Sep 17 00:00:00 2001 From: Tomasz Kajtoch Date: Thu, 20 Jul 2023 19:12:47 +0200 Subject: [PATCH 07/13] feat: add usePropsWithComponentDefaults --- src/components/focus_trap/focus_trap.tsx | 19 ++++++------- src/components/portal/portal.tsx | 17 +++++------ .../component_defaults/component_defaults.tsx | 28 +++++++++++++++++++ .../table_pagination/table_pagination.tsx | 13 ++++----- 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/components/focus_trap/focus_trap.tsx b/src/components/focus_trap/focus_trap.tsx index 52a68bca9eb..76d5a6e44d0 100644 --- a/src/components/focus_trap/focus_trap.tsx +++ b/src/components/focus_trap/focus_trap.tsx @@ -13,7 +13,7 @@ import { RemoveScrollBar } from 'react-remove-scroll-bar'; import { CommonProps } from '../common'; import { findElementBySelectorOrRef, ElementTarget } from '../../services'; -import { useEuiComponentDefaults } from '../provider/component_defaults'; +import { usePropsWithComponentDefaults } from '../provider/component_defaults'; export type FocusTarget = ElementTarget; @@ -82,16 +82,15 @@ export type EuiFocusTrapProps = Omit< returnFocus?: ReactFocusOnProps['returnFocus']; }; -export const EuiFocusTrap: FunctionComponent = ({ - children, - ...props -}) => { - const { EuiFocusTrap: defaults } = useEuiComponentDefaults(); - return ( - - {children} - +export const EuiFocusTrap: FunctionComponent = ( + originalProps +) => { + const { children, ...rest } = usePropsWithComponentDefaults( + 'EuiFocusTrap', + originalProps ); + + return {children}; }; interface State { diff --git a/src/components/portal/portal.tsx b/src/components/portal/portal.tsx index bb3fae6706c..ef153c7934a 100644 --- a/src/components/portal/portal.tsx +++ b/src/components/portal/portal.tsx @@ -15,7 +15,7 @@ import React, { Component, FunctionComponent, ReactNode } from 'react'; import { createPortal } from 'react-dom'; import { EuiNestedThemeContext } from '../../services'; -import { useEuiComponentDefaults } from '../provider/component_defaults'; +import { usePropsWithComponentDefaults } from '../provider/component_defaults'; const INSERT_POSITIONS = ['after', 'before'] as const; type EuiPortalInsertPosition = (typeof INSERT_POSITIONS)[number]; @@ -40,16 +40,13 @@ export interface EuiPortalProps { portalRef?: (ref: HTMLDivElement | null) => void; } -export const EuiPortal: FunctionComponent = ({ - children, - ...props -}) => { - const { EuiPortal: defaults } = useEuiComponentDefaults(); - return ( - - {children} - +export const EuiPortal: FunctionComponent = (originalProps) => { + const { children, ...rest } = usePropsWithComponentDefaults( + 'EuiPortal', + originalProps ); + + return {children}; }; export class EuiPortalClass extends Component { diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx index af1d75a8370..02f7a0d05b0 100644 --- a/src/components/provider/component_defaults/component_defaults.tsx +++ b/src/components/provider/component_defaults/component_defaults.tsx @@ -17,6 +17,13 @@ import type { EuiPortalProps } from '../../portal'; import type { EuiFocusTrapProps } from '../../focus_trap'; import type { EuiTablePaginationProps } from '../../table'; +// This type should always be in sync with EuiComponentDefaults +type EuiComponentDefaultsSupportedComponents = { + EuiPortal: EuiPortalProps; + EuiFocusTrap: EuiFocusTrapProps; + EuiTablePagination: EuiTablePaginationProps; +}; + export type EuiComponentDefaults = { /** * Provide a global configuration for EuiPortal's default insertion position. @@ -105,3 +112,24 @@ export const EuiComponentDefaultsProvider: FunctionComponent<{ export const useEuiComponentDefaults = () => { return useContext(EuiComponentDefaultsContext); }; + +export const usePropsWithComponentDefaults = < + TComponentName extends keyof EuiComponentDefaults +>( + componentName: TComponentName, + originalProps: EuiComponentDefaultsSupportedComponents[TComponentName] +): EuiComponentDefaultsSupportedComponents[TComponentName] => { + const context = useEuiComponentDefaults(); + + const defaults = context?.[ + componentName + ] as EuiComponentDefaultsSupportedComponents[TComponentName]; + + return useMemo( + () => ({ + ...defaults, + ...originalProps, + }), + [originalProps, defaults] + ); +}; diff --git a/src/components/table/table_pagination/table_pagination.tsx b/src/components/table/table_pagination/table_pagination.tsx index b0f38c473f2..f0c98aff8f7 100644 --- a/src/components/table/table_pagination/table_pagination.tsx +++ b/src/components/table/table_pagination/table_pagination.tsx @@ -20,7 +20,7 @@ import { EuiPagination, EuiPaginationProps } from '../../pagination'; import { EuiPopover } from '../../popover'; import { EuiI18n } from '../../i18n'; -import { useEuiComponentDefaults } from '../../provider/component_defaults'; +import { usePropsWithComponentDefaults } from '../../provider/component_defaults'; export type PageChangeHandler = EuiPaginationProps['onPageClick']; export type ItemsPerPageChangeHandler = (pageSize: number) => void; @@ -60,19 +60,18 @@ export interface EuiTablePaginationProps } export const EuiTablePagination: FunctionComponent = ( - props + originalProps ) => { - const { EuiTablePagination: defaults } = useEuiComponentDefaults(); const { activePage, - itemsPerPage = defaults.itemsPerPage, - itemsPerPageOptions = defaults.itemsPerPageOptions, - showPerPageOptions = defaults.showPerPageOptions, + itemsPerPage = 50, + itemsPerPageOptions = [10, 20, 50, 100], + showPerPageOptions = true, onChangeItemsPerPage, onChangePage, pageCount, ...rest - } = props; + } = usePropsWithComponentDefaults('EuiTablePagination', originalProps); const [isPopoverOpen, setIsPopoverOpen] = useState(false); From 8dcf924da148b49f4de47e92212d455384852185 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 24 Jul 2023 13:13:46 -0700 Subject: [PATCH 08/13] [PR feedback feedback] Prop naming tweaks, order --- src/components/focus_trap/focus_trap.tsx | 11 +++---- src/components/portal/portal.tsx | 10 ++---- .../component_defaults/component_defaults.tsx | 33 ++++++++++--------- .../table_pagination/table_pagination.tsx | 4 +-- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/components/focus_trap/focus_trap.tsx b/src/components/focus_trap/focus_trap.tsx index 76d5a6e44d0..e71a892de1e 100644 --- a/src/components/focus_trap/focus_trap.tsx +++ b/src/components/focus_trap/focus_trap.tsx @@ -82,15 +82,12 @@ export type EuiFocusTrapProps = Omit< returnFocus?: ReactFocusOnProps['returnFocus']; }; -export const EuiFocusTrap: FunctionComponent = ( - originalProps -) => { - const { children, ...rest } = usePropsWithComponentDefaults( +export const EuiFocusTrap: FunctionComponent = (props) => { + const propsWithDefaults = usePropsWithComponentDefaults( 'EuiFocusTrap', - originalProps + props ); - - return {children}; + return ; }; interface State { diff --git a/src/components/portal/portal.tsx b/src/components/portal/portal.tsx index ef153c7934a..d73863b6d06 100644 --- a/src/components/portal/portal.tsx +++ b/src/components/portal/portal.tsx @@ -40,13 +40,9 @@ export interface EuiPortalProps { portalRef?: (ref: HTMLDivElement | null) => void; } -export const EuiPortal: FunctionComponent = (originalProps) => { - const { children, ...rest } = usePropsWithComponentDefaults( - 'EuiPortal', - originalProps - ); - - return {children}; +export const EuiPortal: FunctionComponent = (props) => { + const propsWithDefaults = usePropsWithComponentDefaults('EuiPortal', props); + return ; }; export class EuiPortalClass extends Component { diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx index 02f7a0d05b0..51d81f80f21 100644 --- a/src/components/provider/component_defaults/component_defaults.tsx +++ b/src/components/provider/component_defaults/component_defaults.tsx @@ -17,13 +17,6 @@ import type { EuiPortalProps } from '../../portal'; import type { EuiFocusTrapProps } from '../../focus_trap'; import type { EuiTablePaginationProps } from '../../table'; -// This type should always be in sync with EuiComponentDefaults -type EuiComponentDefaultsSupportedComponents = { - EuiPortal: EuiPortalProps; - EuiFocusTrap: EuiFocusTrapProps; - EuiTablePagination: EuiTablePaginationProps; -}; - export type EuiComponentDefaults = { /** * Provide a global configuration for EuiPortal's default insertion position. @@ -45,6 +38,15 @@ export type EuiComponentDefaults = { >; }; +// This type must be manually kept in sync with EuiComponentDefaults, until we +// decide to extend this architecture / extrapolate component defaults to *all* +// props across *all* components +type EuiComponentDefaultsSupportedComponents = { + EuiPortal: EuiPortalProps; + EuiFocusTrap: EuiFocusTrapProps; + EuiTablePagination: EuiTablePaginationProps; +}; + /** * The above types are external/consumer facing and have many optional props * The below types reflect props EUI provides defaults for and should always @@ -107,29 +109,28 @@ export const EuiComponentDefaultsProvider: FunctionComponent<{ }; /* - * Hook + * Hooks */ export const useEuiComponentDefaults = () => { return useContext(EuiComponentDefaultsContext); }; +// Merge individual component props with component defaults export const usePropsWithComponentDefaults = < TComponentName extends keyof EuiComponentDefaults >( componentName: TComponentName, - originalProps: EuiComponentDefaultsSupportedComponents[TComponentName] + props: EuiComponentDefaultsSupportedComponents[TComponentName] ): EuiComponentDefaultsSupportedComponents[TComponentName] => { - const context = useEuiComponentDefaults(); + const context = useContext(EuiComponentDefaultsContext); - const defaults = context?.[ - componentName - ] as EuiComponentDefaultsSupportedComponents[TComponentName]; + const componentDefaults = context[componentName] ?? emptyDefaults; return useMemo( () => ({ - ...defaults, - ...originalProps, + ...componentDefaults, + ...props, }), - [originalProps, defaults] + [componentDefaults, props] ); }; diff --git a/src/components/table/table_pagination/table_pagination.tsx b/src/components/table/table_pagination/table_pagination.tsx index f0c98aff8f7..8b254a9756e 100644 --- a/src/components/table/table_pagination/table_pagination.tsx +++ b/src/components/table/table_pagination/table_pagination.tsx @@ -60,7 +60,7 @@ export interface EuiTablePaginationProps } export const EuiTablePagination: FunctionComponent = ( - originalProps + props ) => { const { activePage, @@ -71,7 +71,7 @@ export const EuiTablePagination: FunctionComponent = ( onChangePage, pageCount, ...rest - } = usePropsWithComponentDefaults('EuiTablePagination', originalProps); + } = usePropsWithComponentDefaults('EuiTablePagination', props); const [isPopoverOpen, setIsPopoverOpen] = useState(false); From 3a44a791d9962ed6937e88044812e999212e626d Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 24 Jul 2023 13:28:58 -0700 Subject: [PATCH 09/13] Revert EUI_COMPONENT_DEFAULTS const/merging logic --- .../component_defaults.test.tsx | 100 +++++------------- .../component_defaults/component_defaults.tsx | 49 +-------- 2 files changed, 31 insertions(+), 118 deletions(-) diff --git a/src/components/provider/component_defaults/component_defaults.test.tsx b/src/components/provider/component_defaults/component_defaults.test.tsx index e61b4f1522c..c5e8086c3ee 100644 --- a/src/components/provider/component_defaults/component_defaults.test.tsx +++ b/src/components/provider/component_defaults/component_defaults.test.tsx @@ -12,85 +12,37 @@ import { renderHook } from '@testing-library/react-hooks'; import { EuiComponentDefaultsProvider, useEuiComponentDefaults, - EUI_COMPONENT_DEFAULTS, } from './component_defaults'; describe('EuiComponentDefaultsProvider', () => { - it('returns baseline EUI defaults if no consumer configurations are passed', () => { - const wrapper = ({ children }: PropsWithChildren<{}>) => ( - - {children} - - ); - const { result } = renderHook(useEuiComponentDefaults, { wrapper }); - - expect(result.current).toEqual(EUI_COMPONENT_DEFAULTS); - }); - - it('overrides undefined EUI defaults with consumer defaults', () => { - const wrapper = ({ children }: PropsWithChildren<{}>) => ( - { + it('allows accessing provided `componentDefaults` from anywhere', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + const { result } = renderHook(useEuiComponentDefaults, { wrapper }); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "EuiPortal": Object { + "insert": Object { + "position": "before", + "sibling":
, }, }, - }} - > - {children} - - ); - const { result } = renderHook(useEuiComponentDefaults, { wrapper }); - - expect(result.current.EuiPortal).toMatchInlineSnapshot(` - Object { - "insert": Object { - "position": "before", - "sibling":
, - }, - } - `); - }); - - it('does not override existing EUI defaults if a consumer does not configure the component', () => { - const wrapper = ({ children }: PropsWithChildren<{}>) => ( - - {children} - - ); - const { result } = renderHook(useEuiComponentDefaults, { wrapper }); - - // Should not override other existing EUI component defaults - expect(result.current.EuiTablePagination).toEqual( - EUI_COMPONENT_DEFAULTS.EuiTablePagination - ); - // Should set/override the passed defaults - expect(result.current.EuiFocusTrap).toEqual({ gapMode: 'margin' }); - }); - - it('does not override all other existing EUI default props if a consumer only configures a subset of the component props', () => { - const wrapper = ({ children }: PropsWithChildren<{}>) => ( - - {children} - - ); - const { result } = renderHook(useEuiComponentDefaults, { wrapper }); - - expect(result.current.EuiTablePagination).toEqual({ - ...EUI_COMPONENT_DEFAULTS.EuiTablePagination, - itemsPerPageOptions: [0, 10], + } + `); }); }); diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx index 51d81f80f21..56dc95c840a 100644 --- a/src/components/provider/component_defaults/component_defaults.tsx +++ b/src/components/provider/component_defaults/component_defaults.tsx @@ -47,60 +47,21 @@ type EuiComponentDefaultsSupportedComponents = { EuiTablePagination: EuiTablePaginationProps; }; -/** - * The above types are external/consumer facing and have many optional props - * The below types reflect props EUI provides defaults for and should always - * be present within the internal context - */ -type _EuiComponentDefaults = Required & { - EuiTablePagination: Required; -}; - -export const EUI_COMPONENT_DEFAULTS: _EuiComponentDefaults = { - EuiPortal: {}, - EuiFocusTrap: {}, - EuiTablePagination: { - itemsPerPage: 50, - itemsPerPageOptions: [10, 20, 50, 100], - showPerPageOptions: true, - }, -}; +// Declaring as a static const for reference integrity/reducing rerenders +const emptyDefaults = {}; /* * Context */ -export const EuiComponentDefaultsContext = createContext<_EuiComponentDefaults>( - EUI_COMPONENT_DEFAULTS -); +export const EuiComponentDefaultsContext = + createContext(emptyDefaults); /* * Component */ export const EuiComponentDefaultsProvider: FunctionComponent<{ componentDefaults?: EuiComponentDefaults; -}> = ({ componentDefaults: configuredDefaults, children }) => { - // Merge consumer configured component props with baseline EUI component props - const componentDefaults: _EuiComponentDefaults = useMemo(() => { - if (!configuredDefaults) return EUI_COMPONENT_DEFAULTS; - - const mergedDefaults: _EuiComponentDefaults = { ...EUI_COMPONENT_DEFAULTS }; - - Object.entries(configuredDefaults).forEach( - ([componentName, componentProps]) => { - Object.entries(componentProps as any).forEach( - ([propName, propValue]) => { - if (propValue !== undefined) { - // @ts-ignore Object.entries is inherently untyped, but we don't need it to be here - mergedDefaults[componentName][propName] = propValue; - } - } - ); - } - ); - - return mergedDefaults; - }, [configuredDefaults]); - +}> = ({ componentDefaults = emptyDefaults, children }) => { return ( {children} From 4f8a62101b91c96fd48b9920efd1c4911e106e9e Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 24 Jul 2023 13:33:40 -0700 Subject: [PATCH 10/13] Rename `useEuiComponentDefaults` to just `useComponentDefaults` - to make it (maybe??) a bit clearer that component defaults in this context are 100% from the consumer and 0% from EUI at this point --- .../component_defaults/component_defaults.test.tsx | 8 ++++---- .../provider/component_defaults/component_defaults.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/provider/component_defaults/component_defaults.test.tsx b/src/components/provider/component_defaults/component_defaults.test.tsx index c5e8086c3ee..a7bce9259b3 100644 --- a/src/components/provider/component_defaults/component_defaults.test.tsx +++ b/src/components/provider/component_defaults/component_defaults.test.tsx @@ -11,11 +11,11 @@ import { renderHook } from '@testing-library/react-hooks'; import { EuiComponentDefaultsProvider, - useEuiComponentDefaults, + useComponentDefaults, } from './component_defaults'; describe('EuiComponentDefaultsProvider', () => { - describe('useEuiComponentDefaults', () => { + describe('useComponentDefaults', () => { it('allows accessing provided `componentDefaults` from anywhere', () => { const wrapper = ({ children }: PropsWithChildren<{}>) => ( { {children} ); - const { result } = renderHook(useEuiComponentDefaults, { wrapper }); + const { result } = renderHook(useComponentDefaults, { wrapper }); expect(result.current).toMatchInlineSnapshot(` Object { @@ -47,7 +47,7 @@ describe('EuiComponentDefaultsProvider', () => { }); // NOTE: Components are in charge of their own testing to ensure that the props - // coming from `useEuiComponentDefaults()` were properly applied. + // coming from the `componentDefaults` configuration were properly applied. // Examples: // @see src/components/portal/portal.spec.tsx // @see src/components/table/table_pagination/table_pagination.test.tsx diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx index 56dc95c840a..c51107fd649 100644 --- a/src/components/provider/component_defaults/component_defaults.tsx +++ b/src/components/provider/component_defaults/component_defaults.tsx @@ -72,7 +72,7 @@ export const EuiComponentDefaultsProvider: FunctionComponent<{ /* * Hooks */ -export const useEuiComponentDefaults = () => { +export const useComponentDefaults = () => { return useContext(EuiComponentDefaultsContext); }; From 4f5f2253089a3ab47ee9afeae52747dee456357f Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 24 Jul 2023 13:42:22 -0700 Subject: [PATCH 11/13] Add tests for `usePropsWithComponentDefaults` --- .../component_defaults.test.tsx | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/components/provider/component_defaults/component_defaults.test.tsx b/src/components/provider/component_defaults/component_defaults.test.tsx index a7bce9259b3..57f1cf07632 100644 --- a/src/components/provider/component_defaults/component_defaults.test.tsx +++ b/src/components/provider/component_defaults/component_defaults.test.tsx @@ -12,6 +12,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { EuiComponentDefaultsProvider, useComponentDefaults, + usePropsWithComponentDefaults, } from './component_defaults'; describe('EuiComponentDefaultsProvider', () => { @@ -46,6 +47,99 @@ describe('EuiComponentDefaultsProvider', () => { }); }); + describe('usePropsWithComponentDefaults', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + + it("returns a specific component's provided default props", () => { + const { result } = renderHook( + () => usePropsWithComponentDefaults('EuiTablePagination', {}), + { wrapper } + ); + + expect(result.current).toEqual({ + itemsPerPage: 20, + itemsPerPageOptions: [20, 40, 80, 0], + }); + }); + + it('correctly overrides defaults with actual props passed', () => { + const { result } = renderHook( + () => + usePropsWithComponentDefaults('EuiTablePagination', { + itemsPerPageOptions: [5, 10, 20], + }), + { wrapper } + ); + + expect(result.current).toEqual({ + itemsPerPage: 20, + itemsPerPageOptions: [5, 10, 20], + }); + }); + + it('correctly handles props without a default defined', () => { + const { result } = renderHook( + () => + usePropsWithComponentDefaults('EuiTablePagination', { + showPerPageOptions: false, + }), + { wrapper } + ); + + expect(result.current).toEqual({ + itemsPerPage: 20, + itemsPerPageOptions: [20, 40, 80, 0], + showPerPageOptions: false, + }); + }); + + it('correctly handles components with no defaults defined', () => { + const { result } = renderHook( + () => + usePropsWithComponentDefaults('EuiFocusTrap', { + children: 'test', + crossFrame: true, + }), + { wrapper } + ); + + expect(result.current).toEqual({ + children: 'test', + crossFrame: true, + }); + }); + + it('correctly handles no component defaults defined at all', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + {children} + ); + const { result } = renderHook( + () => + usePropsWithComponentDefaults('EuiFocusTrap', { + children: 'test', + gapMode: 'margin', + }), + { wrapper } + ); + + expect(result.current).toEqual({ + children: 'test', + gapMode: 'margin', + }); + }); + }); + // NOTE: Components are in charge of their own testing to ensure that the props // coming from the `componentDefaults` configuration were properly applied. // Examples: From 28523ea0de39b60833bcca3931cc94d89e85ac20 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 24 Jul 2023 21:03:54 -0700 Subject: [PATCH 12/13] Remove need for `EuiComponentDefaultsSupportedComponents` via the magic of an inferred generic --- .../component_defaults/component_defaults.tsx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx index c51107fd649..da71ccfc71a 100644 --- a/src/components/provider/component_defaults/component_defaults.tsx +++ b/src/components/provider/component_defaults/component_defaults.tsx @@ -38,15 +38,6 @@ export type EuiComponentDefaults = { >; }; -// This type must be manually kept in sync with EuiComponentDefaults, until we -// decide to extend this architecture / extrapolate component defaults to *all* -// props across *all* components -type EuiComponentDefaultsSupportedComponents = { - EuiPortal: EuiPortalProps; - EuiFocusTrap: EuiFocusTrapProps; - EuiTablePagination: EuiTablePaginationProps; -}; - // Declaring as a static const for reference integrity/reducing rerenders const emptyDefaults = {}; @@ -78,11 +69,12 @@ export const useComponentDefaults = () => { // Merge individual component props with component defaults export const usePropsWithComponentDefaults = < - TComponentName extends keyof EuiComponentDefaults + TComponentName extends keyof EuiComponentDefaults, + TComponentProps >( componentName: TComponentName, - props: EuiComponentDefaultsSupportedComponents[TComponentName] -): EuiComponentDefaultsSupportedComponents[TComponentName] => { + props: TComponentProps +): TComponentProps => { const context = useContext(EuiComponentDefaultsContext); const componentDefaults = context[componentName] ?? emptyDefaults; From f303f962023bd478badb8fa0f6fc92771b76c289 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 24 Jul 2023 22:04:13 -0700 Subject: [PATCH 13/13] changelog --- upcoming_changelogs/6951.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 upcoming_changelogs/6951.md diff --git a/upcoming_changelogs/6951.md b/upcoming_changelogs/6951.md new file mode 100644 index 00000000000..98f082b1149 --- /dev/null +++ b/upcoming_changelogs/6951.md @@ -0,0 +1 @@ +- `EuiTablePagination`'s `itemsPerPage`, `itemsPerPageOptions`, and `showPerPageOptions` props can now be configured globally via `EuiProvider.componentDefaults`