diff --git a/packages/desktop-client/src/components/common/Menu.tsx b/packages/desktop-client/src/components/common/Menu.tsx index 2f0d002f71c..523023f087e 100644 --- a/packages/desktop-client/src/components/common/Menu.tsx +++ b/packages/desktop-client/src/components/common/Menu.tsx @@ -34,21 +34,21 @@ type MenuItem = { style?: CSSProperties; }; -type MenuProps = { +type MenuProps = { header?: ReactNode; footer?: ReactNode; - items: Array; - onMenuSelect: (itemName: T['name']) => void; + items: Array; + onMenuSelect: (itemName: MenuItem['name']) => void; style?: CSSProperties; }; -export default function Menu({ +export default function Menu({ header, footer, items: allItems, onMenuSelect, style, -}: MenuProps) { +}: MenuProps) { const elRef = useRef(null); const items = allItems.filter(x => x); const [hoveredIndex, setHoveredIndex] = useState(null); diff --git a/packages/desktop-client/src/components/payees/PayeeTable.tsx b/packages/desktop-client/src/components/payees/PayeeTable.tsx index c7504d76cfe..b376752ae30 100644 --- a/packages/desktop-client/src/components/payees/PayeeTable.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTable.tsx @@ -1,8 +1,10 @@ import { + forwardRef, useCallback, useLayoutEffect, useState, type ComponentProps, + type ComponentRef, } from 'react'; import { type PayeeEntity } from 'loot-core/src/types/models'; @@ -18,7 +20,6 @@ import PayeeTableRow from './PayeeTableRow'; type PayeeWithId = PayeeEntity & Required>; type PayeeTableProps = { - tableRef: ComponentProps>['tableRef']; payees: PayeeWithId[]; ruleCounts: Map; navigator: TableNavigator; @@ -27,56 +28,56 @@ type PayeeTableProps = { 'onUpdate' | 'onViewRules' | 'onCreateRule' >; -const PayeeTable = ({ - tableRef, - payees, - ruleCounts, - navigator, - onUpdate, - onViewRules, - onCreateRule, -}: PayeeTableProps) => { - const [hovered, setHovered] = useState(null); - const selectedItems = useSelectedItems(); +const PayeeTable = forwardRef< + ComponentRef>, + PayeeTableProps +>( + ( + { payees, ruleCounts, navigator, onUpdate, onViewRules, onCreateRule }, + ref, + ) => { + const [hovered, setHovered] = useState(null); + const selectedItems = useSelectedItems(); - useLayoutEffect(() => { - const firstSelected = [...selectedItems][0] as string; - if (typeof tableRef !== 'function') { - tableRef.current.scrollTo(firstSelected, 'center'); - } - navigator.onEdit(firstSelected, 'select'); - }, []); + useLayoutEffect(() => { + const firstSelected = [...selectedItems][0] as string; + if (typeof ref !== 'function') { + ref.current.scrollTo(firstSelected, 'center'); + } + navigator.onEdit(firstSelected, 'select'); + }, []); - const onHover = useCallback((id: string) => { - setHovered(id); - }, []); + const onHover = useCallback(id => { + setHovered(id); + }, []); - return ( - setHovered(null)}> - { - return ( - - ); - }} - /> - - ); -}; + return ( + setHovered(null)}> + + ref={ref} + items={payees} + navigator={navigator} + renderItem={({ item, editing, focusedField, onEdit }) => { + return ( + + ); + }} + /> + + ); + }, +); export default PayeeTable; diff --git a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx index e7c2574936c..149cb6fd188 100644 --- a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx +++ b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx @@ -118,7 +118,7 @@ function OverflowMenu({ onClose={() => setOpen(false)} > { + onMenuSelect={(name: ScheduleItemAction) => { onAction(name, schedule.id); setOpen(false); }} diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx index ddf274b1b50..92e2e833a47 100644 --- a/packages/desktop-client/src/components/table.tsx +++ b/packages/desktop-client/src/components/table.tsx @@ -11,6 +11,7 @@ import React, { type ReactNode, type KeyboardEvent, type UIEvent, + type ReactElement, type Ref, } from 'react'; import { useStore } from 'react-redux'; @@ -846,18 +847,17 @@ type TableHandleRef = { type TableWithNavigatorProps = TableProps & { fields; }; -export const TableWithNavigator = ({ - fields, - ...props -}: TableWithNavigatorProps) => { +export const TableWithNavigator = forwardRef< + TableHandleRef, + TableWithNavigatorProps +>(({ fields, ...props }, ref) => { const navigator = useTableNavigator(props.items, fields); return
; -}; +}); type TableItem = { id: number | string }; type TableProps = { - tableRef?: Ref>; items: T[]; count?: number; headers?: ReactNode | TableHeaderProps['headers']; @@ -886,276 +886,282 @@ type TableProps = { saveScrollWidth?: (parent, child) => void; }; -export const Table = ({ - tableRef, - items, - count, - headers, - contentHeader, - loading, - rowHeight = ROW_HEIGHT, - backgroundColor = theme.tableHeaderBackground, - renderItem, - renderEmpty, - getItemKey, - loadMore, - style, - navigator, - onScroll, - version = 'v1', - allowPopupsEscape, - isSelected, - saveScrollWidth, - ...props -}: TableProps) => { - if (!navigator) { - navigator = { - onEdit: () => {}, - editingId: null, - focusedField: null, - getNavigatorProps: props => props, - }; - } +export const Table: ( + props: TableProps & { ref?: Ref> }, +) => ReactElement = forwardRef( + ( + { + items, + count, + headers, + contentHeader, + loading, + rowHeight = ROW_HEIGHT, + backgroundColor = theme.tableHeaderBackground, + renderItem, + renderEmpty, + getItemKey, + loadMore, + style, + navigator, + onScroll, + version = 'v1', + allowPopupsEscape, + isSelected, + saveScrollWidth, + ...props + }, + ref, + ) => { + if (!navigator) { + navigator = { + onEdit: () => {}, + editingId: null, + focusedField: null, + getNavigatorProps: props => props, + }; + } - const { onEdit, editingId, focusedField, getNavigatorProps } = navigator; - const list = useRef(null); - const listContainer = useRef(null); - const scrollContainer = useRef(null); - const initialScrollTo = useRef(null); - const listInitialized = useRef(false); - - useImperativeHandle(tableRef, () => ({ - scrollTo: (id, alignment = 'smart') => { - const index = items.findIndex(item => item.id === id); - if (index !== -1) { - if (!list.current) { - // If the table hasn't been laid out yet, we need to wait for - // that to happen before we can scroll to something - initialScrollTo.current = index; - } else { - list.current.scrollToItem(index, alignment); + const { onEdit, editingId, focusedField, getNavigatorProps } = navigator; + const list = useRef(null); + const listContainer = useRef(null); + const scrollContainer = useRef(null); + const initialScrollTo = useRef(null); + const listInitialized = useRef(false); + + useImperativeHandle(ref, () => ({ + scrollTo: (id, alignment = 'smart') => { + const index = items.findIndex(item => item.id === id); + if (index !== -1) { + if (!list.current) { + // If the table hasn't been laid out yet, we need to wait for + // that to happen before we can scroll to something + initialScrollTo.current = index; + } else { + list.current.scrollToItem(index, alignment); + } } - } - }, + }, - scrollToTop: () => { - list.current?.scrollTo(0); - }, + scrollToTop: () => { + list.current?.scrollTo(0); + }, - getScrolledItem: () => { - if (scrollContainer.current) { - const offset = scrollContainer.current.scrollTop; - const index = list.current.getStartIndexForOffset(offset); - return items[index].id; - } - return 0; - }, + getScrolledItem: () => { + if (scrollContainer.current) { + const offset = scrollContainer.current.scrollTop; + const index = list.current.getStartIndexForOffset(offset); + return items[index].id; + } + return 0; + }, - setRowAnimation: flag => { - list.current?.setRowAnimation(flag); - }, + setRowAnimation: flag => { + list.current?.setRowAnimation(flag); + }, - edit(id, field, shouldScroll) { - onEdit(id, field); + edit(id, field, shouldScroll) { + onEdit(id, field); - if (id && shouldScroll) { - // @ts-expect-error this should not be possible - ref.scrollTo(id); - } - }, + if (id && shouldScroll) { + // @ts-expect-error this should not be possible + ref.scrollTo(id); + } + }, - anchor() { - list.current?.anchor(); - }, + anchor() { + list.current?.anchor(); + }, - unanchor() { - list.current?.unanchor(); - }, + unanchor() { + list.current?.unanchor(); + }, - isAnchored() { - return list.current && list.current.isAnchored(); - }, - })); - - useLayoutEffect(() => { - // We wait for the list to mount because AutoSizer needs to run - // before it's mounted - if (!listInitialized.current && listContainer.current) { - // Animation is on by default - list.current?.setRowAnimation(true); - listInitialized.current = true; - } + isAnchored() { + return list.current && list.current.isAnchored(); + }, + })); + + useLayoutEffect(() => { + // We wait for the list to mount because AutoSizer needs to run + // before it's mounted + if (!listInitialized.current && listContainer.current) { + // Animation is on by default + list.current?.setRowAnimation(true); + listInitialized.current = true; + } + + if (scrollContainer.current && saveScrollWidth) { + saveScrollWidth( + scrollContainer.current.offsetParent + ? scrollContainer.current.offsetParent.clientWidth + : 0, + scrollContainer.current ? scrollContainer.current.clientWidth : 0, + ); + } + }); - if (scrollContainer.current && saveScrollWidth) { - saveScrollWidth( - scrollContainer.current.offsetParent - ? scrollContainer.current.offsetParent.clientWidth - : 0, - scrollContainer.current ? scrollContainer.current.clientWidth : 0, + function renderRow({ index, style, key }) { + const item = items[index]; + const editing = editingId === item.id; + const selected = isSelected && isSelected(item.id); + + const row = renderItem({ + item, + editing, + focusedField: editing && focusedField, + onEdit, + index, + position: style.top, + }); + + // TODO: Need to also apply zIndex if item is selected + // * Port over ListAnimation to Table + // * Move highlighted functionality into here + return ( + + {row} + ); } - }); - - function renderRow({ index, style, key }) { - const item = items[index]; - const editing = editingId === item.id; - const selected = isSelected && isSelected(item.id); - const row = renderItem({ - item, - editing, - focusedField: editing && focusedField, - onEdit, - index, - position: style.top, - }); + function getScrollOffset(height, index) { + return ( + index * (rowHeight - 1) + + (rowHeight - 1) / 2 - + height / 2 + + (rowHeight - 1) * 2 + ); + } - // TODO: Need to also apply zIndex if item is selected - // * Port over ListAnimation to Table - // * Move highlighted functionality into here - return ( - - {row} - - ); - } + function onItemsRendered({ overscanStartIndex, overscanStopIndex }) { + if (loadMore && overscanStopIndex > items.length - 100) { + loadMore(); + } + } - function getScrollOffset(height, index) { - return ( - index * (rowHeight - 1) + - (rowHeight - 1) / 2 - - height / 2 + - (rowHeight - 1) * 2 - ); - } + function getEmptyContent(empty) { + if (empty == null) { + return null; + } else if (typeof empty === 'function') { + return empty(); + } - function onItemsRendered({ overscanStartIndex, overscanStopIndex }) { - if (loadMore && overscanStopIndex > items.length - 100) { - loadMore(); + return ( + + {empty} + + ); } - } - function getEmptyContent(empty) { - if (empty == null) { - return null; - } else if (typeof empty === 'function') { - return empty(); + if (loading) { + return ( + + + + ); } - return ( - - {empty} - - ); - } + const isEmpty = (count || items.length) === 0; - if (loading) { return ( - - - ); - } - - const isEmpty = (count || items.length) === 0; - - return ( - - {headers && ( - - )} - - {isEmpty ? ( - getEmptyContent(renderEmpty) - ) : ( - - {({ width, height }) => { - if (width === 0 || height === 0) { - return null; - } - - return ( - - - items[index].id) - } - indexForKey={key => - items.findIndex(item => item.id === key) - } - initialScrollOffset={ - initialScrollTo.current - ? getScrollOffset(height, initialScrollTo.current) - : 0 - } - overscanCount={5} - onItemsRendered={onItemsRendered} - onScroll={onScroll} - /> - - - ); - }} - + {headers && ( + )} + + {isEmpty ? ( + getEmptyContent(renderEmpty) + ) : ( + + {({ width, height }) => { + if (width === 0 || height === 0) { + return null; + } + + return ( + + + items[index].id) + } + indexForKey={key => + items.findIndex(item => item.id === key) + } + initialScrollOffset={ + initialScrollTo.current + ? getScrollOffset(height, initialScrollTo.current) + : 0 + } + overscanCount={5} + onItemsRendered={onItemsRendered} + onScroll={onScroll} + /> + + + ); + }} + + )} + - - ); -}; + ); + }, +); export type TableNavigator = { onEdit: (id: T['id'], field?: string) => void; diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 5c04a9289e7..a1009c1aaca 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -1722,7 +1722,7 @@ function TransactionTableInner({ >
>({ value, timestamp: jsc.integer(1000, 10000).smap( x => { - let clientId: string; + let clientId; switch (jsc.random(0, 1)) { case 0: clientId = clientId1; diff --git a/upcoming-release-notes/2071.md b/upcoming-release-notes/2071.md deleted file mode 100644 index e443cc99adc..00000000000 --- a/upcoming-release-notes/2071.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MatissJanis] ---- - -Fixing TypeScript issues when enabling `strictFunctionTypes` (pt.4).