diff --git a/package.json b/package.json index 04bec6217..d71ca493e 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "@types/react": "^16.8.24", "@types/react-dom": "^16.8.5", "@types/react-redux": "^7.1.1", + "@types/react-virtualized-auto-sizer": "^1.0.0", + "@types/react-window": "^1.8.1", "@types/redux-persist": "^4.3.1", "@types/rsocket-core": "^0.0.2", "@types/uuid": "^3.4.5", diff --git a/src/index.js b/src/index.js index 7db5955d4..01dbe37fb 100644 --- a/src/index.js +++ b/src/index.js @@ -73,17 +73,17 @@ export { TableColumnOrder, TableColumnOrderVal, TableColumnSizes, -} from './ui/components/table/types.js'; -export {default as ManagedTable} from './ui/components/table/ManagedTable.js'; -export {ManagedTableProps} from './ui/components/table/ManagedTable.js'; +} from './ui/components/table/types.tsx'; +export {default as ManagedTable} from './ui/components/table/ManagedTable.tsx'; +export {ManagedTableProps} from './ui/components/table/ManagedTable.tsx'; export { default as ManagedTable_immutable, -} from './ui/components/table/ManagedTable_immutable.js'; -export { ManagedTableProps_immutable, -} from './ui/components/table/ManagedTable_immutable.js'; -export {Value} from './ui/components/table/TypeBasedValueRenderer.js'; -export {renderValue} from './ui/components/table/TypeBasedValueRenderer.js'; +} from './ui/components/table/ManagedTable_immutable.tsx'; +export { + Value, + renderValue, +} from './ui/components/table/TypeBasedValueRenderer.tsx'; export { DataValueExtractor, DataInspectorExpanded, diff --git a/src/plugins/TableNativePlugin.tsx b/src/plugins/TableNativePlugin.tsx index 407061ebf..4cde22ba3 100644 --- a/src/plugins/TableNativePlugin.tsx +++ b/src/plugins/TableNativePlugin.tsx @@ -17,12 +17,14 @@ import Select from '../ui/components/Select'; import ErrorBlock from '../ui/components/ErrorBlock'; import FlexColumn from '../ui/components/FlexColumn'; import SearchableTable from '../ui/components/searchable/SearchableTable'; -import TableHighlightedRows from '../ui/components/table/types'; -import TableRows from '../ui/components/table/types'; -import TableColumnSizes from '../ui/components/table/types'; -import TableColumns from '../ui/components/table/types'; -import TableColumnOrderVal from '../ui/components/table/types'; -import TableBodyRow from '../ui/components/table/types'; +import { + TableHighlightedRows, + TableRows, + TableColumnSizes, + TableColumns, + TableColumnOrderVal, + TableBodyRow, +} from '../ui/components/table/types'; import DetailSidebar from '../chrome/DetailSidebar'; import {FlipperPlugin} from '../plugin'; import textContent from '../utils/textContent'; diff --git a/src/plugins/databases/ClientProtocol.js b/src/plugins/databases/ClientProtocol.js index 423c92d98..13f7f7398 100644 --- a/src/plugins/databases/ClientProtocol.js +++ b/src/plugins/databases/ClientProtocol.js @@ -5,8 +5,7 @@ * @format */ -import type {PluginClient} from '../../plugin.tsx'; -import type {Value} from '../../ui/components/table/TypeBasedValueRenderer'; +import type {PluginClient, Value} from 'flipper'; type ClientCall = Params => Promise; diff --git a/src/plugins/databases/index.js b/src/plugins/databases/index.js index 7592a9677..5e7e43ab7 100644 --- a/src/plugins/databases/index.js +++ b/src/plugins/databases/index.js @@ -25,10 +25,7 @@ import { ManagedDataInspector, } from 'flipper'; import {Component} from 'react'; -import type { - TableBodyRow, - TableRowSortOrder, -} from '../../ui/components/table/types'; +import type {TableBodyRow, TableRowSortOrder} from 'flipper'; import {FlipperPlugin} from 'flipper'; import {DatabaseClient} from './ClientProtocol'; import {renderValue} from 'flipper'; diff --git a/src/ui/components/ContextMenu.tsx b/src/ui/components/ContextMenu.tsx index 1d4c6faf5..5bce7db1e 100644 --- a/src/ui/components/ContextMenu.tsx +++ b/src/ui/components/ContextMenu.tsx @@ -21,7 +21,7 @@ type Props = { children: React.ReactNode; /** The component that is used to wrap the children. Defaults to `FlexColumn`. */ component: React.ComponentType | string; - onMouseDown: (e: React.MouseEvent) => any; + onMouseDown?: (e: React.MouseEvent) => any; }; /** diff --git a/src/ui/components/Interactive.tsx b/src/ui/components/Interactive.tsx index cb9a5f73f..5cf0dbbb9 100644 --- a/src/ui/components/Interactive.tsx +++ b/src/ui/components/Interactive.tsx @@ -60,8 +60,8 @@ type InteractiveProps = { minLeft?: number; width?: number | string; height?: number | string; - minWidth: number; - minHeight: number; + minWidth?: number; + minHeight?: number; maxWidth?: number; maxHeight?: number; onCanResize?: (sides: ResizingSides) => void; diff --git a/src/ui/components/StackTrace.tsx b/src/ui/components/StackTrace.tsx index 7b652875f..83391419e 100644 --- a/src/ui/components/StackTrace.tsx +++ b/src/ui/components/StackTrace.tsx @@ -14,6 +14,7 @@ import Glyph from './Glyph'; import styled from 'react-emotion'; import React from 'react'; import {BackgroundColorProperty} from 'csstype'; +import {TableBodyRow} from './table/types'; const Padder = styled('div')( ({ @@ -132,8 +133,8 @@ export default class StackTrace extends Component<{ return acc; }, {}); - const rows = children.map((l, i) => ({ - key: i, + const rows: TableBodyRow[] = children.map((l, i) => ({ + key: String(i), columns: Object.keys(columns).reduce((acc, cv) => { acc[cv] = { align: cv === 'lineNumber' ? 'right' : 'left', diff --git a/src/ui/components/searchable/SearchableTable.tsx b/src/ui/components/searchable/SearchableTable.tsx index ab1b1b1e0..fa64d3c3f 100644 --- a/src/ui/components/searchable/SearchableTable.tsx +++ b/src/ui/components/searchable/SearchableTable.tsx @@ -6,8 +6,8 @@ */ import {Filter} from '../filter/types'; -import ManagedTable, {ManagedTableProps} from '../table/ManagedTable.js'; -import {TableBodyRow} from '../table/types.js'; +import ManagedTable, {ManagedTableProps} from '../table/ManagedTable'; +import {TableBodyRow} from '../table/types'; import Searchable, {SearchableProps} from './Searchable'; import React, {PureComponent} from 'react'; import textContent from '../../../utils/textContent'; diff --git a/src/ui/components/searchable/SearchableTable_immutable.tsx b/src/ui/components/searchable/SearchableTable_immutable.tsx index a6f0c3150..2bd454a00 100644 --- a/src/ui/components/searchable/SearchableTable_immutable.tsx +++ b/src/ui/components/searchable/SearchableTable_immutable.tsx @@ -6,11 +6,11 @@ */ import {Filter} from '../filter/types'; -import {ManagedTableProps_immutable} from '../table/ManagedTable_immutable.js'; -import {TableBodyRow} from '../table/types.js'; +import {ManagedTableProps_immutable} from '../table/ManagedTable_immutable'; +import {TableBodyRow} from '../table/types'; import Searchable, {SearchableProps} from './Searchable'; import {PureComponent} from 'react'; -import ManagedTable_immutable from '../table/ManagedTable_immutable.js'; +import ManagedTable_immutable from '../table/ManagedTable_immutable'; import textContent from '../../../utils/textContent'; import deepEqual from 'deep-equal'; import React from 'react'; diff --git a/src/ui/components/table/ManagedTable.js b/src/ui/components/table/ManagedTable.tsx similarity index 87% rename from src/ui/components/table/ManagedTable.js rename to src/ui/components/table/ManagedTable.tsx index 36a9dd782..d2748adec 100644 --- a/src/ui/components/table/ManagedTable.js +++ b/src/ui/components/table/ManagedTable.tsx @@ -5,7 +5,7 @@ * @format */ -import type { +import { TableColumnOrder, TableColumnSizes, TableColumns, @@ -14,146 +14,143 @@ import type { TableRows, TableBodyRow, TableOnAddFilter, -} from './types.js'; - -import type {MenuTemplate} from '../ContextMenu.tsx'; - +} from './types'; +import {MenuTemplate} from '../ContextMenu'; import React from 'react'; -import styled from '../../styled/index.js'; +import styled from 'react-emotion'; import AutoSizer from 'react-virtualized-auto-sizer'; import {VariableSizeList as List} from 'react-window'; -import {clipboard} from 'electron'; -import TableHead from './TableHead.js'; -import TableRow from './TableRow.js'; -import ContextMenu from '../ContextMenu.tsx'; -import FlexColumn from '../FlexColumn.tsx'; -import createPaste from '../../../fb-stubs/createPaste.tsx'; +import {clipboard, MenuItemConstructorOptions} from 'electron'; +import TableHead from './TableHead'; +import TableRow from './TableRow'; +import ContextMenu from '../ContextMenu'; +import FlexColumn from '../FlexColumn'; +import createPaste from '../../../fb-stubs/createPaste'; import debounceRender from 'react-debounce-render'; import debounce from 'lodash.debounce'; import {DEFAULT_ROW_HEIGHT} from './types'; -import textContent from '../../../utils/textContent.tsx'; +import textContent from '../../../utils/textContent'; -export type ManagedTableProps = {| +export type ManagedTableProps = { /** * Column definitions. */ - columns: TableColumns, + columns: TableColumns; /** * Row definitions. */ - rows: TableRows, + rows: TableRows; /* * Globally unique key for persisting data between uses of a table such as column sizes. */ - tableKey?: string, + tableKey?: string; /** * Whether the table has a border. */ - floating?: boolean, + floating?: boolean; /** * Whether a row can span over multiple lines. Otherwise lines cannot wrap and * are truncated. */ - multiline?: boolean, + multiline?: boolean; /** * Whether the body is scrollable. When this is set to `true` then the table * is not scrollable. */ - autoHeight?: boolean, + autoHeight?: boolean; /** * Order of columns. */ - columnOrder?: TableColumnOrder, + columnOrder?: TableColumnOrder; /** * Initial size of the columns. */ - columnSizes?: TableColumnSizes, + columnSizes?: TableColumnSizes; /** * Value to filter rows on. Alternative to the `filter` prop. */ - filterValue?: string, + filterValue?: string; /** * Callback to filter rows. */ - filter?: (row: TableBodyRow) => boolean, + filter?: (row: TableBodyRow) => boolean; /** * Callback when the highlighted rows change. */ - onRowHighlighted?: (keys: TableHighlightedRows) => void, + onRowHighlighted?: (keys: TableHighlightedRows) => void; /** * Whether rows can be highlighted or not. */ - highlightableRows?: boolean, + highlightableRows?: boolean; /** * Whether multiple rows can be highlighted or not. */ - multiHighlight?: boolean, + multiHighlight?: boolean; /** * Height of each row. */ - rowLineHeight?: number, + rowLineHeight?: number; /** * This makes it so the scroll position sticks to the bottom of the window. * Useful for streaming data like requests, logs etc. */ - stickyBottom?: boolean, + stickyBottom?: boolean; /** * Used by SearchableTable to add filters for rows. */ - onAddFilter?: TableOnAddFilter, + onAddFilter?: TableOnAddFilter; /** * Enable or disable zebra striping. */ - zebra?: boolean, + zebra?: boolean; /** * Whether to hide the column names at the top of the table. */ - hideHeader?: boolean, + hideHeader?: boolean; /** * Rows that are highlighted initially. */ - highlightedRows?: Set, + highlightedRows?: Set; /** * Allows to create context menu items for rows. */ - buildContextMenuItems?: () => MenuTemplate, - initialSortOrder?: ?TableRowSortOrder, + buildContextMenuItems?: () => MenuTemplate; /** * Callback when sorting changes. */ - onSort?: (order: TableRowSortOrder) => void, + onSort?: (order: TableRowSortOrder) => void; /** * Initial sort order of the table. */ - initialSortOrder?: ?TableRowSortOrder, + initialSortOrder?: TableRowSortOrder; /** * Table scroll horizontally, if needed */ - horizontallyScrollable?: boolean, + horizontallyScrollable?: boolean; /** * Whether to allow navigation via arrow keys. Default: true */ - enableKeyboardNavigation?: boolean, -|}; + enableKeyboardNavigation?: boolean; +}; -type ManagedTableState = {| - highlightedRows: Set, - sortOrder: ?TableRowSortOrder, - columnOrder: TableColumnOrder, - columnSizes: TableColumnSizes, - shouldScrollToBottom: boolean, -|}; +type ManagedTableState = { + highlightedRows: Set; + sortOrder?: TableRowSortOrder; + columnOrder: TableColumnOrder; + columnSizes: TableColumnSizes; + shouldScrollToBottom: boolean; +}; -const Container = styled(FlexColumn)(props => ({ +const Container = styled(FlexColumn)((props: {canOverflow?: boolean}) => ({ overflow: props.canOverflow ? 'scroll' : 'visible', flexGrow: 1, })); -const globalTableState: {[string]: TableColumnSizes} = {}; +const globalTableState: {[key: string]: TableColumnSizes} = {}; class ManagedTable extends React.Component< ManagedTableProps, - ManagedTableState, + ManagedTableState > { static defaultProps = { highlightableRows: true, @@ -188,10 +185,10 @@ class ManagedTable extends React.Component< tableRef = React.createRef(); scrollRef: { - current: null | HTMLDivElement, + current: null | HTMLDivElement; } = React.createRef(); - dragStartIndex: ?number = null; + dragStartIndex: number | null = null; // We want to call scrollToHighlightedRows on componentDidMount. However, at // this time, tableRef is still null, because AutoSizer needs one render to @@ -225,7 +222,7 @@ class ManagedTable extends React.Component< // if columnOrder has changed if (nextProps.columnOrder !== this.props.columnOrder) { if (this.tableRef && this.tableRef.current) { - this.tableRef.current.resetAfterIndex(0); + this.tableRef.current.resetAfterIndex(0, true); } this.setState({ columnOrder: nextProps.columnOrder, @@ -238,7 +235,7 @@ class ManagedTable extends React.Component< this.tableRef.current ) { // rows were filtered, we need to recalculate heights - this.tableRef.current.resetAfterIndex(0); + this.tableRef.current.resetAfterIndex(0, true); } } @@ -336,7 +333,7 @@ class ManagedTable extends React.Component< } }; - onRowHighlighted = (highlightedRows: Set, cb?: Function) => { + onRowHighlighted = (highlightedRows: Set, cb?: () => void) => { if (!this.props.highlightableRows) { return; } @@ -385,11 +382,7 @@ class ManagedTable extends React.Component< } } - onHighlight = ( - e: SyntheticMouseEvent<>, - row: TableBodyRow, - index: number, - ) => { + onHighlight = (e: React.MouseEvent, row: TableBodyRow, index: number) => { if (e.shiftKey) { // prevents text selection e.preventDefault(); @@ -468,11 +461,7 @@ class ManagedTable extends React.Component< return selected; }; - onMouseEnterRow = ( - e: SyntheticMouseEvent<>, - row: TableBodyRow, - index: number, - ) => { + onMouseEnterRow = (e: React.MouseEvent, row: TableBodyRow, index: number) => { const {dragStartIndex} = this; const {current} = this.tableRef; if ( @@ -585,9 +574,9 @@ class ManagedTable extends React.Component< scrollDirection, scrollOffset, }: { - scrollDirection: 'forward' | 'backward', - scrollOffset: number, - scrollUpdateWasRequested: boolean, + scrollDirection: 'forward' | 'backward'; + scrollOffset: number; + scrollUpdateWasRequested: boolean; }) => { const {current} = this.scrollRef; const parent = current ? current.parentElement : null; @@ -612,13 +601,7 @@ class ManagedTable extends React.Component< ); getRow = ({index, style}) => { - const { - onAddFilter, - multiline, - zebra, - rows, - horizontallyScrollable, - } = this.props; + const {onAddFilter, multiline, zebra, rows} = this.props; const {columnOrder, columnSizes, highlightedRows} = this.state; const columnKeys = columnOrder .map(k => (k.visible ? k.key : null)) @@ -639,7 +622,6 @@ class ManagedTable extends React.Component< style={style} onAddFilter={onAddFilter} zebra={zebra} - horizontallyScrollable={horizontallyScrollable} /> ); }; @@ -664,12 +646,12 @@ class ManagedTable extends React.Component< } const width = columnSizes[col.key]; - if (isNaN(width)) { + if (typeof width === 'number' && isNaN(width)) { // non-numeric columns with, can't caluclate computedWidth = 0; break; } else { - computedWidth += parseInt(width, 10); + computedWidth += parseInt(String(width), 10); } } } diff --git a/src/ui/components/table/ManagedTable_immutable.js b/src/ui/components/table/ManagedTable_immutable.tsx similarity index 87% rename from src/ui/components/table/ManagedTable_immutable.js rename to src/ui/components/table/ManagedTable_immutable.tsx index d45459668..cd50a1151 100644 --- a/src/ui/components/table/ManagedTable_immutable.js +++ b/src/ui/components/table/ManagedTable_immutable.tsx @@ -5,7 +5,7 @@ * @format */ -import type { +import { TableColumnOrder, TableColumnSizes, TableColumns, @@ -14,142 +14,140 @@ import type { TableRows_immutable, TableBodyRow, TableOnAddFilter, -} from './types.js'; - -import type {MenuTemplate} from '../ContextMenu.tsx'; +} from './types'; +import {MenuTemplate} from '../ContextMenu'; import React from 'react'; -import styled from '../../styled/index.js'; +import styled from 'react-emotion'; import AutoSizer from 'react-virtualized-auto-sizer'; import {VariableSizeList as List} from 'react-window'; -import {clipboard} from 'electron'; -import TableHead from './TableHead.js'; -import TableRow from './TableRow.js'; -import ContextMenu from '../ContextMenu.tsx'; -import FlexColumn from '../FlexColumn.tsx'; -import createPaste from '../../../fb-stubs/createPaste.tsx'; +import {clipboard, MenuItemConstructorOptions} from 'electron'; +import TableHead from './TableHead'; +import TableRow from './TableRow'; +import ContextMenu from '../ContextMenu'; +import FlexColumn from '../FlexColumn'; +import createPaste from '../../../fb-stubs/createPaste'; import debounceRender from 'react-debounce-render'; import debounce from 'lodash.debounce'; import {DEFAULT_ROW_HEIGHT} from './types'; -import textContent from '../../../utils/textContent.tsx'; +import textContent from '../../../utils/textContent'; -export type ManagedTableProps_immutable = {| +export type ManagedTableProps_immutable = { /** * Column definitions. */ - columns: TableColumns, + columns: TableColumns; /** * Row definitions. */ - rows: TableRows_immutable, + rows: TableRows_immutable; /* * Globally unique key for persisting data between uses of a table such as column sizes. */ - tableKey?: string, + tableKey?: string; /** * Whether the table has a border. */ - floating?: boolean, + floating?: boolean; /** * Whether a row can span over multiple lines. Otherwise lines cannot wrap and * are truncated. */ - multiline?: boolean, + multiline?: boolean; /** * Whether the body is scrollable. When this is set to `true` then the table * is not scrollable. */ - autoHeight?: boolean, + autoHeight?: boolean; /** * Order of columns. */ - columnOrder?: TableColumnOrder, + columnOrder?: TableColumnOrder; /** * Initial size of the columns. */ - columnSizes?: TableColumnSizes, + columnSizes?: TableColumnSizes; /** * Value to filter rows on. Alternative to the `filter` prop. */ - filterValue?: string, + filterValue?: string; /** * Callback to filter rows. */ - filter?: (row: TableBodyRow) => boolean, + filter?: (row: TableBodyRow) => boolean; /** * Callback when the highlighted rows change. */ - onRowHighlighted?: (keys: TableHighlightedRows) => void, + onRowHighlighted?: (keys: TableHighlightedRows) => void; /** * Whether rows can be highlighted or not. */ - highlightableRows?: boolean, + highlightableRows?: boolean; /** * Whether multiple rows can be highlighted or not. */ - multiHighlight?: boolean, + multiHighlight?: boolean; /** * Height of each row. */ - rowLineHeight?: number, + rowLineHeight?: number; /** * This makes it so the scroll position sticks to the bottom of the window. * Useful for streaming data like requests, logs etc. */ - stickyBottom?: boolean, + stickyBottom?: boolean; /** * Used by SearchableTable to add filters for rows. */ - onAddFilter?: TableOnAddFilter, + onAddFilter?: TableOnAddFilter; /** * Enable or disable zebra striping. */ - zebra?: boolean, + zebra?: boolean; /** * Whether to hide the column names at the top of the table. */ - hideHeader?: boolean, + hideHeader?: boolean; /** * Rows that are highlighted initially. */ - highlightedRows?: Set, + highlightedRows?: Set; /** * Allows to create context menu items for rows. */ - buildContextMenuItems?: () => MenuTemplate, - initialSortOrder?: ?TableRowSortOrder, + buildContextMenuItems?: () => MenuTemplate; /** * Callback when sorting changes. */ - onSort?: (order: TableRowSortOrder) => void, + onSort?: (order: TableRowSortOrder) => void; /** * Initial sort order of the table. */ - initialSortOrder?: ?TableRowSortOrder, + initialSortOrder?: TableRowSortOrder; /** * Table scroll horizontally, if needed */ - horizontallyScrollable?: boolean, -|}; + horizontallyScrollable?: boolean; +}; -type ManagedTableState = {| - highlightedRows: Set, - sortOrder: ?TableRowSortOrder, - columnOrder: TableColumnOrder, - columnSizes: TableColumnSizes, - shouldScrollToBottom: boolean, -|}; +type ManagedTableState = { + highlightedRows: Set; + sortOrder?: TableRowSortOrder; + columnOrder: TableColumnOrder; + columnSizes: TableColumnSizes; + shouldScrollToBottom: boolean; +}; -const Container = styled(FlexColumn)(props => ({ +const Container = styled(FlexColumn)((props: {canOverflow?: boolean}) => ({ overflow: props.canOverflow ? 'scroll' : 'visible', flexGrow: 1, })); -const globalTableState: {[string]: TableColumnSizes} = {}; +const globalTableState: {[key: string]: TableColumnSizes} = {}; class ManagedTable extends React.Component< ManagedTableProps_immutable, - ManagedTableState, + ManagedTableState > { static defaultProps = { highlightableRows: true, @@ -183,10 +181,10 @@ class ManagedTable extends React.Component< tableRef = React.createRef(); scrollRef: { - current: null | HTMLDivElement, + current: null | HTMLDivElement; } = React.createRef(); - dragStartIndex: ?number = null; + dragStartIndex: number | null = null; // We want to call scrollToHighlightedRows on componentDidMount. However, at // this time, tableRef is still null, because AutoSizer needs one render to @@ -220,7 +218,7 @@ class ManagedTable extends React.Component< // if columnOrder has changed if (nextProps.columnOrder !== this.props.columnOrder) { if (this.tableRef && this.tableRef.current) { - this.tableRef.current.resetAfterIndex(0); + this.tableRef.current.resetAfterIndex(0, true); } this.setState({ columnOrder: nextProps.columnOrder, @@ -233,7 +231,7 @@ class ManagedTable extends React.Component< this.tableRef.current ) { // rows were filtered, we need to recalculate heights - this.tableRef.current.resetAfterIndex(0); + this.tableRef.current.resetAfterIndex(0, true); } } @@ -331,7 +329,7 @@ class ManagedTable extends React.Component< } }; - onRowHighlighted = (highlightedRows: Set, cb?: Function) => { + onRowHighlighted = (highlightedRows: Set, cb?: () => void) => { if (!this.props.highlightableRows) { return; } @@ -380,11 +378,7 @@ class ManagedTable extends React.Component< } } - onHighlight = ( - e: SyntheticMouseEvent<>, - row: TableBodyRow, - index: number, - ) => { + onHighlight = (e: React.MouseEvent, row: TableBodyRow, index: number) => { if (e.shiftKey) { // prevents text selection e.preventDefault(); @@ -466,11 +460,7 @@ class ManagedTable extends React.Component< return selected; }; - onMouseEnterRow = ( - e: SyntheticMouseEvent<>, - row: TableBodyRow, - index: number, - ) => { + onMouseEnterRow = (e: React.MouseEvent, row: TableBodyRow, index: number) => { const {dragStartIndex} = this; const {current} = this.tableRef; if ( @@ -493,7 +483,7 @@ class ManagedTable extends React.Component< clipboard.writeText(cellText); }; - buildContextMenuItems: () => Array = () => { + buildContextMenuItems: () => MenuItemConstructorOptions[] = () => { const {highlightedRows} = this.state; if (highlightedRows.size === 0) { return []; @@ -584,9 +574,9 @@ class ManagedTable extends React.Component< scrollDirection, scrollOffset, }: { - scrollDirection: 'forward' | 'backward', - scrollOffset: number, - scrollUpdateWasRequested: boolean, + scrollDirection: 'forward' | 'backward'; + scrollOffset: number; + scrollUpdateWasRequested: boolean; }) => { const {current} = this.scrollRef; const parent = current ? current.parentElement : null; @@ -611,13 +601,7 @@ class ManagedTable extends React.Component< ); getRow = ({index, style}) => { - const { - onAddFilter, - multiline, - zebra, - rows, - horizontallyScrollable, - } = this.props; + const {onAddFilter, multiline, zebra, rows} = this.props; const {columnOrder, columnSizes, highlightedRows} = this.state; const columnKeys = columnOrder .map(k => (k.visible ? k.key : null)) @@ -643,7 +627,6 @@ class ManagedTable extends React.Component< style={style} onAddFilter={onAddFilter} zebra={zebra} - horizontallyScrollable={horizontallyScrollable} /> ); }; @@ -668,12 +651,12 @@ class ManagedTable extends React.Component< } const width = columnSizes[col.key]; - if (isNaN(width)) { + if (typeof width === 'number' && isNaN(width)) { // non-numeric columns with, can't caluclate computedWidth = 0; break; } else { - computedWidth += parseInt(width, 10); + computedWidth += parseInt(String(width), 10); } } } diff --git a/src/ui/components/table/TableHead.js b/src/ui/components/table/TableHead.tsx similarity index 74% rename from src/ui/components/table/TableHead.js rename to src/ui/components/table/TableHead.tsx index 45c5d4b86..0dea68ccb 100644 --- a/src/ui/components/table/TableHead.js +++ b/src/ui/components/table/TableHead.tsx @@ -5,27 +5,24 @@ * @format */ -import type { +import { TableColumnOrder, TableColumnSizes, TableColumns, TableOnColumnResize, TableOnSort, TableRowSortOrder, -} from './types.js'; - -import {normaliseColumnWidth, isPercentage} from './utils.js'; +} from './types'; +import {normaliseColumnWidth, isPercentage} from './utils'; import {PureComponent} from 'react'; -import ContextMenu from '../ContextMenu.tsx'; -import Interactive from '../Interactive.tsx'; -import styled from '../../styled/index.js'; -import {colors} from '../colors.tsx'; - -import FlexRow from '../FlexRow.tsx'; - +import ContextMenu from '../ContextMenu'; +import Interactive from '../Interactive'; +import styled from 'react-emotion'; +import {colors} from '../colors'; +import FlexRow from '../FlexRow'; import invariant from 'invariant'; - -type MenuTemplate = Array; +import {MenuItemConstructorOptions} from 'electron'; +import React from 'react'; const TableHeaderArrow = styled('span')({ float: 'right', @@ -43,41 +40,45 @@ const TableHeaderColumnContainer = styled('div')({ padding: '0 8px', }); -const TableHeadContainer = styled(FlexRow)(props => ({ - borderBottom: `1px solid ${colors.sectionHeaderBorder}`, - color: colors.light50, - flexShrink: 0, - left: 0, - overflow: 'hidden', - right: 0, - textAlign: 'left', - top: 0, - zIndex: 2, - minWidth: props.horizontallyScrollable ? 'min-content' : 0, -})); - -const TableHeadColumnContainer = styled('div')(props => ({ - position: 'relative', - backgroundColor: colors.white, - flexShrink: props.width === 'flex' ? 1 : 0, - height: 23, - lineHeight: '23px', - fontSize: '0.85em', - fontWeight: 500, - width: props.width === 'flex' ? '100%' : props.width, - '&::after': { - position: 'absolute', - content: '""', +const TableHeadContainer = styled(FlexRow)( + (props: {horizontallyScrollable?: boolean}) => ({ + borderBottom: `1px solid ${colors.sectionHeaderBorder}`, + color: colors.light50, + flexShrink: 0, + left: 0, + overflow: 'hidden', right: 0, - top: 5, - height: 13, - width: 1, - background: colors.light15, - }, - '&:last-child::after': { - display: 'none', - }, -})); + textAlign: 'left', + top: 0, + zIndex: 2, + minWidth: props.horizontallyScrollable ? 'min-content' : 0, + }), +); + +const TableHeadColumnContainer = styled('div')( + (props: {width: string | number}) => ({ + position: 'relative', + backgroundColor: colors.white, + flexShrink: props.width === 'flex' ? 1 : 0, + height: 23, + lineHeight: '23px', + fontSize: '0.85em', + fontWeight: 500, + width: props.width === 'flex' ? '100%' : props.width, + '&::after': { + position: 'absolute', + content: '""', + right: 0, + top: 5, + height: 13, + width: 1, + background: colors.light15, + }, + '&:last-child::after': { + display: 'none', + }, + }), +); const RIGHT_RESIZABLE = {right: true}; @@ -86,19 +87,19 @@ function calculatePercentage(parentWidth: number, selfWidth: number): string { } class TableHeadColumn extends PureComponent<{ - id: string, - width: string | number, - sortable: ?boolean, - isResizable: boolean, - leftHasResizer: boolean, - hasFlex: boolean, - sortOrder: ?TableRowSortOrder, - onSort: ?TableOnSort, - columnSizes: TableColumnSizes, - onColumnResize: ?TableOnColumnResize, - children?: React$Node, - title?: string, - horizontallyScrollable?: boolean, + id: string; + width: string | number; + sortable?: boolean; + isResizable: boolean; + leftHasResizer: boolean; + hasFlex: boolean; + sortOrder?: TableRowSortOrder; + onSort?: TableOnSort; + columnSizes: TableColumnSizes; + onColumnResize?: TableOnColumnResize; + children?: React.ReactNode; + title?: string; + horizontallyScrollable?: boolean; }> { ref: HTMLElement; @@ -131,7 +132,7 @@ class TableHeadColumn extends PureComponent<{ return; } - let normalizedWidth = newWidth; + let normalizedWidth: number | string = newWidth; // normalise number to a percentage if we were originally passed a percentage if (isPercentage(width)) { @@ -191,16 +192,16 @@ class TableHeadColumn extends PureComponent<{ } export default class TableHead extends PureComponent<{ - columnOrder: TableColumnOrder, - onColumnOrder: ?(order: TableColumnOrder) => void, - columns: TableColumns, - sortOrder: ?TableRowSortOrder, - onSort: ?TableOnSort, - columnSizes: TableColumnSizes, - onColumnResize: ?TableOnColumnResize, - horizontallyScrollable?: boolean, + columnOrder: TableColumnOrder; + onColumnOrder?: (order: TableColumnOrder) => void; + columns: TableColumns; + sortOrder?: TableRowSortOrder; + onSort?: TableOnSort; + columnSizes: TableColumnSizes; + onColumnResize?: TableOnColumnResize; + horizontallyScrollable?: boolean; }> { - buildContextMenu = (): MenuTemplate => { + buildContextMenu = (): MenuItemConstructorOptions[] => { const visibles = this.props.columnOrder .map(c => (c.visible ? c.key : null)) .filter(Boolean) @@ -231,7 +232,7 @@ export default class TableHead extends PureComponent<{ } } }, - type: 'checkbox', + type: 'checkbox' as 'checkbox', checked: visible, }; }); diff --git a/src/ui/components/table/TableRow.js b/src/ui/components/table/TableRow.js deleted file mode 100644 index 3471307f7..000000000 --- a/src/ui/components/table/TableRow.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Copyright 2018-present Facebook. - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * @format - */ - -import type { - TableColumnKeys, - TableColumnSizes, - TableOnAddFilter, - TableBodyRow, -} from './types.js'; - -import React from 'react'; -import FilterRow from '../filter/FilterRow.tsx'; -import styled from 'react-emotion'; -import FlexRow from '../FlexRow.tsx'; -import {colors} from '../colors.tsx'; -import {normaliseColumnWidth} from './utils.js'; -import {DEFAULT_ROW_HEIGHT} from './types'; - -const backgroundColor = props => { - if (props.highlighted) { - if (props.highlightedBackgroundColor) { - return props.highlightedBackgroundColor; - } else { - return colors.macOSTitleBarIconSelected; - } - } else { - if (props.backgroundColor) { - return props.backgroundColor; - } else if (props.even && props.zebra) { - return colors.light02; - } else { - return 'transparent'; - } - } -}; - -const TableBodyRowContainer = styled(FlexRow)(props => ({ - backgroundColor: backgroundColor(props), - boxShadow: props.zebra ? 'none' : 'inset 0 -1px #E9EBEE', - color: props.highlighted ? colors.white : props.color || 'inherit', - '& *': { - color: props.highlighted ? `${colors.white} !important` : null, - }, - '& img': { - backgroundColor: props.highlighted ? `${colors.white} !important` : 'none', - }, - height: props.multiline ? 'auto' : props.rowLineHeight, - lineHeight: `${String(props.rowLineHeight || DEFAULT_ROW_HEIGHT)}px`, - fontWeight: props.fontWeight || 'inherit', - overflow: 'hidden', - width: '100%', - userSelect: 'none', - flexShrink: 0, - '&:hover': { - backgroundColor: - !props.highlighted && props.highlightOnHover ? colors.light02 : 'none', - }, -})); - -const TableBodyColumnContainer = styled('div')(props => ({ - display: 'flex', - flexShrink: props.width === 'flex' ? 1 : 0, - overflow: 'hidden', - padding: '0 8px', - userSelect: 'none', - textOverflow: 'ellipsis', - verticalAlign: 'top', - whiteSpace: props.multiline ? 'normal' : 'nowrap', - wordWrap: props.multiline ? 'break-word' : 'normal', - width: props.width === 'flex' ? '100%' : props.width, - maxWidth: '100%', - justifyContent: props.justifyContent, -})); - -type Props = { - columnSizes: TableColumnSizes, - columnKeys: TableColumnKeys, - onMouseDown: (e: SyntheticMouseEvent<>) => mixed, - onMouseEnter?: (e: SyntheticMouseEvent<>) => void, - multiline: ?boolean, - rowLineHeight: number, - highlighted: boolean, - row: TableBodyRow, - index: number, - style: ?Object, - onAddFilter?: TableOnAddFilter, - zebra: ?boolean, -}; - -export default class TableRow extends React.PureComponent { - static defaultProps = { - zebra: true, - }; - - render() { - const { - index, - highlighted, - rowLineHeight, - row, - style, - multiline, - columnKeys, - columnSizes, - onMouseEnter, - onMouseDown, - zebra, - onAddFilter, - } = this.props; - - return ( - - {columnKeys.map(key => { - const col = row.columns[key]; - - const isFilterable = col?.isFilterable || false; - const value = col?.value; - const title = col?.title; - - return ( - - {isFilterable && onAddFilter != null ? ( - - {value} - - ) : ( - value - )} - - ); - })} - - ); - } -} diff --git a/src/ui/components/table/TableRow.tsx b/src/ui/components/table/TableRow.tsx new file mode 100644 index 000000000..22c8eaab7 --- /dev/null +++ b/src/ui/components/table/TableRow.tsx @@ -0,0 +1,185 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import { + TableColumnKeys, + TableColumnSizes, + TableOnAddFilter, + TableBodyRow, +} from './types'; +import React from 'react'; +import FilterRow from '../filter/FilterRow'; +import styled from 'react-emotion'; +import FlexRow from '../FlexRow'; +import {colors} from '../colors'; +import {normaliseColumnWidth} from './utils'; +import {DEFAULT_ROW_HEIGHT} from './types'; +import { + FontWeightProperty, + ColorProperty, + JustifyContentProperty, + BackgroundColorProperty, +} from 'csstype'; + +type TableBodyRowContainerProps = { + even?: boolean; + zebra?: boolean; + highlighted?: boolean; + rowLineHeight?: number; + multiline?: boolean; + fontWeight?: FontWeightProperty; + color?: ColorProperty; + highlightOnHover?: boolean; + backgroundColor?: BackgroundColorProperty; + highlightedBackgroundColor?: BackgroundColorProperty; +}; + +const backgroundColor = (props: TableBodyRowContainerProps) => { + if (props.highlighted) { + if (props.highlightedBackgroundColor) { + return props.highlightedBackgroundColor; + } else { + return colors.macOSTitleBarIconSelected; + } + } else { + if (props.backgroundColor) { + return props.backgroundColor; + } else if (props.even && props.zebra) { + return colors.light02; + } else { + return 'transparent'; + } + } +}; + +const TableBodyRowContainer = styled(FlexRow)( + (props: TableBodyRowContainerProps) => ({ + backgroundColor: backgroundColor(props), + boxShadow: props.zebra ? 'none' : 'inset 0 -1px #E9EBEE', + color: props.highlighted ? colors.white : props.color || 'inherit', + '& *': { + color: props.highlighted ? `${colors.white} !important` : null, + }, + '& img': { + backgroundColor: props.highlighted + ? `${colors.white} !important` + : 'none', + }, + height: props.multiline ? 'auto' : props.rowLineHeight, + lineHeight: `${String(props.rowLineHeight || DEFAULT_ROW_HEIGHT)}px`, + fontWeight: props.fontWeight || 'inherit', + overflow: 'hidden', + width: '100%', + userSelect: 'none', + flexShrink: 0, + '&:hover': { + backgroundColor: + !props.highlighted && props.highlightOnHover ? colors.light02 : 'none', + }, + }), +); + +const TableBodyColumnContainer = styled('div')( + (props: { + width?: any; + multiline?: boolean; + justifyContent: JustifyContentProperty; + }) => ({ + display: 'flex', + flexShrink: props.width === 'flex' ? 1 : 0, + overflow: 'hidden', + padding: '0 8px', + userSelect: 'none', + textOverflow: 'ellipsis', + verticalAlign: 'top', + whiteSpace: props.multiline ? 'normal' : 'nowrap', + wordWrap: props.multiline ? 'break-word' : 'normal', + width: props.width === 'flex' ? '100%' : props.width, + maxWidth: '100%', + justifyContent: props.justifyContent, + }), +); + +type Props = { + columnSizes: TableColumnSizes; + columnKeys: TableColumnKeys; + onMouseDown: (e: React.MouseEvent) => any; + onMouseEnter?: (e: React.MouseEvent) => void; + multiline?: boolean; + rowLineHeight: number; + highlighted: boolean; + row: TableBodyRow; + index: number; + style?: React.CSSProperties | null; + onAddFilter?: TableOnAddFilter; + zebra?: boolean; +}; + +export default class TableRow extends React.PureComponent { + static defaultProps = { + zebra: true, + }; + + render() { + const { + index, + highlighted, + rowLineHeight, + row, + style, + multiline, + columnKeys, + columnSizes, + onMouseEnter, + onMouseDown, + zebra, + onAddFilter, + } = this.props; + + return ( + + {columnKeys.map(key => { + const col = row.columns[key]; + + const isFilterable = Boolean(col && col.isFilterable); + const value = col && col.value ? col.value : null; + const title = col && col.title ? col.title : null; + + return ( + + {isFilterable && onAddFilter != null ? ( + + {value} + + ) : ( + value + )} + + ); + })} + + ); + } +} diff --git a/src/ui/components/table/TypeBasedValueRenderer.js b/src/ui/components/table/TypeBasedValueRenderer.tsx similarity index 74% rename from src/ui/components/table/TypeBasedValueRenderer.js rename to src/ui/components/table/TypeBasedValueRenderer.tsx index d6ae66557..2ee09c1a5 100644 --- a/src/ui/components/table/TypeBasedValueRenderer.js +++ b/src/ui/components/table/TypeBasedValueRenderer.tsx @@ -5,24 +5,25 @@ * @format */ import {default as styled} from 'react-emotion'; -import {colors} from '../colors.tsx'; -import {default as Text} from '../Text.tsx'; +import {colors} from '../colors'; +import {default as Text} from '../Text'; +import React from 'react'; export type Value = | { - type: 'string', - value: string, + type: 'string'; + value: string; } | { - type: 'boolean', - value: boolean, + type: 'boolean'; + value: boolean; } | { - type: 'integer' | 'float' | 'double' | 'number', - value: number, + type: 'integer' | 'float' | 'double' | 'number'; + value: number; } | { - type: 'null', + type: 'null'; }; const NonWrappingText = styled(Text)({ @@ -32,7 +33,7 @@ const NonWrappingText = styled(Text)({ userSelect: 'none', }); -const BooleanValue = styled(NonWrappingText)(props => ({ +const BooleanValue = styled(NonWrappingText)((props: {active?: boolean}) => ({ '&::before': { content: '""', display: 'inline-block', @@ -63,6 +64,6 @@ export function renderValue(val: Value) { case 'null': return NULL; default: - return {val.value}; + return ; } } diff --git a/src/ui/components/table/types.js b/src/ui/components/table/types.tsx similarity index 54% rename from src/ui/components/table/types.js rename to src/ui/components/table/types.tsx index fd4dc2deb..297193866 100644 --- a/src/ui/components/table/types.js +++ b/src/ui/components/table/types.tsx @@ -5,23 +5,23 @@ * @format */ -import type {Filter} from '../filter/types.tsx'; - +import {Filter} from '../filter/types'; import {List} from 'immutable'; +import {BackgroundColorProperty} from 'csstype'; export const MINIMUM_COLUMN_WIDTH = 100; export const DEFAULT_COLUMN_WIDTH = 200; export const DEFAULT_ROW_HEIGHT = 23; export type TableColumnOrderVal = { - key: string, - visible: boolean, + key: string; + visible: boolean; }; export type TableColumnOrder = Array; export type TableColumnSizes = { - [key: string]: string | number, + [key: string]: string | number; }; export type TableHighlightedRows = Array; @@ -33,55 +33,55 @@ export type TableOnColumnOrder = (order: TableColumnOrder) => void; export type TableOnSort = (order: TableRowSortOrder) => void; export type TableOnHighlight = ( highlightedRows: TableHighlightedRows, - e: SyntheticUIEvent<>, + e: React.UIEvent, ) => void; -export type TableHeaderColumn = {| - value: string, - sortable?: boolean, - resizable?: boolean, -|}; +export type TableHeaderColumn = { + value: string; + sortable?: boolean; + resizable?: boolean; +}; -export type TableBodyRow = {| - key: string, - height?: ?number, - filterValue?: ?string, - backgroundColor?: ?string, - sortKey?: string | number, - style?: Object, - type?: ?string, - highlightedBackgroundColor?: ?string, - onDoubleClick?: (e: SyntheticMouseEvent<>) => void, - copyText?: string, - highlightOnHover?: boolean, +export type TableBodyRow = { + key: string; + height?: number | null | undefined; + filterValue?: string | null | undefined; + backgroundColor?: string | null | undefined; + sortKey?: string | number; + style?: Object; + type?: string | null | undefined; + highlightedBackgroundColor?: BackgroundColorProperty | null | undefined; + onDoubleClick?: (e: React.MouseEvent) => void; + copyText?: string; + highlightOnHover?: boolean; columns: { - [key: string]: TableBodyColumn, - }, -|}; + [key: string]: TableBodyColumn; + }; +}; -export type TableBodyColumn = {| - sortValue?: string | number, - isFilterable?: boolean, - value: any, - align?: 'left' | 'center' | 'right' | 'flex-start' | 'flex-end', - title?: string, -|}; +export type TableBodyColumn = { + sortValue?: string | number; + isFilterable?: boolean; + value: any; + align?: 'left' | 'center' | 'right' | 'flex-start' | 'flex-end'; + title?: string; +}; export type TableColumns = { - [key: string]: TableHeaderColumn, + [key: string]: TableHeaderColumn; }; export type TableRows = Array; export type TableRows_immutable = List; -export type TableRowSortOrder = {| - key: string, - direction: 'up' | 'down', -|}; +export type TableRowSortOrder = { + key: string; + direction: 'up' | 'down'; +}; export type TableOnDragSelect = ( - e: SyntheticMouseEvent<>, + e: React.MouseEvent, key: string, index: number, ) => void; diff --git a/src/ui/components/table/utils.js b/src/ui/components/table/utils.tsx similarity index 86% rename from src/ui/components/table/utils.js rename to src/ui/components/table/utils.tsx index fc865d486..710b58149 100644 --- a/src/ui/components/table/utils.js +++ b/src/ui/components/table/utils.tsx @@ -6,7 +6,7 @@ */ export function normaliseColumnWidth( - width: void | string | number, + width: string | number | null | undefined, ): number | string { if (width == null || width === 'flex') { // default @@ -26,6 +26,6 @@ export function normaliseColumnWidth( throw new TypeError(`Unknown value ${width} for table column width`); } -export function isPercentage(width: mixed): boolean { +export function isPercentage(width: any): boolean { return typeof width === 'string' && width[width.length - 1] === '%'; } diff --git a/src/ui/index.js b/src/ui/index.js index ec2e3c2a6..13324eeea 100644 --- a/src/ui/index.js +++ b/src/ui/index.js @@ -38,18 +38,18 @@ export type { TableColumnOrder, TableColumnOrderVal, TableColumnSizes, -} from './components/table/types.js'; -export {default as ManagedTable} from './components/table/ManagedTable.js'; -export type {ManagedTableProps} from './components/table/ManagedTable.js'; +} from './components/table/types.tsx'; +export {default as ManagedTable} from './components/table/ManagedTable.tsx'; +export type {ManagedTableProps} from './components/table/ManagedTable.tsx'; export { default as ManagedTable_immutable, -} from './components/table/ManagedTable_immutable.js'; +} from './components/table/ManagedTable_immutable.tsx'; export type { ManagedTableProps_immutable, -} from './components/table/ManagedTable_immutable.js'; +} from './components/table/ManagedTable_immutable.tsx'; -export type {Value} from './components/table/TypeBasedValueRenderer.js'; -export {renderValue} from './components/table/TypeBasedValueRenderer.js'; +export type {Value} from './components/table/TypeBasedValueRenderer.tsx'; +export {renderValue} from './components/table/TypeBasedValueRenderer.tsx'; // export type { diff --git a/tsconfig.json b/tsconfig.json index 9ccd48cf5..65541c9b2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,6 @@ "flipper": ["./src/index.js"] } }, - "include": ["src/**/*", "types/globals.tsx", "types/nodejs.tsx"], + "include": ["src/**/*", "types/*"], "exclude": ["node_modules", "**/*.spec.ts"] } diff --git a/types/ReactDebounceRender.d.tsx b/types/ReactDebounceRender.d.tsx new file mode 100644 index 000000000..854dc63b1 --- /dev/null +++ b/types/ReactDebounceRender.d.tsx @@ -0,0 +1,18 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +declare module 'react-debounce-render' { + export default function

( + component: React.ComponentType

, + maxWait: number, + options: { + maxWait?: number; + leading?: boolean; + trailing?: boolean; + }, + ): React.ComponentType

) => void}>; +} diff --git a/yarn.lock b/yarn.lock index 2d03aa820..1529b2eb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1185,6 +1185,20 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-virtualized-auto-sizer@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.0.tgz#fc32f30a8dab527b5816f3a757e1e1d040c8f272" + integrity sha512-NMErdIdSnm2j/7IqMteRiRvRulpjoELnXWUwdbucYCz84xG9PHcoOrr7QfXwB/ku7wd6egiKFrzt/+QK4Imeeg== + dependencies: + "@types/react" "*" + +"@types/react-window@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.1.tgz#6e1ceab2e6f2f78dbf1f774ee0e00f1bb0364bb3" + integrity sha512-V3k1O5cbfZIRa0VVbQ81Ekq/7w42CK1SuiB9U1oPMTxv270D9qUn7rHb3sZoqMkIJFfB1NZxaH7NRDlk+ToDsg== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^16.8.24": version "16.8.24" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.24.tgz#8d1ea1fcbfa214220da3d3c04e506f1077b0deac"