Skip to content

Commit

Permalink
<Collection> improvements: isPagination/isSearchable (#507)
Browse files Browse the repository at this point in the history
* <Collection> improvements: isPagination/isSearchable
  • Loading branch information
hvergara authored Oct 27, 2021
1 parent fe77c15 commit 9f94bc3
Show file tree
Hide file tree
Showing 17 changed files with 473 additions and 71 deletions.
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"aws-amplify-react": "latest",
"aws-amplify": "latest",
"classnames": "^2.3.1",
"countries-list": "^2.6.1",
"gray-matter": "^4.0.3",
"lodash": "^4.17.21",
"mdx-prism": "^0.3.3",
Expand Down
93 changes: 85 additions & 8 deletions docs/src/pages/ui/primitives/collection/react.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Card, Collection, View, Text, Heading } from '@aws-amplify/ui-react';
import {
Button,
Card,
Collection,
Flex,
View,
Text,
Heading,
} from '@aws-amplify/ui-react';
import { countries } from 'countries-list';
import { CollectionDemo, ListCollectionExample } from './demo';
import { Example } from '@/components/Example';

A Collection wraps Flex and Grid components, and provides a way to display items in a collection from a data source. They come in `paginated` and `infiniteScrolling` versions. Both versions need paginated data.
A Collection wraps Flex and Grid components, and provides a way to display items in a collection from a data source.

## Demo

Expand Down Expand Up @@ -74,11 +83,11 @@ const visitNewZealand = [
</Collection>
</Example>

### Collection types
## Collection types

Collection `type` options include `list`, `grid`, and `table`.

**List collection type**
### List

The `list` collection type can be customized with any of following Flex props: `alignItems`, `alignContent`, `direction`, `gap`, `justifyContent`, `wrap`.

Expand Down Expand Up @@ -133,10 +142,78 @@ The `list` collection type can be customized with any of following Flex props: `
</Collection>
</Example>

#### Grid collection type
## Pagination

TBD
A Collection can be paginated, adding a special `isPaginated` property. Change the page size passing a `itemsPerPage` property (default: 10).

#### Table collection type
```jsx
<Collection type="list" items={countries} isPaginated itemsPerPage={12}>
{(country) => (
<Button>
{country.emoji} {country.name}
</Button>
)}
</Collection>
```

<Example>
<Collection
type="list"
direction="row"
wrap="wrap"
items={Object.values(countries).map(({ name, emoji }) => ({ name, emoji }))}
isPaginated
itemsPerPage={12}
>
{(country) => (
<Button grow="1">
{country.emoji} {country.name}
</Button>
)}
</Collection>
</Example>

## Search

TBD
Collections can also be filtered, adding a `isSearchable` property. Pass a custom `searchFilter` function to enhance your search experience (default search filter looks for any string-like property inside of items)

```jsx
const startsWith = (country, keyword) => country.name.startsWith(keyword)

<Collection
type="grid"
items={countries}
itemsPerPage={9}
isSearchable
searchFilter={startsWith}
searchPlaceholder="Type to search..."
>
{(country) => (
<Button grow="1">
{country.emoji} {country.name}
</Button>
)}
</Collection>
```

<Example>
<Collection
type="grid"
templateColumns="1fr 1fr 1fr"
gap="15px"
items={Object.values(countries).map(({ name, emoji }) => ({ name, emoji }))}
isSearchable
isPaginated
itemsPerPage={9}
searchPlaceholder="Type to search..."
searchFilter={(country, keyword) =>
country.name.toLowerCase().startsWith(keyword.toLowerCase())
}
>
{(country) => (
<Button grow="1">
{country.emoji} {country.name}
</Button>
)}
</Collection>
</Example>
1 change: 0 additions & 1 deletion packages/react/__tests__/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ describe('@aws-amplify/ui-react', () => {
"Card",
"CheckboxField",
"Collection",
"CollectionTypeMap",
"ComponentClassNames",
"ComponentPropsToStylePropsMap",
"ComponentPropsToStylePropsMapKeys",
Expand Down
117 changes: 108 additions & 9 deletions packages/react/src/primitives/Collection/Collection.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,115 @@
import * as React from 'react';
import { CollectionProps } from '../types';
import classNames from 'classnames';
import debounce from 'lodash/debounce';
import { useCallback, useState } from 'react';
import { Flex } from '../Flex';
import { Grid } from '../Grid';
import { Pagination, usePagination } from '../Pagination';
import { SearchField } from '../SearchField';
import { ComponentClassNames } from '../shared/constants';
import { SharedText } from '../shared/i18n';
import { strHasLength } from '../shared/utils';
import {
CollectionProps,
GridCollectionProps,
ListCollectionProps,
} from '../types';
import { getItemsAtPage, itemHasText } from './utils';

export const Collection = <CollectionItemType,>({
items,
const DEFAULT_PAGE_SIZE = 10;
const TYPEAHEAD_DELAY_MS = 300;

const ListCollection = <Item,>({
children,
className,
direction = 'column',
type = 'list',
items,
...rest
}: CollectionProps<CollectionItemType>) => (
<Flex direction={direction} className={className} {...rest}>
{Array.isArray(items) && items.map(children)}
}: ListCollectionProps<Item>) => (
<Flex direction={direction} {...rest}>
{Array.isArray(items) ? items.map(children) : null}
</Flex>
);

const GridCollection = <Item,>({
children,
items,
...rest
}: GridCollectionProps<Item>) => (
<Grid {...rest}>{Array.isArray(items) ? items.map(children) : null}</Grid>
);

export const Collection = <Item,>({
className,
isSearchable,
isPaginated,
items,
itemsPerPage = DEFAULT_PAGE_SIZE,
searchFilter = itemHasText,
searchPlaceholder,
type = 'list',
testId,
...rest
}: CollectionProps<Item>): JSX.Element => {
const [searchText, setSearchText] = useState<string>();

const onSearch = useCallback(debounce(setSearchText, TYPEAHEAD_DELAY_MS), [
setSearchText,
]);

// Make sure that items are iterable
items = Array.isArray(items) ? items : [];

// Filter items by text
if (isSearchable && strHasLength(searchText)) {
items = items.filter((item) => searchFilter(item, searchText));
}

// Pagination
const pagination = usePagination({
totalPages: Math.floor(items.length / itemsPerPage),
});

if (isPaginated) {
items = getItemsAtPage(items, pagination.currentPage, itemsPerPage);
}

const collection =
type === 'list' ? (
<ListCollection
className={ComponentClassNames.CollectionItems}
items={items}
{...rest}
/>
) : type === 'grid' ? (
<GridCollection
className={ComponentClassNames.CollectionItems}
items={items}
{...rest}
/>
) : null;

return (
<Flex
testId={testId}
className={classNames(ComponentClassNames.Collection, className)}
>
{isSearchable ? (
<Flex className={ComponentClassNames.CollectionSearch}>
<SearchField
label={SharedText.Collection.SearchFieldLabel}
placeholder={searchPlaceholder}
onChange={(e) => onSearch(e.target.value)}
onClear={() => setSearchText('')}
/>
</Flex>
) : null}

{collection}

{isPaginated ? (
<Flex className={ComponentClassNames.CollectionPagination}>
<Pagination {...pagination} />
</Flex>
) : null}
</Flex>
);
};
Loading

0 comments on commit 9f94bc3

Please sign in to comment.