Files
flipper/desktop/flipper-plugin/src/ui/DataList.tsx
Andrey Goncharov 97b8b8a1c4 Split flipper-plugin package
Summary:
flipper-server-companion depends on flipper-plugin. flipper-plugin includes dependencies that run only in a browser. Splitting flipper-plugin into core and browser packages helps to avoid including browser-only dependencies into flipper-server bundle.
As a result, bundle size could be cut in half. Subsequently, RSS usage drops as there is twice as less code to process for V8.

Note: it currently breaks external flipper-data-source package. It will be restored in subsequent diffs

Reviewed By: lblasa

Differential Revision: D38658285

fbshipit-source-id: 751b11fa9f3a2d938ce166687b8310ba8b059dee
2022-09-15 10:02:19 -07:00

232 lines
6.6 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and 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, {memo, createRef, useMemo, useEffect, useCallback} from 'react';
import {DataFormatter} from './DataFormatter';
import {Layout} from './Layout';
import {Typography} from 'antd';
import {
DataTable,
DataTableColumn,
DataTableProps,
ItemRenderer,
} from './data-table/DataTable';
import {RightOutlined} from '@ant-design/icons';
import {theme} from './theme';
import styled from '@emotion/styled';
import {DataTableManager} from './data-table/DataTableManager';
import {useAssertStableRef} from '../utils/useAssertStableRef';
import {DataSource} from 'flipper-plugin-core';
import {useMakeStableCallback} from '../utils/useMakeStableCallback';
const {Text} = Typography;
type DataListBaseProps<Item> = {
/**
* 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;
/**
* Handler that is fired if selection is changed
*/
selection?: string | undefined;
onSelect?(id: string | undefined, value: Item | undefined): void;
className?: string;
style?: React.CSSProperties;
/**
* Items to display. Per item at least a title and unique id should be provided
*/
items: DataSource<Item, Item[keyof Item]> | readonly Item[];
/**
* Custom render function. By default the component will render the `title` in bold and description (if any) below it
*/
onRenderItem?: ItemRenderer<Item>;
/**
* The attributes that will be picked as id/title/description for the default rendering.
* Defaults to id/title/description, but can be customized
*/
titleAttribute?: keyof Item & string;
descriptionAttribute?: keyof Item & string;
/**
* Show a right arrow by default
*/
enableArrow?: boolean;
} & (Item extends {id: string}
? {
idAttribute?: keyof Item & string; // optional if id field is present
}
: {
idAttribute: keyof Item & string;
});
export type DataListProps<Item> = DataListBaseProps<Item> &
// Some table props are set by DataList instead, so override them
Omit<DataTableProps<Item>, 'records' | 'dataSource' | 'columns' | 'onSelect'>;
export const DataList: (<T extends object>(
props: DataListProps<T>,
) => React.ReactElement) & {
Item: React.FC<DataListItemProps>;
} = Object.assign(
function <T extends object>({
onSelect: baseOnSelect,
selection,
className,
style,
items,
onRenderItem,
enableArrow,
idAttribute,
titleAttribute,
descriptionAttribute,
...tableProps
}: DataListProps<T>) {
// if a tableManagerRef is provided, we piggy back on that same ref
// eslint-disable-next-line
const tableManagerRef =
tableProps.tableManagerRef ??
createRef<undefined | DataTableManager<T>>();
useAssertStableRef(onRenderItem, 'onRenderItem');
useAssertStableRef(enableArrow, 'enableArrow');
const onSelect = useMakeStableCallback(baseOnSelect);
const handleSelect = useCallback(
(item: T | undefined) => {
if (!item) {
onSelect?.(undefined, undefined);
} else {
const id = '' + item[idAttribute!];
if (id == null) {
throw new Error(`No valid identifier for attribute ${idAttribute}`);
}
onSelect?.(id, item);
}
},
[onSelect, idAttribute],
);
useEffect(() => {
if (selection) {
tableManagerRef.current?.selectItemById(selection);
} else {
tableManagerRef.current?.clearSelection();
}
}, [selection, tableManagerRef]);
const dataListColumns: DataTableColumn<T>[] = useMemo(
() => [
{
key: idAttribute!,
wrap: true,
onRender(item: T, selected: boolean, index: number) {
return onRenderItem ? (
onRenderItem(item, selected, index)
) : (
<DataList.Item
title={item[titleAttribute!] as any}
description={item[descriptionAttribute!] as any}
enableArrow={enableArrow}
/>
);
},
},
],
[
onRenderItem,
enableArrow,
titleAttribute,
descriptionAttribute,
idAttribute,
],
);
return (
<Layout.Container style={style} className={className} grow>
<DataTable<T>
{...tableProps}
tableManagerRef={tableManagerRef}
records={Array.isArray(items) ? items : undefined!}
dataSource={Array.isArray(items) ? undefined! : (items as any)}
recordsKey={idAttribute}
columns={dataListColumns}
onSelect={handleSelect}
/>
</Layout.Container>
);
},
{
Item: memo(({title, description, enableArrow}: DataListItemProps) => {
return (
<Layout.Horizontal center grow shrink padv>
<Layout.Container grow shrink>
{typeof title === 'string' ? (
<Text strong ellipsis>
{DataFormatter.format(title)}
</Text>
) : (
title
)}
{typeof description === 'string' ? (
<Text type="secondary" ellipsis>
{DataFormatter.format(description)}
</Text>
) : (
description
)}
</Layout.Container>
{enableArrow && (
<ArrowWrapper>
<RightOutlined />
</ArrowWrapper>
)}
</Layout.Horizontal>
);
}),
},
);
(DataList as React.FC<DataListProps<any>>).defaultProps = {
type: 'default',
scrollable: true,
enableSearchbar: false,
enableColumnHeaders: false,
enableArrow: true,
enableContextMenu: false,
enableMultiSelect: false,
enableHorizontalScroll: false,
idAttribute: 'id',
titleAttribute: 'title',
descriptionAttribute: 'description',
};
(DataList.Item as React.FC<DataListItemProps>).defaultProps = {
enableArrow: true,
};
type DataListItemProps = {
// TODO: add icon support
title?: string | React.ReactElement;
description?: string | React.ReactElement;
enableArrow?: boolean;
};
const ArrowWrapper = styled.div({
flex: 0,
paddingLeft: theme.space.small,
'.anticon': {
lineHeight: '14px',
},
});