Skip to content

Commit

Permalink
List - initial implementation (#29760)
Browse files Browse the repository at this point in the history
  • Loading branch information
george-cz committed Mar 28, 2024
1 parent 61b9039 commit ff674cf
Show file tree
Hide file tree
Showing 72 changed files with 3,938 additions and 385 deletions.
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "deprecate unused ListItemButton exports",
"packageName": "@fluentui/react-shared-contexts",
"email": "jirivyhnalek@microsoft.com",
"dependentChangeType": "patch"
}
2 changes: 2 additions & 0 deletions packages/react-components/react-list-preview/config/tests.js
@@ -1 +1,3 @@
/** Jest test setup file. */

require('@testing-library/jest-dom');
@@ -0,0 +1,3 @@
import { baseConfig } from '@fluentui/scripts-cypress';

export default baseConfig;
243 changes: 243 additions & 0 deletions packages/react-components/react-list-preview/docs/ListA11y.md

Large diffs are not rendered by default.

203 changes: 203 additions & 0 deletions packages/react-components/react-list-preview/docs/MIGRATION.md
@@ -0,0 +1,203 @@
# List migration

## Migration from v8

### Composition over configuration

Compared to its v8 counterpart, the v9 `List` uses composition over configuration when it comes to rendering items, same as other components in Fluent UI React v9. This means that instead of passing an array of items to the `List` component, it's up to you to render `ListItem` components with appropriate content.

Take this example in v8:

```js
const items = [{ name: 'John' }, { name: 'Alice' }];

const MyList = () => {
return <List items={items} />;
};
```

becomes this in v9:

```js
const items = [{ name: 'John' }, { name: 'Alice' }];

const MyList = () => {
return (
<List>
{items.map(item => {
<ListItem key={item}>{item}</ListItem>;
})}
</List>
);
};
```

### Virtualization approach

Virtualization is **not part** of `List` in Fluent UI React v9. We don't want to force any particular solution for virtualization, but we provide [examples](https://react.fluentui.dev/?path=/story/preview-components-list--virtualized-list-with-actionable-items) how to use `List` with a popular library `react-window` to get the desired effect.

This makes the API of `List` much simpler.

### v8 Property mapping

Most of the v8 props are for it's virtualization functionality. Since the v9 `List` takes a different approach, most of the props cannot be directly migrated.

| v8 List | v9 List |
| ------------------------- | -------------------------------- |
| `className` | `className` |
| `componentRef` | `componentRef` |
| `getItemCountForPage` | N/A |
| `getKey` | N/A as you control the ListItems |
| `getPageHeight` | N/A |
| `getPageSpecification` | N/A |
| `getPageStyle` | N/A |
| `ignoreScrollingState` | N/A |
| `items` | render `<ListItem>` instead |
| `onPageAdded` | N/A |
| `onPagesUpdated` | N/A |
| `onRenderCell` | N/A |
| `onRenderCellConditional` | N/A |
| `onRenderPage` | N/A |
| `onRenderRoot` | N/A |
| `onRenderSurface` | N/A |
| `onShouldVirtualize` | N/A |
| `renderCount` | N/A |
| `renderEarly` | N/A |
| `renderedWindowsAhead` | N/A |
| `renderedWindowsBehind` | N/A |
| `role` | `role` |
| `startIndex` | N/A |
| `usePageCache` | N/A |
| `version` | N/A |
| - | `defaultSelectedItems` |
| - | `onSelectionChange` |
| - | `selectionMode` |

## Migration from v0

### Composition, also known as "Children API"

In Fluent UI React v9 we prefer to use composition over configuration where possible. List is no exception. the v0 list also supports composition API under a name of "Children API".

#### Children API component mapping

Migrating from a v9 Children API to v9 composition API is quite straighforward. You can replace the components like this:

- Use v9 `List` instead of v0 `List`
- Use v9 `ListItem` instead of v0 `List.Item`

For props please refer to [Property mapping](#v0-property-mapping) section.

#### Shorthand API

For Shorthand API things are a bit more complicated, as your code needs to me updated to use composition.

Take this example in v0:

```js
const items = [
{
key: 'robert',
header: 'Robert Tolbert',
content: 'Program the sensor to the SAS alarm through the haptic SQL card!',
},
{
key: 'celeste',
header: 'Celeste Burton',
content: 'Use the online FTP application to input the multi-byte application!',
},
];

const MyList = () => {
return <List items={items} />;
};
```

becomes this in v9:

```js
const items = [
{
key: 'robert',
header: 'Robert Tolbert',
content: 'Program the sensor to the SAS alarm through the haptic SQL card!',
},
{
key: 'celeste',
header: 'Celeste Burton',
content: 'Use the online FTP application to input the multi-byte application!',
},
];

const MyList = () => {
return (
<List>
{items.map(item => {
<ListItem key={item.key}>
<h2>{item.header}</h2>
<p>{item.content}></p>
</ListItem>;
})}
</List>
);
};
```

### v0 Property mapping

Compared to its v0 counterpart, the v9 List implementation is much more generic and it **doesn't have any opinion** on how it's content should look like. This means that you will **not** find layout specific props like `header`, `headerMedia`, `content` or layout specific components. This allows for much more flexible use of the component.

We recommend using a component like `Persona` where possible, or creating a custom layout component where necessary.

#### List

| v0 List | v9 List |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `accessibility` | built in, customize with `useArrowNavigationGroup` from `tabster` |
| `as` | `as` |
| `className` | `className` |
| `debug` | N/A |
| `defaultSelectedIndex` | `defaultSelectedItems` |
| `design` | N/A |
| `horizontal` | N/A - will be added in the future |
| `items` | N/A - use `ListItem` components as Children |
| `navigable` | `navigable` |
| `onSelectedIndexChange` | `onSelectionChange` |
| `ref` | `ref` |
| `selectable` | use `selectionMode` of value `single` or `multiselect` |
| `selectedIndex` | only in controlled mode, use `selection` state; see [example](https://react.fluentui.dev/?path=/story/preview-components-list--list-selection-controlled). |
| `styles` | use slots in combination with `className` |
| `truncateContent` | N/A - the `List` is not concerned about it's content |
| `truncateHeader` | N/A - the `List` is not concerned about it's content |
| `variables` | N/A - use slots in combination with `className` |
| `wrap` | N/A - the `List` is not concerned about it's content |

#### ListItem

| v0 ListItem | v9 ListItem |
| ----------------- | ------------------------------------------------------------------------------------- |
| `accessibility` | N/A |
| `as` | `as` |
| `className` | `className` |
| `content` | N/A - use children |
| `contentMedia` | N/A - use children |
| `debug` | N/A |
| `design` | N/A |
| `endMedia` | N/A - use children |
| `header` | N/A - use children |
| `headerMedia` | N/A - use children |
| `important` | N/A |
| `index` | N/A |
| `media` | N/A - use children |
| `navigable` | N/A - use `tabIndex={0}` or `navigable` on the `List` |
| `onClick` | `onAction` |
| `ref` | ref |
| `selectable` | N/A - use `List` props like `selectionMode`, `selectedItems` and `onSelectionChange` |
| `selected` | N/A - use `selectedItems` (or tracked internally when `defaultSelectedItems` is used) |
| `styles` | N/A - use `className` for any slot |
| `truncateContent` | N/A - the `List` is not concerned about it's content |
| `truncateHeader` | N/A - the `List` is not concerned about it's content |

#### Other

Other components like `ListItemContent`, `ListItemContentMedia`, `ListItemEndMedia`, `ListItemHeader`,`ListItemHeaderMedia` and `ListItemMedia` are _not_ currently present in v9 `List` implementation for the reasons mentioned above.
16 changes: 10 additions & 6 deletions packages/react-components/react-list-preview/docs/Spec.md
Expand Up @@ -2,15 +2,19 @@

## Background

_Description and use cases of this component_
A List is a component that displays a set of vertically stacked components.

## Prior Art
If you are displaying more than one dimension of the data, the List probably isn't the proper component to use, instead, consider using Table or DataGrid.

The List supports plain list items, interactive list items with one action or multiple actions. It also has support for single and multi selection built in. This can be utilized in either uncontrolled or controlled way.

_Include background research done for this component_
All of the List scenarios are also accessible, as the whole component was built with accessibility in mind. It is easily navigable with a keyboard and supports different screen reader applications.

## Prior Art

- _Link to Open UI research_
- _Link to comparison of v7 and v0_
- _Link to GitHub epic issue for the converged component_
- [Fluent UI v0 docs](https://fluentsite.z22.web.core.windows.net/components/list/definition)
- [Fluent UI v8 docs](https://developer.microsoft.com/en-us/fluentui#/controls/web/list)
- [Open UI research](https://open-ui.org/components/list.research/)

## Sample Code

Expand Down
Expand Up @@ -4,84 +4,78 @@
```ts

/// <reference types="react" />

import { Checkbox } from '@fluentui/react-checkbox';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { EventData } from '@fluentui/react-utilities';
import type { EventHandler } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import * as React_2 from 'react';
import { SelectionItemId } from '@fluentui/react-utilities';
import type { SelectionMode as SelectionMode_2 } from '@fluentui/react-utilities';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';

// @public
// @public (undocumented)
export const List: ForwardRefComponent<ListProps>;

// @public (undocumented)
export const listClassNames: SlotClassNames<ListSlots>;

// @public
export const ListItem: ForwardRefComponent<ListItemProps>;

// @public
export const ListItemButton: ForwardRefComponent<ListItemButtonProps>;

// @public (undocumented)
export const listItemButtonClassNames: SlotClassNames<ListItemButtonSlots>;

// @public
export type ListItemButtonProps = ComponentProps<ListItemButtonSlots> & {};

// @public (undocumented)
export type ListItemButtonSlots = {
root: Slot<'div'>;
};

// @public
export type ListItemButtonState = ComponentState<ListItemButtonSlots>;
export const ListItem: ForwardRefComponent<ListItemProps>;

// @public (undocumented)
export const listItemClassNames: SlotClassNames<ListItemSlots>;

// @public
export type ListItemProps = ComponentProps<ListItemSlots> & {};
export type ListItemProps = ComponentProps<ListItemSlots> & {
value?: string | number;
onAction?: (e: ListItemActionEvent) => void;
};

// @public (undocumented)
export type ListItemSlots = {
root: Slot<'div'>;
root: NonNullable<Slot<'li', 'div'>>;
checkmark?: Slot<typeof Checkbox>;
};

// @public
export type ListItemState = ComponentState<ListItemSlots>;
export type ListItemState = ComponentState<ListItemSlots> & {
selectable: boolean;
navigable: boolean;
};

// @public
export type ListProps = ComponentProps<ListSlots> & {};
export type ListProps = ComponentProps<ListSlots> & {
navigationMode?: ListNavigationMode;
selectionMode?: SelectionMode_2;
selectedItems?: SelectionItemId[];
defaultSelectedItems?: SelectionItemId[];
onSelectionChange?: EventHandler<OnListSelectionChangeData>;
};

// @public (undocumented)
export type ListSlots = {
root: Slot<'div'>;
root: NonNullable<Slot<'ul', 'div' | 'ol'>>;
};

// @public
export type ListState = ComponentState<ListSlots>;
export type ListState = ComponentState<ListSlots> & ListContextValue;

// @public
export const renderList_unstable: (state: ListState) => JSX.Element;
export const renderList_unstable: (state: ListState, contextValues: ListContextValues) => JSX.Element;

// @public
export const renderListItem_unstable: (state: ListItemState) => JSX.Element;

// @public
export const renderListItemButton_unstable: (state: ListItemButtonState) => JSX.Element;

// @public
export const useList_unstable: (props: ListProps, ref: React_2.Ref<HTMLDivElement>) => ListState;

// @public
export const useListItem_unstable: (props: ListItemProps, ref: React_2.Ref<HTMLDivElement>) => ListItemState;

// @public
export const useListItemButton_unstable: (props: ListItemButtonProps, ref: React_2.Ref<HTMLDivElement>) => ListItemButtonState;
export const useList_unstable: (props: ListProps, ref: React_2.Ref<HTMLDivElement | HTMLUListElement | HTMLOListElement>) => ListState;

// @public
export const useListItemButtonStyles_unstable: (state: ListItemButtonState) => ListItemButtonState;
export const useListItem_unstable: (props: ListItemProps, ref: React_2.Ref<HTMLLIElement | HTMLDivElement>) => ListItemState;

// @public
export const useListItemStyles_unstable: (state: ListItemState) => ListItemState;
Expand Down
10 changes: 8 additions & 2 deletions packages/react-components/react-list-preview/package.json
Expand Up @@ -2,7 +2,7 @@
"name": "@fluentui/react-list-preview",
"version": "0.0.0",
"private": true,
"description": "New fluentui react package",
"description": "React List v9",
"main": "lib-commonjs/index.js",
"module": "lib/index.js",
"typings": "./dist/index.d.ts",
Expand All @@ -27,7 +27,9 @@
"storybook": "start-storybook",
"test": "jest --passWithNoTests",
"test-ssr": "test-ssr \"./stories/**/*.stories.tsx\"",
"type-check": "tsc -b tsconfig.json"
"type-check": "tsc -b tsconfig.json",
"e2e": "cypress run --component",
"e2e:local": "cypress open --component"
},
"devDependencies": {
"@fluentui/eslint-plugin": "*",
Expand All @@ -37,7 +39,11 @@
"@fluentui/scripts-tasks": "*"
},
"dependencies": {
"@fluentui/react-checkbox": "^9.2.18",
"@fluentui/react-context-selector": "^9.1.56",
"@fluentui/react-jsx-runtime": "^9.0.34",
"@fluentui/keyboard-keys": "^9.0.7",
"@fluentui/react-tabster": "^9.19.5",
"@fluentui/react-theme": "^9.1.19",
"@fluentui/react-utilities": "^9.18.5",
"@fluentui/react-shared-contexts": "^9.15.2",
Expand Down

This file was deleted.

0 comments on commit ff674cf

Please sign in to comment.