Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
useCallback,
useEffect,
useMemo,
Ref
Ref,
lazy,
Suspense
} from 'react'

import { CSSObject, useTheme } from '@emotion/react'
Expand All @@ -20,9 +22,27 @@ import { Text } from '~harmony/components/text/Text'
import { useControlled } from '~harmony/hooks/useControlled'
import { IconCaretDown, IconCloseAlt, IconSearch } from '~harmony/icons'

import { OptionsList, VirtualizedOptionsList } from './FilterButtonOptionsList'
import { OptionsList } from './FilterButtonOptionsList'
import { FilterButtonProps } from './types'

/**
* `react-virtualized` is ~638 KB of source and is only needed when a consumer
* opts in with the `virtualized` prop (3 call sites across the web app). It was
* previously a static import, so every surface using FilterButton — and
* FilterButton is reachable from the eager app shell via PaymentMethod — paid
* for it. Loaded on demand instead; the menu is only rendered while open, so
* the import starts when a virtualized filter menu is actually opened.
*/
const VirtualizedOptionsList = lazy(() =>
import('./FilterButtonVirtualizedOptionsList').then((m) => ({
// `lazy()` erases the component's `<Value extends string>` generic, so it
// resolves to the `string` instantiation. Re-declaring the generic on the
// lazy const keeps call sites type-safe; the generic is erased at runtime
// either way, so this only restores what lazy() dropped.
default: m.VirtualizedOptionsList as typeof m.VirtualizedOptionsList
}))
) as typeof import('./FilterButtonVirtualizedOptionsList').VirtualizedOptionsList

const messages = {
noMatches: 'No matches'
}
Expand Down Expand Up @@ -228,13 +248,15 @@ export const FilterButton = forwardRef(function FilterButton<

const optionElements = filteredOptions ? (
virtualized ? (
<VirtualizedOptionsList
options={filteredOptions}
optionRefs={optionRefs}
onChange={handleOptionSelected}
height={menuProps?.maxHeight}
width={menuProps?.width}
/>
<Suspense fallback={null}>
<VirtualizedOptionsList
options={filteredOptions}
optionRefs={optionRefs}
onChange={handleOptionSelected}
height={menuProps?.maxHeight}
width={menuProps?.width}
/>
</Suspense>
) : (
<OptionsList
options={filteredOptions}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { useState, useCallback, useEffect, RefObject } from 'react'

import { CSSObject } from '@emotion/react'
import { List, ListRowProps } from 'react-virtualized'
import { RefObject } from 'react'

import { MenuItem } from '~harmony/components/internal/MenuItem'
import { OptionKeyHandler } from '~harmony/components/internal/OptionKeyHandler'
Expand All @@ -16,61 +13,6 @@ type OptionsListProps<Value extends string> = {
onChange: (value: Value) => void
}

type VirtualizedOptionsListProps<Value extends string> = {
options: FilterButtonOptionType<Value>[]
optionRefs: RefObject<HTMLButtonElement[]>
onChange: (value: Value) => void
height: CSSObject['height']
width: CSSObject['width']
}

export const VirtualizedOptionsList = <Value extends string>({
options,
optionRefs,
onChange,
height = 100,
width = 100
}: VirtualizedOptionsListProps<Value>) => {
const [rowHeight, setRowHeight] = useState(50)
useEffect(() => {
if (optionRefs.current?.[0]) {
setRowHeight(optionRefs.current?.[0].offsetHeight)
}
}, [optionRefs])

const renderItem = useCallback(
({ index, style }: ListRowProps) => {
const option = options[index]

return (
<MenuItem
style={style}
variant='option'
ref={(el) => {
if (optionRefs && optionRefs.current && el) {
optionRefs.current[index] = el
}
}}
key={option.value}
{...option}
onChange={onChange}
/>
)
},
[onChange, optionRefs, options]
)

return (
<List
width={Number(width)}
height={Number(height)}
rowCount={options.length}
rowHeight={rowHeight}
rowRenderer={renderItem}
/>
)
}

export const OptionsList = <Value extends string>({
options,
isOpen,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useState, useCallback, useEffect, RefObject } from 'react'

import { CSSObject } from '@emotion/react'
import { List, ListRowProps } from 'react-virtualized'

import { MenuItem } from '~harmony/components/internal/MenuItem'

import { FilterButtonOptionType } from './types'

type VirtualizedOptionsListProps<Value extends string> = {
options: FilterButtonOptionType<Value>[]
optionRefs: RefObject<HTMLButtonElement[]>
onChange: (value: Value) => void
height: CSSObject['height']
width: CSSObject['width']
}

export const VirtualizedOptionsList = <Value extends string>({
options,
optionRefs,
onChange,
height = 100,
width = 100
}: VirtualizedOptionsListProps<Value>) => {
const [rowHeight, setRowHeight] = useState(50)
useEffect(() => {
if (optionRefs.current?.[0]) {
setRowHeight(optionRefs.current?.[0].offsetHeight)
}
}, [optionRefs])

const renderItem = useCallback(
({ index, style }: ListRowProps) => {
const option = options[index]

return (
<MenuItem
style={style}
variant='option'
ref={(el) => {
if (optionRefs && optionRefs.current && el) {
optionRefs.current[index] = el
}
}}
key={option.value}
{...option}
onChange={onChange}
/>
)
},
[onChange, optionRefs, options]
)

return (
<List
width={Number(width)}
height={Number(height)}
rowCount={options.length}
rowHeight={rowHeight}
rowRenderer={renderItem}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,7 @@ const defaultColumns: AudioTransactionsTableColumn[] = [
'spacer2'
]

export const isChangePositive = (tx: TransactionDetails) => {
return (
tx.transactionType === TransactionType.PURCHASE ||
tx.method === TransactionMethod.RECEIVE
)
}
export { isChangePositive } from './isChangePositive'

// Cell Render Functions
const renderTransactionTypeCell = (cellInfo: TransactionCell) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
TransactionDetails,
TransactionMethod,
TransactionType
} from '@audius/common/store'

/**
* Whether a transaction increases the user's balance.
*
* Lives in its own module rather than in `AudioTransactionsTable` so that
* consumers needing only this predicate don't pull in the table — which imports
* `components/table` and, through it, `react-virtualized` (~638 KB of source).
* `TransactionDetailsContent` is reachable from the eagerly-registered
* TransactionDetails modal, so that edge landed the whole table in the entry chunk.
*/
export const isChangePositive = (tx: TransactionDetails) => {
return (
tx.transactionType === TransactionType.PURCHASE ||
tx.method === TransactionMethod.RECEIVE
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
import cn from 'classnames'

import { AudioTransactionIcon } from 'components/audio-transaction-icon'
import { isChangePositive } from 'components/audio-transactions-table/AudioTransactionsTable'
import { isChangePositive } from 'components/audio-transactions-table/isChangePositive'
import LoadingSpinner from 'components/loading-spinner/LoadingSpinner'
import { getChallengeConfig } from 'pages/rewards-page/config'

Expand Down
Loading