Skip to content

Commit

Permalink
[PAY-2711] CollectionsTable component (#8147)
Browse files Browse the repository at this point in the history
  • Loading branch information
dharit-tan authored Apr 22, 2024
1 parent 8c3d050 commit 1693e01
Show file tree
Hide file tree
Showing 10 changed files with 420 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.tableRow:hover .tablePlayButton,
.tableRow:hover .trackActionsContainer .tableActionButton {
opacity: 1;
}

.tableRow:hover .hiddenIcon {
opacity: 0;
pointer-events: none;
}

.tableRow.disabled {
cursor: default;
color: var(--harmony-n-400);
background-color: var(--harmony-n-50);
}

.tableRow.disabled * {
color: var(--harmony-n-400);
background-color: var(--harmony-n-50);
}

.tableRow.disabled:hover {
box-shadow: none;
background-color: var(--harmony-n-50);
}

.tableRow.disabled:hover .tablePlayButton {
opacity: 0;
pointer-events: none;
}

.tableRow.disabled:hover .hiddenIcon {
opacity: 1;
}

.tableRow.lockedRow {
background-color: var(--harmony-white);
}
266 changes: 266 additions & 0 deletions packages/web/src/components/collections-table/CollectionsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import { MouseEvent, useCallback, useMemo, useRef } from 'react'

import {
CollectionMetadata,
UserCollectionMetadata
} from '@audius/common/models'
import { formatCount } from '@audius/common/utils'
import { Flex } from '@audius/harmony'
import cn from 'classnames'
import moment from 'moment'
import { Cell, Row } from 'react-table'

import { TextLink } from 'components/link'
import { Table, alphaSorter, dateSorter, numericSorter } from 'components/table'

import styles from './CollectionsTable.module.css'
import { CollectionsTableOverflowMenuButton } from './CollectionsTableOverflowMenuButton'

type RowInfo = UserCollectionMetadata & {
name: string
artist: string
date: string
dateSaved: string
dateAdded: string
}

type CollectionCell = Cell<RowInfo>

type CollectionRow = Row<RowInfo>

export type CollectionsTableColumn =
| 'name'
| 'overflowMenu'
| 'releaseDate'
| 'reposts'
| 'saves'
| 'spacer'

type CollectionsTableProps = {
columns?: CollectionsTableColumn[]
data: any[]
isVirtualized?: boolean
loading?: boolean
defaultSorter?: (a: any, b: any) => number
fetchMore?: (offset: number, limit: number) => void
fetchBatchSize?: number
onClickRow?: (collection: CollectionMetadata, index: number) => void
scrollRef?: React.MutableRefObject<HTMLDivElement | undefined>
showMoreLimit?: number
totalRowCount?: number
tableHeaderClassName?: string
}

const defaultColumns: CollectionsTableColumn[] = [
'name',
'overflowMenu',
'releaseDate',
'reposts',
'saves',
'spacer'
]

/**
* Note: this table is not generalized the way TracksTable is. It is
* specifically tailored to the Artist Dashboard use case.
*/
export const CollectionsTable = ({
columns = defaultColumns,
data,
defaultSorter,
fetchBatchSize,
fetchMore,
isVirtualized = false,
loading = false,
onClickRow,
scrollRef,
showMoreLimit,
totalRowCount,
tableHeaderClassName
}: CollectionsTableProps) => {
// Cell Render Functions
const renderNameCell = useCallback((cellInfo: CollectionCell) => {
const collection = cellInfo.row.original
const deleted = collection.is_delete || !!collection.user?.is_deactivated

return (
<Flex
gap='xs'
w='100%'
css={{
overflow: 'hidden',
position: 'relative',
display: 'inline-flex'
}}
>
<TextLink
tag={deleted ? 'span' : undefined}
to={deleted ? '' : collection.permalink ?? ''}
textVariant='title'
size='s'
strength='weak'
css={{ display: 'block' }}
ellipses
>
{collection.playlist_name}
{deleted ? ` [Deleted By Artist]` : ''}
</TextLink>
</Flex>
)
}, [])

const renderRepostsCell = useCallback((cellInfo: CollectionCell) => {
const collection = cellInfo.row.original
return formatCount(collection.repost_count)
}, [])

const renderSavesCell = useCallback((cellInfo: CollectionCell) => {
const collection = cellInfo.row.original
return formatCount(collection.save_count)
}, [])

const renderReleaseDateCell = useCallback((cellInfo: CollectionCell) => {
const collection = cellInfo.row.original
let suffix = ''
if (
collection.release_date &&
moment(collection.release_date).isAfter(moment.now())
) {
suffix = ' (Scheduled)'
}
return (
moment(collection.release_date ?? collection.created_at).format(
'M/D/YY'
) + suffix
)
}, [])

const overflowMenuRef = useRef<HTMLDivElement>(null)
const renderOverflowMenuCell = useCallback((cellInfo: CollectionCell) => {
const collection = cellInfo.row.original
return (
<div ref={overflowMenuRef}>
<CollectionsTableOverflowMenuButton
collectionId={collection.playlist_id}
/>
</div>
)
}, [])

// Columns
const tableColumnMap = useMemo(
() => ({
name: {
id: 'name',
Header: 'Album Name',
accessor: 'title',
Cell: renderNameCell,
maxWidth: 300,
width: 120,
sortTitle: 'Album Name',
sorter: alphaSorter('title'),
align: 'left'
},
releaseDate: {
id: 'dateReleased',
Header: 'Released',
accessor: 'created_at',
Cell: renderReleaseDateCell,
maxWidth: 160,
sortTitle: 'Date Released',
sorter: dateSorter('created_at'),
align: 'right'
},
reposts: {
id: 'reposts',
Header: 'Reposts',
accessor: 'repost_count',
Cell: renderRepostsCell,
maxWidth: 160,
sortTitle: 'Reposts',
sorter: numericSorter('repost_count'),
align: 'right'
},
saves: {
id: 'saves',
Header: 'Favorites',
accessor: 'save_count',
Cell: renderSavesCell,
maxWidth: 160,
sortTitle: 'Favorites',
sorter: numericSorter('save_count'),
align: 'right'
},
overflowMenu: {
id: 'overflowMenu',
Cell: renderOverflowMenuCell,
minWidth: 64,
maxWidth: 64,
width: 64,
disableResizing: true,
disableSortBy: true
},
spacer: {
id: 'spacer',
maxWidth: 24,
minWidth: 24,
disableSortBy: true,
disableResizing: true
}
}),
[
renderNameCell,
renderReleaseDateCell,
renderRepostsCell,
renderSavesCell,
renderOverflowMenuCell
]
)

const tableColumns = useMemo(
() => columns.map((id) => tableColumnMap[id]),
[columns, tableColumnMap]
)

const handleClickRow = useCallback(
(
e: MouseEvent<HTMLTableRowElement>,
rowInfo: CollectionRow,
index: number
) => {
const collection = rowInfo.original
onClickRow?.(collection, index)
},
[onClickRow]
)

const getRowClassName = useCallback(
(rowIndex: number) => {
const collection = data[rowIndex]
const deleted = collection.is_delete || !!collection.user?.is_deactivated
return cn(styles.tableRow, {
[styles.disabled]: deleted
})
},
[data]
)

return (
<Table
columns={tableColumns}
data={data}
defaultSorter={defaultSorter}
fetchBatchSize={fetchBatchSize}
fetchMore={fetchMore}
getRowClassName={getRowClassName}
isTracksTable
isVirtualized={isVirtualized}
loading={loading}
onClickRow={handleClickRow}
scrollRef={scrollRef}
showMoreLimit={showMoreLimit}
totalRowCount={totalRowCount}
tableHeaderClassName={tableHeaderClassName}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useGetCurrentUserId } from '@audius/common/api'
import { Collection } from '@audius/common/models'
import { collectionPageSelectors, CommonState } from '@audius/common/store'
import { Flex, IconKebabHorizontal } from '@audius/harmony'
import { useSelector } from 'react-redux'

import { CollectionMenuProps } from 'components/menu/CollectionMenu'
import Menu from 'components/menu/Menu'

const { getCollection } = collectionPageSelectors

type OverflowMenuButtonProps = {
collectionId: number
}

export const CollectionsTableOverflowMenuButton = (
props: OverflowMenuButtonProps
) => {
const { collectionId } = props
const { data: currentUserId } = useGetCurrentUserId({})
const {
is_album: isAlbum,
playlist_owner_id: playlistOwnerId,
is_private: isPrivate,
permalink
} = (useSelector((state: CommonState) =>
getCollection(state, { id: collectionId })
) as Collection) ?? {}

const overflowMenu = {
type: isAlbum ? 'album' : 'playlist',
playlistId: collectionId,
includeEmbed: true,
includeVisitArtistPage: false,
includeShare: true,
includeEdit: true,
includeSave: false,
isPublic: !isPrivate,
isOwner: currentUserId === playlistOwnerId,
permalink
} as unknown as CollectionMenuProps

return (
<Flex
alignItems='center'
css={{ cursor: 'pointer', 'user-select': 'none' }}
>
<Menu menu={overflowMenu}>
{(ref, triggerPopup) => (
<Flex
onClick={(e) => {
e.stopPropagation()
triggerPopup()
}}
>
<Flex ref={ref}>
<IconKebabHorizontal color='subdued' />
</Flex>
</Flex>
)}
</Menu>
</Flex>
)
}
3 changes: 3 additions & 0 deletions packages/web/src/components/collections-table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { CollectionsTable } from './CollectionsTable'
export { CollectionsTableOverflowMenuButton } from './CollectionsTableOverflowMenuButton'
export type { CollectionsTableColumn } from './CollectionsTable'
Loading

0 comments on commit 1693e01

Please sign in to comment.