refactor(album-track-list): I.2 — split AlbumTrackList.tsx 662 → 187 LOC across 7 files (#672)

* refactor(album-track-list): extract helpers + types

Pull formatDuration / codecLabel, the COLUMNS / CENTERED_COLS / SORTABLE_COLS
tables, ColKey / SortKey types, and the isSortable type guard into
utils/albumTrackListHelpers.ts. SortKey is re-exported from
AlbumTrackList.tsx so existing imports stay valid.

AlbumTrackList.tsx: 662 → 633 LOC.

* refactor(album-track-list): extract TrackRow subcomponent

Move the memoised tracklist row (~220 LOC including renderCell switch and
mouse handlers) into components/albumTrackList/TrackRow.tsx. It still
subscribes to its own selection + preview state via primitive selectors,
so per-row re-render scope is unchanged.

AlbumTrackList.tsx: 633 → 404 LOC.

* refactor(album-track-list): extract AlbumTrackListMobile subcomponent

Move the narrow-viewport branch (compact tracklist with disc separators
and no column grid) into components/albumTrackList/AlbumTrackListMobile.tsx.

AlbumTrackList.tsx: 404 → 376 LOC.

* refactor(album-track-list): extract TracklistColumnPicker subcomponent

The column visibility dropdown lives outside .tracklist to avoid the
overflow box clipping its menu — pull the wrapper + button + popover into
components/albumTrackList/TracklistColumnPicker.tsx.

AlbumTrackList.tsx: 376 → 347 LOC.

* refactor(album-track-list): extract TracklistHeaderRow subcomponent

The fixed header (sortable + resizable per-column with the bulk-select
toggle on the num cell) moves into
components/albumTrackList/TracklistHeaderRow.tsx, taking 85+ LOC of cell
rendering with it.

AlbumTrackList.tsx: 347 → 254 LOC.

* refactor(album-track-list): extract useAlbumTrackListSelection hook

Pull bulk-selection state (selectedIds-size subscription, shift-range
toggle, click-outside-clear, song-list-change clear) and the drag-start
dispatcher (single vs multi-song drag) into
hooks/useAlbumTrackListSelection.ts.

AlbumTrackList.tsx: 254 → 187 LOC.
This commit is contained in:
Frank Stellmacher
2026-05-14 00:01:36 +02:00
committed by GitHub
parent b591a1cb5f
commit f14c8f21e6
7 changed files with 726 additions and 522 deletions
@@ -0,0 +1,70 @@
import React from 'react';
import { Check, ChevronDown, RotateCcw } from 'lucide-react';
import type { TFunction } from 'i18next';
import { COLUMNS } from '../../utils/albumTrackListHelpers';
interface Props {
pickerRef: React.RefObject<HTMLDivElement | null>;
pickerOpen: boolean;
setPickerOpen: (updater: (v: boolean) => boolean) => void;
colVisible: Set<string>;
toggleColumn: (key: string) => void;
resetColumns: () => void;
t: TFunction;
}
/**
* The column visibility dropdown that sits outside `.tracklist` so the
* popover menu can grow without being clipped by the tracklist's overflow
* box. Lists every non-required column and offers a reset-to-defaults
* button.
*/
export function TracklistColumnPicker({
pickerRef,
pickerOpen,
setPickerOpen,
colVisible,
toggleColumn,
resetColumns,
t,
}: Props) {
return (
<div className="tracklist-col-picker-wrapper" ref={pickerRef}>
<div className="tracklist-col-picker">
<button
className="tracklist-col-picker-btn"
onClick={e => { e.stopPropagation(); setPickerOpen(v => !v); }}
data-tooltip={t('albumDetail.columns')}
>
<ChevronDown size={14} />
</button>
{pickerOpen && (
<div className="tracklist-col-picker-menu">
<div className="tracklist-col-picker-label">{t('albumDetail.columns')}</div>
{COLUMNS.filter(c => !c.required).map(c => {
const label = c.i18nKey ? t(`albumDetail.${c.i18nKey as string}`) : c.key;
const isOn = colVisible.has(c.key);
return (
<button
key={c.key}
className={`tracklist-col-picker-item${isOn ? ' active' : ''}`}
onClick={() => toggleColumn(c.key)}
>
<span className="tracklist-col-picker-check">
{isOn && <Check size={13} />}
</span>
{label}
</button>
);
})}
<div className="tracklist-col-picker-divider" />
<button className="tracklist-col-picker-reset" onClick={resetColumns}>
<RotateCcw size={13} />
{t('albumDetail.resetColumns')}
</button>
</div>
)}
</div>
</div>
);
}