From 22e4c8fcc0c8c55f108d95228540927355655447 Mon Sep 17 00:00:00 2001
From: Cee Chen
Date: Thu, 26 Oct 2023 13:11:38 -0700
Subject: [PATCH] [docs] Update table selection example to support switching
between controlled & uncontrolled logic
- requires creating a new prop and semi custom section
- + clean up and remove unnecessary table data not relevant to the demo(s)
---
...tsx => in_memory_selection_controlled.tsx} | 78 ++----
.../in_memory/in_memory_selection_section.js | 103 +++----
.../in_memory_selection_uncontrolled.tsx | 259 ++++++++++++++++++
...selection.tsx => selection_controlled.tsx} | 35 +--
.../tables/selection/selection_section.js | 108 ++++++--
.../selection/selection_uncontrolled.tsx | 234 ++++++++++++++++
6 files changed, 652 insertions(+), 165 deletions(-)
rename src-docs/src/views/tables/in_memory/{in_memory_selection.tsx => in_memory_selection_controlled.tsx} (77%)
create mode 100644 src-docs/src/views/tables/in_memory/in_memory_selection_uncontrolled.tsx
rename src-docs/src/views/tables/selection/{selection.tsx => selection_controlled.tsx} (86%)
create mode 100644 src-docs/src/views/tables/selection/selection_uncontrolled.tsx
diff --git a/src-docs/src/views/tables/in_memory/in_memory_selection.tsx b/src-docs/src/views/tables/in_memory/in_memory_selection_controlled.tsx
similarity index 77%
rename from src-docs/src/views/tables/in_memory/in_memory_selection.tsx
rename to src-docs/src/views/tables/in_memory/in_memory_selection_controlled.tsx
index 5d01925f869f..a6d87de580d7 100644
--- a/src-docs/src/views/tables/in_memory/in_memory_selection.tsx
+++ b/src-docs/src/views/tables/in_memory/in_memory_selection_controlled.tsx
@@ -1,18 +1,15 @@
-import React, { useState, useRef, ReactNode } from 'react';
+import React, { useState, ReactNode } from 'react';
import { faker } from '@faker-js/faker';
-import { formatDate, Random } from '../../../../../src/services';
+import { Random } from '../../../../../src/services';
import {
EuiInMemoryTable,
EuiBasicTableColumn,
EuiTableSelectionType,
EuiSearchBarProps,
- EuiLink,
EuiHealth,
EuiButton,
EuiEmptyPrompt,
- EuiFlexGroup,
- EuiFlexItem,
EuiSpacer,
} from '../../../../../src/components';
@@ -20,8 +17,6 @@ type User = {
id: number;
firstName: string | null | undefined;
lastName: string;
- github: string;
- dateOfBirth: Date;
online: boolean;
location: {
city: string;
@@ -36,8 +31,6 @@ for (let i = 0; i < 20; i++) {
id: i + 1,
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
- github: faker.internet.userName(),
- dateOfBirth: faker.date.past(),
online: faker.datatype.boolean(),
location: {
city: faker.location.city(),
@@ -46,17 +39,6 @@ for (let i = 0; i < 20; i++) {
});
}
-const onlineUsers = userData.filter((user) => user.online);
-
-const deleteUsersByIds = (...ids: number[]) => {
- ids.forEach((id) => {
- const index = userData.findIndex((user) => user.id === id);
- if (index >= 0) {
- userData.splice(index, 1);
- }
- });
-};
-
const columns: Array> = [
{
field: 'firstName',
@@ -83,23 +65,6 @@ const columns: Array> = [
show: false,
},
},
- {
- field: 'github',
- name: 'Github',
- render: (username: User['github']) => (
-
- {username}
-
- ),
- },
- {
- field: 'dateOfBirth',
- name: 'Date of Birth',
- dataType: 'date',
- render: (dateOfBirth: User['dateOfBirth']) =>
- formatDate(dateOfBirth, 'dobLong'),
- sortable: true,
- },
{
field: 'location',
name: 'Location',
@@ -153,7 +118,6 @@ export default () => {
const [selection, setSelection] = useState([]);
const [error, setError] = useState();
- const tableRef = useRef | null>(null);
const loadUsers = () => {
setMessage('Loading users...');
@@ -168,6 +132,8 @@ export default () => {
}, random.number({ min: 0, max: 3000 }));
};
+ const onlineUsers = users.filter((user) => user.online);
+
const loadUsersWithError = () => {
setMessage('Loading users...');
setLoading(true);
@@ -187,7 +153,23 @@ export default () => {
}
const onClick = () => {
- deleteUsersByIds(...selection.map((user) => user.id));
+ const deleteUsersByIds = (users: User[], ids: number[]) => {
+ const updatedUsers = [...users];
+ ids.forEach((id) => {
+ const index = updatedUsers.findIndex((user) => user.id === id);
+ if (index >= 0) {
+ updatedUsers.splice(index, 1);
+ }
+ });
+ return updatedUsers;
+ };
+
+ setUsers((users) =>
+ deleteUsersByIds(
+ users,
+ selection.map((user) => user.id)
+ )
+ );
setSelection([]);
};
@@ -256,27 +238,19 @@ export default () => {
selectableMessage: (selectable) =>
!selectable ? 'User is currently offline' : '',
onSelectionChange: (selection) => setSelection(selection),
- initialSelected: onlineUsers,
- };
-
- const onSelection = () => {
- tableRef.current?.setSelection(onlineUsers);
+ selected: selection,
};
return (
<>
-
-
- Select online users
-
-
-
+ setSelection(onlineUsers)}>
+ Select online users
+
- The following example shows how to use EuiInMemoryTable{' '}
- along with item selection. It also shows how you can display messages,
- errors and show loading indication. You can set items to be selected
- initially by passing an array of items as the{' '}
- initialSelected value inside{' '}
- selection property and passing{' '}
- itemId property to enable selection. You can also use
- the setSelection method to take complete control over
- table selection. This can be useful if you want to handle selection in
- table based on user interaction with another part of the UI.
-
+ <>
+
+ To enable selection, both the itemId and{' '}
+ selection props must be passed. The following example
+ shows how to use EuiInMemoryTable with both controlled
+ and uncontrolled item selection. It also shows how you can display
+ messages, errors and show loading indication.
+
+
+ For uncontrolled usage, where selection changes are determined entirely
+ by the user, you can set items to be selected initially by passing an
+ array of items to an array of items to{' '}
+ selection.initialSelected. You can also use{' '}
+ selected.onSelectionChange to track or respond to the
+ items that users select.
+
+
+ To completely control table selection, use{' '}
+ selection.selected instead (which requires passing{' '}
+ selected.onSelectionChange). This can be useful if
+ you want to handle selection in table based on user interaction with
+ another part of the UI.
+
+ >
+ ),
+ children: (
+ <>
+
+ }
+ uncontrolledDemo={}
+ controlledSource={controlledSource}
+ uncontrolledSource={uncontrolledSource}
+ />
+ >
),
- props: {
- EuiInMemoryTable,
- Criteria,
- CriteriaWithPagination,
- Pagination,
- EuiTableSortingType,
- EuiTableSelectionType,
- EuiTableFieldDataColumnType,
- EuiTableComputedColumnType,
- EuiTableActionsColumnType,
- DefaultItemAction,
- CustomItemAction,
- Search,
- SearchFilterConfig,
- FieldValueOptionType,
- FieldValueToggleGroupFilterItemType,
- },
- demo: ,
};
diff --git a/src-docs/src/views/tables/in_memory/in_memory_selection_uncontrolled.tsx b/src-docs/src/views/tables/in_memory/in_memory_selection_uncontrolled.tsx
new file mode 100644
index 000000000000..c3c0a1258dce
--- /dev/null
+++ b/src-docs/src/views/tables/in_memory/in_memory_selection_uncontrolled.tsx
@@ -0,0 +1,259 @@
+import React, { useState, ReactNode } from 'react';
+import { faker } from '@faker-js/faker';
+import { Random } from '../../../../../src/services';
+
+import {
+ EuiInMemoryTable,
+ EuiBasicTableColumn,
+ EuiTableSelectionType,
+ EuiSearchBarProps,
+ EuiHealth,
+ EuiButton,
+ EuiEmptyPrompt,
+} from '../../../../../src/components';
+
+type User = {
+ id: number;
+ firstName: string | null | undefined;
+ lastName: string;
+ online: boolean;
+ location: {
+ city: string;
+ country: string;
+ };
+};
+
+const userData: User[] = [];
+
+for (let i = 0; i < 20; i++) {
+ userData.push({
+ id: i + 1,
+ firstName: faker.person.firstName(),
+ lastName: faker.person.lastName(),
+ online: faker.datatype.boolean(),
+ location: {
+ city: faker.location.city(),
+ country: faker.location.country(),
+ },
+ });
+}
+
+const onlineUsers = userData.filter((user) => user.online);
+
+const columns: Array> = [
+ {
+ field: 'firstName',
+ name: 'First Name',
+ sortable: true,
+ truncateText: true,
+ mobileOptions: {
+ render: (user: User) => (
+
+ {user.firstName} {user.lastName}
+
+ ),
+ header: false,
+ truncateText: false,
+ enlarge: true,
+ width: '100%',
+ },
+ },
+ {
+ field: 'lastName',
+ name: 'Last Name',
+ truncateText: true,
+ mobileOptions: {
+ show: false,
+ },
+ },
+ {
+ field: 'location',
+ name: 'Location',
+ truncateText: true,
+ textOnly: true,
+ render: (location: User['location']) => {
+ return `${location.city}, ${location.country}`;
+ },
+ },
+ {
+ field: 'online',
+ name: 'Online',
+ dataType: 'boolean',
+ render: (online: User['online']) => {
+ const color = online ? 'success' : 'danger';
+ const label = online ? 'Online' : 'Offline';
+ return {label};
+ },
+ sortable: true,
+ mobileOptions: {
+ show: false,
+ },
+ },
+];
+
+const random = new Random();
+
+const noItemsFoundMsg = 'No users match search criteria';
+
+export default () => {
+ const [loading, setLoading] = useState(false);
+ const [users, setUsers] = useState([]);
+ const [message, setMessage] = useState(
+ No users}
+ titleSize="xs"
+ body="Looks like you don’t have any users. Let’s create some!"
+ actions={
+ {
+ loadUsers();
+ }}
+ >
+ Load Users
+
+ }
+ />
+ );
+
+ const [selection, setSelection] = useState([]);
+ const [error, setError] = useState();
+
+ const loadUsers = () => {
+ setMessage('Loading users...');
+ setLoading(true);
+ setUsers([]);
+ setError(undefined);
+ setTimeout(() => {
+ setLoading(false);
+ setMessage(noItemsFoundMsg);
+ setError(undefined);
+ setUsers(userData);
+ }, random.number({ min: 0, max: 3000 }));
+ };
+
+ const loadUsersWithError = () => {
+ setMessage('Loading users...');
+ setLoading(true);
+ setUsers([]);
+ setError(undefined);
+ setTimeout(() => {
+ setLoading(false);
+ setMessage(noItemsFoundMsg);
+ setError('ouch!... again... ');
+ setUsers([]);
+ }, random.number({ min: 0, max: 3000 }));
+ };
+
+ const renderToolsLeft = () => {
+ if (selection.length === 0) {
+ return;
+ }
+
+ const onClick = () => {
+ const deleteUsersByIds = (users: User[], ids: number[]) => {
+ const updatedUsers = [...users];
+ ids.forEach((id) => {
+ const index = updatedUsers.findIndex((user) => user.id === id);
+ if (index >= 0) {
+ updatedUsers.splice(index, 1);
+ }
+ });
+ return updatedUsers;
+ };
+
+ setUsers((users) =>
+ deleteUsersByIds(
+ users,
+ selection.map((user) => user.id)
+ )
+ );
+ setSelection([]);
+ };
+
+ return (
+
+ Delete {selection.length} Users
+
+ );
+ };
+
+ const renderToolsRight = () => {
+ return [
+ {
+ loadUsers();
+ }}
+ isDisabled={loading}
+ >
+ Load Users
+ ,
+ {
+ loadUsersWithError();
+ }}
+ isDisabled={loading}
+ >
+ Load Users (Error)
+ ,
+ ];
+ };
+
+ const search: EuiSearchBarProps = {
+ toolsLeft: renderToolsLeft(),
+ toolsRight: renderToolsRight(),
+ box: {
+ incremental: true,
+ },
+ filters: [
+ {
+ type: 'is',
+ field: 'online',
+ name: 'Online',
+ negatedName: 'Offline',
+ },
+ {
+ type: 'field_value_selection',
+ field: 'location.country',
+ name: 'Country',
+ multiSelect: false,
+ options: userData.map(({ location: { country } }) => ({
+ value: country,
+ })),
+ },
+ ],
+ };
+
+ const pagination = {
+ initialPageSize: 5,
+ pageSizeOptions: [3, 5, 8],
+ };
+
+ const selectionValue: EuiTableSelectionType = {
+ selectable: (user) => user.online,
+ selectableMessage: (selectable) =>
+ !selectable ? 'User is currently offline' : '',
+ onSelectionChange: (selection) => setSelection(selection),
+ initialSelected: onlineUsers,
+ };
+
+ return (
+
+ );
+};
diff --git a/src-docs/src/views/tables/selection/selection.tsx b/src-docs/src/views/tables/selection/selection_controlled.tsx
similarity index 86%
rename from src-docs/src/views/tables/selection/selection.tsx
rename to src-docs/src/views/tables/selection/selection_controlled.tsx
index fe6484debd6e..45173ff008f4 100644
--- a/src-docs/src/views/tables/selection/selection.tsx
+++ b/src-docs/src/views/tables/selection/selection_controlled.tsx
@@ -1,6 +1,6 @@
-import React, { useState, useRef } from 'react';
+import React, { useState } from 'react';
import { faker } from '@faker-js/faker';
-import { formatDate, Comparators } from '../../../../../src/services';
+import { Comparators } from '../../../../../src/services';
import {
EuiBasicTable,
@@ -8,7 +8,6 @@ import {
EuiTableSelectionType,
EuiTableSortingType,
Criteria,
- EuiLink,
EuiHealth,
EuiButton,
EuiFlexGroup,
@@ -20,8 +19,6 @@ type User = {
id: number;
firstName: string | null | undefined;
lastName: string;
- github: string;
- dateOfBirth: Date;
online: boolean;
location: {
city: string;
@@ -36,8 +33,6 @@ for (let i = 0; i < 20; i++) {
id: i + 1,
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
- github: faker.internet.userName(),
- dateOfBirth: faker.date.past(),
online: faker.datatype.boolean(),
location: {
city: faker.location.city(),
@@ -46,7 +41,7 @@ for (let i = 0; i < 20; i++) {
});
}
-const onlineUsers = users.filter((user) => user.online);
+const getOnlineUsers = () => users.filter((user) => user.online);
const deleteUsersByIds = (...ids: number[]) => {
ids.forEach((id) => {
@@ -83,23 +78,6 @@ const columns: Array> = [
show: false,
},
},
- {
- field: 'github',
- name: 'Github',
- render: (username: User['github']) => (
-
- {username}
-
- ),
- },
- {
- field: 'dateOfBirth',
- name: 'Date of Birth',
- dataType: 'date',
- render: (dateOfBirth: User['dateOfBirth']) =>
- formatDate(dateOfBirth, 'dobLong'),
- sortable: true,
- },
{
field: 'location',
name: 'Location',
@@ -133,10 +111,8 @@ export default () => {
const onSelectionChange = (selectedItems: User[]) => {
setSelectedItems(selectedItems);
};
-
- const tableRef = useRef(null);
const selectOnlineUsers = () => {
- tableRef.current?.setSelection(onlineUsers);
+ setSelectedItems(getOnlineUsers());
};
const selection: EuiTableSelectionType = {
@@ -144,7 +120,7 @@ export default () => {
selectableMessage: (selectable: boolean) =>
!selectable ? 'User is currently offline' : '',
onSelectionChange,
- initialSelected: onlineUsers,
+ selected: selectedItems,
};
const deleteSelectedUsers = () => {
@@ -253,7 +229,6 @@ export default () => {
{
+ const [isControlled, setIsControlled] = useState(false);
+
+ return (
+
+
+ setIsControlled(!isControlled)}
+ />
+
+
+ {isControlled ? controlledDemo : uncontrolledDemo}
+
+
+
+
+
+ );
+};
export const section = {
title: 'Adding selection to a table',
- source: [
- {
- type: GuideSectionTypes.TSX,
- code: source,
- },
- ],
text: (
-
- The following example shows how to configure selection via the{' '}
- selection
- property. You can set items to be selected initially by passing an array
- of items as the initialSelected value inside{' '}
- selection property. You can also use the{' '}
- setSelection method to take complete control over table
- selection. This can be useful if you want to handle selection in table
- based on user interaction with another part of the UI.
-
+ <>
+
+ The following example shows how to configure selection via the{' '}
+ selection property. For uncontrolled usage, where
+ selection changes are determined entirely by the user, you can set items
+ to be selected initially by passing an array of items to an array of
+ items to selection.initialSelected. You can also use{' '}
+ selected.onSelectionChange to track or respond to the
+ items that users select.
+
+
+ To completely control table selection, use{' '}
+ selection.selected instead (which requires passing{' '}
+ selected.onSelectionChange). This can be useful if
+ you want to handle selection in table based on user interaction with
+ another part of the UI.
+
+ >
+ ),
+ children: (
+ <>
+
+ }
+ uncontrolledSource={uncontrolledSource}
+ controlledDemo={}
+ controlledSource={controlledSource}
+ />
+ >
),
- components: { EuiBasicTable },
- demo: ,
};
diff --git a/src-docs/src/views/tables/selection/selection_uncontrolled.tsx b/src-docs/src/views/tables/selection/selection_uncontrolled.tsx
new file mode 100644
index 000000000000..3b0da5e20850
--- /dev/null
+++ b/src-docs/src/views/tables/selection/selection_uncontrolled.tsx
@@ -0,0 +1,234 @@
+import React, { useState } from 'react';
+import { faker } from '@faker-js/faker';
+import { Comparators } from '../../../../../src/services';
+
+import {
+ EuiBasicTable,
+ EuiBasicTableColumn,
+ EuiTableSelectionType,
+ EuiTableSortingType,
+ Criteria,
+ EuiHealth,
+ EuiButton,
+ EuiSpacer,
+} from '../../../../../src/components';
+
+type User = {
+ id: number;
+ firstName: string | null | undefined;
+ lastName: string;
+ online: boolean;
+ location: {
+ city: string;
+ country: string;
+ };
+};
+
+const users: User[] = [];
+
+for (let i = 0; i < 20; i++) {
+ users.push({
+ id: i + 1,
+ firstName: faker.person.firstName(),
+ lastName: faker.person.lastName(),
+ online: faker.datatype.boolean(),
+ location: {
+ city: faker.location.city(),
+ country: faker.location.country(),
+ },
+ });
+}
+
+const onlineUsers = users.filter((user) => user.online);
+
+const deleteUsersByIds = (...ids: number[]) => {
+ ids.forEach((id) => {
+ const index = users.findIndex((user) => user.id === id);
+ if (index >= 0) {
+ users.splice(index, 1);
+ }
+ });
+};
+
+const columns: Array> = [
+ {
+ field: 'firstName',
+ name: 'First Name',
+ sortable: true,
+ truncateText: true,
+ mobileOptions: {
+ render: (user: User) => (
+
+ {user.firstName} {user.lastName}
+
+ ),
+ header: false,
+ truncateText: false,
+ enlarge: true,
+ width: '100%',
+ },
+ },
+ {
+ field: 'lastName',
+ name: 'Last Name',
+ truncateText: true,
+ mobileOptions: {
+ show: false,
+ },
+ },
+ {
+ field: 'location',
+ name: 'Location',
+ truncateText: true,
+ textOnly: true,
+ render: (location: User['location']) => {
+ return `${location.city}, ${location.country}`;
+ },
+ },
+ {
+ field: 'online',
+ name: 'Online',
+ dataType: 'boolean',
+ render: (online: User['online']) => {
+ const color = online ? 'success' : 'danger';
+ const label = online ? 'Online' : 'Offline';
+ return {label};
+ },
+ sortable: true,
+ mobileOptions: {
+ show: false,
+ },
+ },
+];
+
+export default () => {
+ /**
+ * Selection
+ */
+ const [selectedItems, setSelectedItems] = useState([]);
+ const onSelectionChange = (selectedItems: User[]) => {
+ setSelectedItems(selectedItems);
+ };
+
+ const selection: EuiTableSelectionType = {
+ selectable: (user: User) => user.online,
+ selectableMessage: (selectable: boolean) =>
+ !selectable ? 'User is currently offline' : '',
+ onSelectionChange,
+ initialSelected: onlineUsers,
+ };
+
+ const deleteSelectedUsers = () => {
+ deleteUsersByIds(...selectedItems.map((user: User) => user.id));
+ setSelectedItems([]);
+ };
+
+ const deleteButton =
+ selectedItems.length > 0 ? (
+
+ Delete {selectedItems.length} Users
+
+ ) : null;
+
+ /**
+ * Pagination & sorting
+ */
+ const [pageIndex, setPageIndex] = useState(0);
+ const [pageSize, setPageSize] = useState(5);
+ const [sortField, setSortField] = useState('firstName');
+ const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
+
+ const onTableChange = ({ page, sort }: Criteria) => {
+ if (page) {
+ const { index: pageIndex, size: pageSize } = page;
+ setPageIndex(pageIndex);
+ setPageSize(pageSize);
+ }
+ if (sort) {
+ const { field: sortField, direction: sortDirection } = sort;
+ setSortField(sortField);
+ setSortDirection(sortDirection);
+ }
+ };
+
+ // Manually handle sorting and pagination of data
+ const findUsers = (
+ users: User[],
+ pageIndex: number,
+ pageSize: number,
+ sortField: keyof User,
+ sortDirection: 'asc' | 'desc'
+ ) => {
+ let items;
+
+ if (sortField) {
+ items = users
+ .slice(0)
+ .sort(
+ Comparators.property(sortField, Comparators.default(sortDirection))
+ );
+ } else {
+ items = users;
+ }
+
+ let pageOfItems;
+
+ if (!pageIndex && !pageSize) {
+ pageOfItems = items;
+ } else {
+ const startIndex = pageIndex * pageSize;
+ pageOfItems = items.slice(
+ startIndex,
+ Math.min(startIndex + pageSize, users.length)
+ );
+ }
+
+ return {
+ pageOfItems,
+ totalItemCount: users.length,
+ };
+ };
+
+ const { pageOfItems, totalItemCount } = findUsers(
+ users,
+ pageIndex,
+ pageSize,
+ sortField,
+ sortDirection
+ );
+
+ const pagination = {
+ pageIndex: pageIndex,
+ pageSize: pageSize,
+ totalItemCount: totalItemCount,
+ pageSizeOptions: [3, 5, 8],
+ };
+
+ const sorting: EuiTableSortingType = {
+ sort: {
+ field: sortField,
+ direction: sortDirection,
+ },
+ };
+
+ return (
+ <>
+ {deleteButton}
+
+
+
+
+ >
+ );
+};