Skip to content

Commit

Permalink
Expose scrollTo and scrollToItem on imperative ref (#6042)
Browse files Browse the repository at this point in the history
* Expose scrollTo and scrollToItem on imperative ref

* Add documentation for `scrollTo` and `scrollToItem`

* Add tests for `scrollTo` and `scrollToItem`

* Add changelog entry

* Update upcoming_changelogs/6042.md

Co-authored-by: Constance <constancecchen@users.noreply.github.com>

* Move react-window API docs into separate section

* [PR feedback] Docs tweaks

- combine sections and use EuiTitle tag for react-window methods

- Fix link to react-window docs

- minor grammar tweaks

Co-authored-by: Constance <constancecchen@users.noreply.github.com>
Co-authored-by: Constance Chen <constance.chen.3@gmail.com>
  • Loading branch information
3 people authored Jul 12, 2022
1 parent b9eeef9 commit cb937f4
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 13 deletions.
75 changes: 71 additions & 4 deletions src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from 'react';

import { GuideSectionTypes } from '../../../components';
import { EuiCode, EuiSpacer, EuiCallOut } from '../../../../../src/components';
import {
EuiCode,
EuiSpacer,
EuiCallOut,
EuiTitle,
EuiLink,
} from '../../../../../src/components';

import { EuiDataGridRefProps } from '!!prop-loader!../../../../../src/components/datagrid/data_grid_types';

Expand Down Expand Up @@ -81,8 +87,6 @@ export const DataGridAdvancedExample = {
</li>
</ul>

<EuiSpacer size="s" />

<EuiCallOut title="Handling cell location">
When using <EuiCode>setFocusedCell</EuiCode> or{' '}
<EuiCode>openCellPopover</EuiCode>, keep in mind:
Expand All @@ -104,11 +108,74 @@ export const DataGridAdvancedExample = {
</ul>
</EuiCallOut>

<EuiSpacer size="s" />

<EuiTitle>
<h3>react-window methods</h3>
</EuiTitle>
<p>
<EuiCode>EuiDataGrid</EuiCode> also exposes several underlying
methods from{' '}
<EuiLink
href="https://react-window.vercel.app/#/api/VariableSizeGrid"
target="_blank"
>
react-window&apos;s <EuiCode>VariableSizeGrid</EuiCode> imperative
API
</EuiLink>{' '}
via its <EuiCode>ref</EuiCode>:
</p>
<ul>
<li>
<p>
<EuiCode>
scrollTo({'{ scrollLeft: number, scrollTop: number }'})
</EuiCode>{' '}
- scrolls the grid to the specified horizontal and vertical
pixel offsets.
</p>
</li>
<li>
<p>
<EuiCode>
scrollToItem(
{
'{ align: string = "auto", columnIndex?: number, rowIndex?: number }'
}
)
</EuiCode>{' '}
- scrolls the grid to the specified row and columns indices
</p>
</li>
</ul>
<EuiCallOut title="react-window vs. EUI">
<p>
Unlike EUI&apos;s ref APIs, <EuiCode>rowIndex</EuiCode> here
refers to the <strong>visible</strong> <EuiCode>rowIndex</EuiCode>{' '}
when passed to a method of a native{' '}
<EuiCode>react-window</EuiCode> API.
</p>
<p>
For example:{' '}
<EuiCode>
{'scrollToItem({ rowIndex: 50, columnIndex: 0 })'}
</EuiCode>{' '}
will always scroll to 51st visible row on the currently visible
page, regardless of the content in the cell. In contrast,{' '}
<EuiCode>
{'setFocusedCell({ rowIndex: 50, colIndex: 0 })'}
</EuiCode>{' '}
will scroll to the 51st row in your data, which may not be the
51st visible row in the grid if it is paginated or sorted.
</p>
</EuiCallOut>

<EuiSpacer />

<p>
The below example shows how to use the internal APIs for a data grid
that opens a modal via cell actions.
that opens a modal via cell actions, that scroll to specific cells,
and that can be put into full-screen mode.
</p>
</>
),
Expand Down
16 changes: 15 additions & 1 deletion src-docs/src/views/datagrid/advanced/ref.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ for (let i = 1; i < 100; i++) {
}

export default () => {
const dataGridRef = useRef<EuiDataGridRefProps>(null);
const dataGridRef = useRef<EuiDataGridRefProps | null>(null);

// Modal
const [isModalVisible, setIsModalVisible] = useState(false);
Expand Down Expand Up @@ -161,6 +161,20 @@ export default () => {
Set cell focus
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
size="s"
onClick={() =>
dataGridRef.current!.scrollToItem?.({
rowIndex: rowIndexAction,
columnIndex: colIndexAction,
align: 'center',
})
}
>
Scroll to cell
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
size="s"
Expand Down
1 change: 1 addition & 0 deletions src/components/datagrid/data_grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export const EuiDataGrid = forwardRef<EuiDataGridRefProps, EuiDataGridProps>(
*/
useImperativeGridRef({
ref,
gridRef,
setIsFullScreen,
focusContext,
cellPopoverContext,
Expand Down
11 changes: 11 additions & 0 deletions src/components/datagrid/data_grid_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ReactElement,
AriaAttributes,
MutableRefObject,
Component,
} from 'react';
import {
VariableSizeGridProps,
Expand All @@ -28,6 +29,8 @@ import { RowHeightUtils } from './utils/row_heights';
import { IconType } from '../icon';
import { EuiTokenProps } from '../token';

export type ImperativeGridApi = Omit<Grid, keyof Component>;

export interface EuiDataGridToolbarProps {
gridWidth: number;
minSizeForControls?: number;
Expand Down Expand Up @@ -358,6 +361,14 @@ export interface EuiDataGridRefProps {
* Closes any currently open popovers in the data grid.
*/
closeCellPopover: () => void;
/**
* Scrolls to a specified top and left offset.
*/
scrollTo?: ImperativeGridApi['scrollTo'];
/**
* Scrolls to a specified rowIndex.
*/
scrollToItem?: ImperativeGridApi['scrollToItem'];
}

export interface EuiDataGridColumnResizerProps {
Expand Down
31 changes: 26 additions & 5 deletions src/components/datagrid/utils/ref.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@

import React, { useState, createRef, forwardRef } from 'react';
import { EuiDataGrid } from '../';
import { EuiDataGridRefProps } from '../data_grid_types';
import type {
EuiDataGridRefProps,
EuiDataGridSorting,
} from '../data_grid_types';

// We need to set up a test component here for sorting/pagination state to work
// The underlying imperative ref should still be forwarded and work as normal
const GridTest = forwardRef<EuiDataGridRefProps, any>((_, ref) => {
// Pagination
const [pageIndex, setPageIndex] = useState(0);
const onChangePage = (pageIndex) => setPageIndex(pageIndex);
const [pageIndex, onChangePage] = useState(0);

// Sorting
const [sortingColumns, setSortingColumns] = useState([]);
const onSort = (sortingColumns) => setSortingColumns(sortingColumns);
const [sortingColumns, onSort] = useState<EuiDataGridSorting['columns']>([]);

return (
<EuiDataGrid
Expand Down Expand Up @@ -197,4 +198,24 @@ describe('useImperativeGridRef', () => {
});
});
});

describe('scrollTo', () => {
it('scrolls the grid to a specified position', () => {
cy.get('.euiDataGrid__virtualized').should('have.prop', 'scrollTop', 0);
cy.then(() => {
ref.current.scrollTo({ scrollTop: 500, scrollLeft: 0 });
});
cy.get('.euiDataGrid__virtualized').should('have.prop', 'scrollTop', 500);
});
});

describe('scrollToItem', () => {
it('scrolls to a specific cell position, rendering the cell', () => {
cy.get('[data-gridcell-row-index="15"]').should('not.exist');
cy.then(() => {
ref.current.scrollToItem({ rowIndex: 15, columnIndex: 5 });
});
cy.get('[data-gridcell-row-index="15"]').should('exist');
});
});
});
28 changes: 25 additions & 3 deletions src/components/datagrid/utils/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Side Public License, v 1.
*/

import { useImperativeHandle, useCallback, Ref } from 'react';
import { useImperativeHandle, useCallback, Ref, RefObject } from 'react';
import type { VariableSizeGrid } from 'react-window';
import {
EuiDataGridRefProps,
EuiDataGridProps,
Expand All @@ -17,6 +18,7 @@ import {

interface Dependencies {
ref: Ref<unknown>;
gridRef: RefObject<VariableSizeGrid>;
setIsFullScreen: EuiDataGridRefProps['setIsFullScreen'];
focusContext: DataGridFocusContextShape;
cellPopoverContext: DataGridCellPopoverContextShape;
Expand All @@ -28,6 +30,7 @@ interface Dependencies {

export const useImperativeGridRef = ({
ref,
gridRef,
setIsFullScreen,
focusContext,
cellPopoverContext,
Expand Down Expand Up @@ -75,16 +78,35 @@ export const useImperativeGridRef = ({
[_openCellPopover, checkCellExists, findVisibleRowIndex]
);

const scrollTo = useCallback<VariableSizeGrid['scrollTo']>(
(...args) => gridRef.current?.scrollTo(...args),
[gridRef]
);

const scrollToItem = useCallback<VariableSizeGrid['scrollToItem']>(
(...args) => gridRef.current?.scrollToItem(...args),
[gridRef]
);

// Set the ref APIs
useImperativeHandle(
ref,
() => ({
(): EuiDataGridRefProps => ({
setIsFullScreen,
setFocusedCell,
openCellPopover,
closeCellPopover,
scrollTo,
scrollToItem,
}),
[setIsFullScreen, setFocusedCell, openCellPopover, closeCellPopover]
[
setIsFullScreen,
setFocusedCell,
openCellPopover,
closeCellPopover,
scrollTo,
scrollToItem,
]
);
};

Expand Down
1 change: 1 addition & 0 deletions upcoming_changelogs/6042.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `EuiDataGrid`'s imperative API now exposes the `scrollTo` and `scrollToItem` APIs of `react-window`.

0 comments on commit cb937f4

Please sign in to comment.