Skip to content
Merged
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
@@ -1,5 +1,21 @@
.selected,
.selected td {
background-color: var(--harmony-bg-surface-2) !important;
/* Light purple tint derived from the Harmony secondary token. Mixed with
* white so the row stays readable in both light and dark themes — there's
* no Harmony secondary-50 primitive to reach for directly. */
background-color: color-mix(
in srgb,
var(--harmony-secondary) 8%,
var(--harmony-white)
) !important;
box-shadow: inset 3px 0 0 0 var(--harmony-secondary, var(--harmony-accent));
}

.selected:hover:not(.skeletonRow),
.selected:hover:not(.skeletonRow) > td {
background-color: color-mix(
in srgb,
var(--harmony-secondary) 12%,
var(--harmony-white)
) !important;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { ComponentProps, useCallback, useEffect, useRef } from 'react'
import {
ComponentProps,
MouseEvent,
useCallback,
useEffect,
useMemo,
useRef
} from 'react'

import { ID } from '@audius/common/models'
import { Checkbox, Flex } from '@audius/harmony'

import { TracksTable } from 'components/tracks-table'

Expand All @@ -22,12 +30,83 @@ type EditAwareTracksTableProps = ComponentProps<typeof TracksTable> & {
* behavior is identical to the underlying TracksTable.
*/
export const EditAwareTracksTable = (props: EditAwareTracksTableProps) => {
const { collectionId, onClickRow, ...rest } = props
const { collectionId, onClickRow, data, ...rest } = props
const editMode = usePlaylistEditMode()
const selection = useTrackSelection()
const isEditingThis =
editMode.isEditMode && editMode.collectionId === collectionId

const selectableTrackIds = useMemo(() => {
if (!isEditingThis) return [] as ID[]
const ids: ID[] = []
for (const t of data) {
if (typeof t.track_id === 'number') ids.push(t.track_id)
}
return ids
}, [data, isEditingThis])

const selectableCount = selectableTrackIds.length
const selectedCount = selection.count
const allSelected = selectableCount > 0 && selectedCount >= selectableCount
const someSelected = selectedCount > 0 && !allSelected

const handleHeaderCheckboxClick = useCallback(
(e: MouseEvent<HTMLInputElement>) => {
// Stop the click from bubbling to the column header's sort handler.
e.stopPropagation()
if (allSelected) {
selection.clear()
} else {
selection.selectAll(selectableTrackIds)
}
},
[allSelected, selectableTrackIds, selection]
)

const trackNameHeader = useMemo(() => {
if (!isEditingThis) return undefined
return (
<Flex alignItems='center' gap='m'>
<Checkbox
aria-label={allSelected ? 'Deselect all tracks' : 'Select all tracks'}
checked={selectedCount > 0}
indeterminate={someSelected}
onClick={handleHeaderCheckboxClick}
onChange={() => {}}
/>
Track
</Flex>
)
}, [
allSelected,
handleHeaderCheckboxClick,
isEditingThis,
selectedCount,
someSelected
])

const renderTrackPrefix = useCallback(
(track: TrackLike, index: number) => {
const id = track.track_id
if (typeof id !== 'number') return null
const checked = selection.isSelected(id)
return (
<Checkbox
aria-label={checked ? 'Deselect track' : 'Select track'}
checked={checked}
onClick={(e) => {
// Avoid double-toggling: the row's onClick handler would also
// call selection.toggle if the click bubbled up.
e.stopPropagation()
selection.toggle(id, index)
}}
onChange={() => {}}
/>
)
},
[selection]
)

// Capture shift modifier state from keyboard so we can extend the selection
// even though TracksTable's onClickRow does not pass the MouseEvent.
const shiftRef = useRef(false)
Expand Down Expand Up @@ -83,8 +162,11 @@ export const EditAwareTracksTable = (props: EditAwareTracksTableProps) => {
return (
<TracksTable
{...rest}
data={data}
onClickRow={handleClickRow}
rowClassNameAddition={rowClassNameAddition}
trackNameHeader={trackNameHeader}
renderTrackPrefix={isEditingThis ? renderTrackPrefix : undefined}
/>
)
}
25 changes: 24 additions & 1 deletion packages/web/src/components/tracks-table/TracksTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ type TracksTableProps = {
showArtistInTrackNameColumn?: boolean
onClickRow?: (track: any, index: number) => void
trackActionsHeader?: ReactNode
/**
* Optional content for the Track column header, replacing the default
* 'Track' label. Used by selection mode to render a select-all checkbox.
*/
trackNameHeader?: ReactNode
/**
* Optional content rendered at the leading edge of the Track-name cell
* (before the inline artwork). Used by selection mode to render the
* per-row selection checkbox.
*/
renderTrackPrefix?: (track: any, rowIndex: number) => ReactNode
/**
* Optional additional className applied per row. The result is appended
* to the table's own per-row className. Use this for things like a
Expand Down Expand Up @@ -220,6 +231,8 @@ export const TracksTable = ({
data,
activeIndex,
trackActionsHeader,
trackNameHeader,
renderTrackPrefix,
rowClassNameAddition,
...tableProps
}: TracksTableProps) => {
Expand All @@ -245,6 +258,8 @@ export const TracksTable = ({
onClickRepostRef.current = onClickRepost
const onClickRemoveRef = useRef(onClickRemove)
onClickRemoveRef.current = onClickRemove
const renderTrackPrefixRef = useRef(renderTrackPrefix)
renderTrackPrefixRef.current = renderTrackPrefix
const { onOpen: openPremiumContentPurchaseModal } =
usePremiumContentPurchaseModal()
const [, setGatedModalVisibility] = useModalState('LockedContent')
Expand Down Expand Up @@ -364,8 +379,11 @@ export const TracksTable = ({
)
) : null

const prefix = renderTrackPrefixRef.current?.(track, index)

return (
<Flex className={styles.trackInfoContainer}>
{prefix}
{showArtistInTrackNameColumn && track.track_id ? (
<MiniTrackArtwork
trackId={track.track_id}
Expand Down Expand Up @@ -980,14 +998,18 @@ export const TracksTable = ({
},
trackName: {
id: 'trackName',
Header: 'Track',
Header: trackNameHeader ?? 'Track',
accessor: 'title',
Cell: renderTrackNameCell,
minWidth: trackNameColumnWidth,
width: trackNameColumnWidth,
maxWidth: Number.MAX_SAFE_INTEGER,
sortTitle: 'Track Name',
sorter: alphaSorter('title'),
// When a custom header is supplied (e.g. select-all checkbox),
// suppress column sorting so the header's controls receive clicks
// instead of toggling sort order.
disableSortBy: Boolean(trackNameHeader),
align: 'left'
},
savedDate: {
Expand Down Expand Up @@ -1024,6 +1046,7 @@ export const TracksTable = ({
renderCommentsCell,
renderTrackActions,
trackActionsHeader,
trackNameHeader,
renderOverflowMenuCell,
renderLengthCell,
isVirtualized,
Expand Down
Loading