diff --git a/desktop/flipper-plugin/src/__tests__/api.node.tsx b/desktop/flipper-plugin/src/__tests__/api.node.tsx index db7958e63..cab2fc4bf 100644 --- a/desktop/flipper-plugin/src/__tests__/api.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/api.node.tsx @@ -31,6 +31,7 @@ test('Correct top level API exposed', () => { "DataDescription", "DataFormatter", "DataInspector", + "DataList", "DataSource", "DataTable", "DetailSidebar", diff --git a/desktop/flipper-plugin/src/index.ts b/desktop/flipper-plugin/src/index.ts index 7c1e1c6df..befb208c7 100644 --- a/desktop/flipper-plugin/src/index.ts +++ b/desktop/flipper-plugin/src/index.ts @@ -84,6 +84,7 @@ export {createDataSource, DataSource} from './state/DataSource'; export {DataTable, DataTableColumn} from './ui/data-table/DataTable'; export {DataTableManager} from './ui/data-table/DataTableManager'; +export {DataList} from './ui/DataList'; export { Interactive as _Interactive, diff --git a/desktop/flipper-plugin/src/state/DataSource.tsx b/desktop/flipper-plugin/src/state/DataSource.tsx index 80db9d2ac..e69ee570a 100644 --- a/desktop/flipper-plugin/src/state/DataSource.tsx +++ b/desktop/flipper-plugin/src/state/DataSource.tsx @@ -94,7 +94,10 @@ export class DataSource< private nextId = 0; private _records: Entry[] = []; private _recordsById: Map = new Map(); - private keyAttribute: undefined | keyof T; + /** + * @readonly + */ + public keyAttribute: undefined | keyof T; private idToIndex: Map = new Map(); // if we shift the window, we increase shiftOffset to correct idToIndex results, rather than remapping all values diff --git a/desktop/flipper-plugin/src/ui/DataList.tsx b/desktop/flipper-plugin/src/ui/DataList.tsx new file mode 100644 index 000000000..f6fc06764 --- /dev/null +++ b/desktop/flipper-plugin/src/ui/DataList.tsx @@ -0,0 +1,152 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import React, {useCallback, memo} from 'react'; +import {DataFormatter} from './DataFormatter'; +import {Layout} from './Layout'; +import {theme} from './theme'; +import {Typography} from 'antd'; + +const {Text} = Typography; + +interface Item { + id: string; + title: string; + description?: string; +} + +interface DataListProps { + /** + * Defines the styling of the component. By default shows a list, but alternatively the items can be displayed in a drop down + */ + type?: 'default' /* | 'compact' | 'dropdown' */; + /** + * By default the data list will take all available space and scroll if items aren't otherwise visible. + * By setting `scrollable={false}` the list will only take its natural size + */ + scrollable?: boolean; + /** + * The current selection + */ + value?: string /* | Atom*/; + /** + * Handler that is fired if selection is changed + */ + onSelect?(id: string, value: T): void; + className?: string; + style?: React.CSSProperties; + /** + * Items to display. Per item at least a title and unique id should be provided + */ + items: readonly Item[]; + /** + * Custom render function. By default the component will render the `title` in bold and description (if any) below it + */ + onRenderItem?: (item: T, selected: boolean) => React.ReactElement; +} + +export const DataList: React.FC> = function DataList< + T extends Item +>({ + // type, + scrollable, + value, + onSelect, + className, + style, + items, + onRenderItem, +}: DataListProps) { + const handleSelect = useCallback( + (key: string, item: T) => { + onSelect?.(key, item); + }, + [onSelect], + ); + + const renderedItems = items.map((item) => ( + + )); + + return scrollable ? ( + + {renderedItems} + + ) : ( + + {renderedItems} + + ); +}; + +DataList.defaultProps = { + type: 'default', + scrollable: false, + onRenderItem: defaultItemRenderer, +}; + +function defaultItemRenderer(item: Item, _selected: boolean) { + return ; +} + +const DataListItemWrapper = memo( + ({ + item, + onRenderItem, + onSelect, + selected, + }: { + item: Item; + onRenderItem: typeof defaultItemRenderer; + onSelect: (id: string, item: Item) => void; + selected: boolean; + }) => { + return ( + { + onSelect(item.id, item); + }}> + {onRenderItem(item, selected)} + + ); + }, +); + +const DataListItem = memo( + ({title, description}: {title: string; description?: string}) => { + return ( + <> + {DataFormatter.format(title)} + {description != null && ( + {DataFormatter.format(description)} + )} + + ); + }, +); diff --git a/desktop/flipper-plugin/src/ui/Layout.tsx b/desktop/flipper-plugin/src/ui/Layout.tsx index 0659d3e2f..021c5611b 100644 --- a/desktop/flipper-plugin/src/ui/Layout.tsx +++ b/desktop/flipper-plugin/src/ui/Layout.tsx @@ -88,9 +88,10 @@ const Container = styled.div( }), ); -const Horizontal = styled(Container)({ +const Horizontal = styled(Container)<{wrap?: boolean}>(({wrap}) => ({ flexDirection: 'row', -}); + flexWrap: wrap ? 'wrap' : undefined, +})); const ScrollParent = styled.div<{axis?: ScrollAxis}>(({axis}) => ({ flex: 1, diff --git a/docs/extending/flipper-plugin.mdx b/docs/extending/flipper-plugin.mdx index 228bb2f89..af97d83fb 100644 --- a/docs/extending/flipper-plugin.mdx +++ b/docs/extending/flipper-plugin.mdx @@ -809,6 +809,19 @@ See `View > Flipper Style Guide` inside the Flipper application for more details Coming soon. +### DataList + +The DataList can be used to display a set of items efficiently, and where a single item can be selected. + +Properties: + +* `items`: Items to display. Per item at least a title and unique id should be provided. +* `value`: The current selection +* `onSelect` +* `onRenderItem`: A custom render function. By default the component will render the `title` in bold and description (if any) below it. +* `type`: `default` or `dropdown. Defines the styling of the component. By default shows a list, but alternatively the items can be displayed in a drop down +* `scrollable`: By default the data list will take all available space and scroll if items aren't otherwise visible. By setting `scrollable={false}` the list will only take its natural size + ### NUX An element that can be used to provide a New User eXperience: Hints that give a one time introduction to new features to the current user.