From 1a5d4461a386696b2916401ddc2a2e3062e87ec7 Mon Sep 17 00:00:00 2001 From: David Newell Date: Wed, 19 Jun 2024 17:59:47 +0100 Subject: [PATCH] type the playlist properly --- .../components/Playlist/Playlist.stories.tsx | 6 +- .../src/lib/components/Playlist/Playlist.tsx | 75 +++++++++++-------- .../playlist/SessionRecordingsPlaylist.tsx | 6 +- 3 files changed, 51 insertions(+), 36 deletions(-) diff --git a/frontend/src/lib/components/Playlist/Playlist.stories.tsx b/frontend/src/lib/components/Playlist/Playlist.stories.tsx index ed90e0a8a1c45..2586abe0fb978 100644 --- a/frontend/src/lib/components/Playlist/Playlist.stories.tsx +++ b/frontend/src/lib/components/Playlist/Playlist.stories.tsx @@ -10,10 +10,10 @@ const meta: Meta = { } export default meta -const ListItem = ({ item }): JSX.Element =>
Object {item.id}
+const ListItem = ({ item }: { item: { id: number } }): JSX.Element =>
Object {item.id}
-const Template: StoryFn = (props: Partial) => { - const mainContent = ({ activeItem }): JSX.Element => ( +const Template: StoryFn = (props: Partial>) => { + const mainContent = ({ activeItem }: { activeItem: { id: number } }): JSX.Element => (
{activeItem ? `Object ${activeItem.id} selected` : 'Select an item from the list'}
diff --git a/frontend/src/lib/components/Playlist/Playlist.tsx b/frontend/src/lib/components/Playlist/Playlist.tsx index 2d4d9a48776b4..069f9b3eeaa52 100644 --- a/frontend/src/lib/components/Playlist/Playlist.tsx +++ b/frontend/src/lib/components/Playlist/Playlist.tsx @@ -14,11 +14,11 @@ import { Resizer } from '../Resizer/Resizer' const SCROLL_TRIGGER_OFFSET = 100 -export type PlaylistSection = { +export type PlaylistSection = { key: string title?: string - items: any[] - render: ({ item, isActive }: { item: any; isActive: any }) => JSX.Element + items: T[] + render: ({ item, isActive }: { item: T; isActive: boolean }) => JSX.Element footer?: JSX.Element } @@ -27,17 +27,17 @@ type PlaylistHeaderAction = Pick = { + sections: PlaylistSection[] listEmptyState: JSX.Element - content: ({ activeItem }: { activeItem: any }) => JSX.Element + content: ({ activeItem }: { activeItem: T }) => JSX.Element title?: string notebooksHref?: string embedded?: boolean loading?: boolean headerActions?: PlaylistHeaderAction[] onScrollListEdge?: (edge: 'top' | 'bottom') => void - onSelect?: (item: any) => void + onSelect?: (item: T) => void 'data-attr'?: string activeItemId?: string } @@ -46,7 +46,12 @@ const CounterBadge = ({ children }: { children: React.ReactNode }): JSX.Element {children} ) -export function Playlist({ +export function Playlist< + T extends { + id: string | number // accepts any object as long as it conforms to the interface of having an `id` + [key: string]: any + } +>({ title, notebooksHref, loading, @@ -59,8 +64,8 @@ export function Playlist({ listEmptyState, onSelect, 'data-attr': dataAttr, -}: PlaylistProps): JSX.Element { - const [controlledActiveItemId, setControlledActiveItemId] = useState(null) +}: PlaylistProps): JSX.Element { + const [controlledActiveItemId, setControlledActiveItemId] = useState(null) const [listCollapsed, setListCollapsed] = useState(false) const playlistListRef = useRef(null) const { ref: playlistRef, size } = useResizeBreakpoints({ @@ -68,14 +73,14 @@ export function Playlist({ 750: 'medium', }) - const onChangeActiveItem = (item: any): void => { + const onChangeActiveItem = (item: T): void => { setControlledActiveItemId(item.id) - onSelect?.(item.id) + onSelect?.(item) } const activeItemId = propsActiveItemId === undefined ? controlledActiveItemId : propsActiveItemId - const activeItem = sections.flatMap((s) => s.items).find((i) => i.id === activeItemId) + const activeItem = sections.flatMap((s) => s.items).find((i) => i.id === activeItemId) || null return (
setListCollapsed(!listCollapsed)} />
-
{content({ activeItem })}
+
{activeItem && content({ activeItem })}
) } @@ -123,7 +128,12 @@ const CollapsedList = ({ onClickOpen }: { onClickOpen: () => void }): JSX.Elemen ) -const List = ({ +function List< + T extends { + id: string | number + [key: string]: any + } +>({ title, notebooksHref, onClickCollapse, @@ -135,17 +145,17 @@ const List = ({ loading, emptyState, }: { - title: PlaylistProps['title'] - notebooksHref: PlaylistProps['notebooksHref'] + title: PlaylistProps['title'] + notebooksHref: PlaylistProps['notebooksHref'] onClickCollapse: () => void - activeItemId: string | null - setActiveItemId: (id: string) => void - headerActions: PlaylistProps['headerActions'] - sections: PlaylistProps['sections'] - onScrollListEdge: PlaylistProps['onScrollListEdge'] - loading: PlaylistProps['loading'] - emptyState: PlaylistProps['listEmptyState'] -}): JSX.Element => { + activeItemId: T['id'] | null + setActiveItemId: (item: T) => void + headerActions: PlaylistProps['headerActions'] + sections: PlaylistProps['sections'] + onScrollListEdge: PlaylistProps['onScrollListEdge'] + loading: PlaylistProps['loading'] + emptyState: PlaylistProps['listEmptyState'] +}): JSX.Element { const [activeHeaderActionKey, setActiveHeaderActionKey] = useState(null) const lastScrollPositionRef = useRef(0) const contentRef = useRef(null) @@ -255,16 +265,21 @@ const List = ({ ) } -const ListSection = ({ +export function ListSection< + T extends { + id: string | number + [key: string]: any + } +>({ items, render, footer, onClick, activeItemId, -}: PlaylistSection & { - onClick: (item: any) => void - activeItemId: string | null -}): JSX.Element => { +}: PlaylistSection & { + onClick: (item: T) => void + activeItemId: T['id'] | null +}): JSX.Element { return ( <> {items.length && diff --git a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx index c591fa655abc0..589907918f20b 100644 --- a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx +++ b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx @@ -61,7 +61,7 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr const notebookNode = useNotebookNode() - const sections: PlaylistSection[] = [] + const sections: PlaylistSection[] = [] const headerActions = [] const onSummarizeClick = (recording: SessionRecordingType): void => { @@ -127,7 +127,7 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr
{sessionRecordingsResponseLoading ? ( <> - Loading + Loading older recordings ) : hasNext ? ( maybeLoadSessionRecordings('older')}>Load more @@ -158,7 +158,7 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr } }} listEmptyState={} - onSelect={setSelectedRecordingId} + onSelect={(item) => setSelectedRecordingId(item.id)} activeItemId={activeSessionRecordingId} content={({ activeItem }) => !activeItem ? (