-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
1 parent
8c3d050
commit 1693e01
Showing
10 changed files
with
420 additions
and
26 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
packages/web/src/components/collections-table/CollectionsTable.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
266
packages/web/src/components/collections-table/CollectionsTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
/> | ||
) | ||
} |
64 changes: 64 additions & 0 deletions
64
packages/web/src/components/collections-table/CollectionsTableOverflowMenuButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
Oops, something went wrong.