Skip to content

Commit

Permalink
type the playlist properly
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin committed Jun 19, 2024
1 parent 5471ecd commit 1a5d446
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 36 deletions.
6 changes: 3 additions & 3 deletions frontend/src/lib/components/Playlist/Playlist.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ const meta: Meta<typeof Playlist> = {
}
export default meta

const ListItem = ({ item }): JSX.Element => <div className="p-1">Object {item.id}</div>
const ListItem = ({ item }: { item: { id: number } }): JSX.Element => <div className="p-1">Object {item.id}</div>

const Template: StoryFn<typeof Playlist> = (props: Partial<PlaylistProps>) => {
const mainContent = ({ activeItem }): JSX.Element => (
const Template: StoryFn<typeof Playlist> = (props: Partial<PlaylistProps<any>>) => {
const mainContent = ({ activeItem }: { activeItem: { id: number } }): JSX.Element => (
<div className="flex items-center justify-center h-full">
{activeItem ? `Object ${activeItem.id} selected` : 'Select an item from the list'}
</div>
Expand Down
75 changes: 45 additions & 30 deletions frontend/src/lib/components/Playlist/Playlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { Resizer } from '../Resizer/Resizer'

const SCROLL_TRIGGER_OFFSET = 100

export type PlaylistSection = {
export type PlaylistSection<T> = {
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
}

Expand All @@ -27,17 +27,17 @@ type PlaylistHeaderAction = Pick<LemonButtonProps, 'icon' | 'tooltip' | 'childre
content: React.ReactNode
}

export type PlaylistProps = {
sections: PlaylistSection[]
export type PlaylistProps<T> = {
sections: PlaylistSection<T>[]
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
}
Expand All @@ -46,7 +46,12 @@ const CounterBadge = ({ children }: { children: React.ReactNode }): JSX.Element
<span className="rounded py-1 px-2 mr-1 text-xs bg-border-light font-semibold select-none">{children}</span>
)

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,
Expand All @@ -59,23 +64,23 @@ export function Playlist({
listEmptyState,
onSelect,
'data-attr': dataAttr,
}: PlaylistProps): JSX.Element {
const [controlledActiveItemId, setControlledActiveItemId] = useState<string | null>(null)
}: PlaylistProps<T>): JSX.Element {
const [controlledActiveItemId, setControlledActiveItemId] = useState<T['id'] | null>(null)
const [listCollapsed, setListCollapsed] = useState<boolean>(false)
const playlistListRef = useRef<HTMLDivElement>(null)
const { ref: playlistRef, size } = useResizeBreakpoints({
0: 'small',
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 (
<div
Expand Down Expand Up @@ -112,7 +117,7 @@ export function Playlist({
onDoubleClick={() => setListCollapsed(!listCollapsed)}
/>
</div>
<div className="Playlist__main">{content({ activeItem })}</div>
<div className="Playlist__main">{activeItem && content({ activeItem })}</div>
</div>
)
}
Expand All @@ -123,7 +128,12 @@ const CollapsedList = ({ onClickOpen }: { onClickOpen: () => void }): JSX.Elemen
</div>
)

const List = ({
function List<
T extends {
id: string | number
[key: string]: any
}
>({
title,
notebooksHref,
onClickCollapse,
Expand All @@ -135,17 +145,17 @@ const List = ({
loading,
emptyState,
}: {
title: PlaylistProps['title']
notebooksHref: PlaylistProps['notebooksHref']
title: PlaylistProps<T>['title']
notebooksHref: PlaylistProps<T>['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<T>['headerActions']
sections: PlaylistProps<T>['sections']
onScrollListEdge: PlaylistProps<T>['onScrollListEdge']
loading: PlaylistProps<T>['loading']
emptyState: PlaylistProps<T>['listEmptyState']
}): JSX.Element {
const [activeHeaderActionKey, setActiveHeaderActionKey] = useState<string | null>(null)
const lastScrollPositionRef = useRef(0)
const contentRef = useRef<HTMLDivElement | null>(null)
Expand Down Expand Up @@ -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<T> & {
onClick: (item: T) => void
activeItemId: T['id'] | null
}): JSX.Element {
return (
<>
{items.length &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr

const notebookNode = useNotebookNode()

const sections: PlaylistSection[] = []
const sections: PlaylistSection<SessionRecordingType>[] = []
const headerActions = []

const onSummarizeClick = (recording: SessionRecordingType): void => {
Expand Down Expand Up @@ -127,7 +127,7 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr
<div className="m-4 h-10 flex items-center justify-center gap-2 text-muted-alt">
{sessionRecordingsResponseLoading ? (
<>
<Spinner textColored /> Loading
<Spinner textColored /> Loading older recordings
</>
) : hasNext ? (
<LemonButton onClick={() => maybeLoadSessionRecordings('older')}>Load more</LemonButton>
Expand Down Expand Up @@ -158,7 +158,7 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr
}
}}
listEmptyState={<ListEmptyState />}
onSelect={setSelectedRecordingId}
onSelect={(item) => setSelectedRecordingId(item.id)}
activeItemId={activeSessionRecordingId}
content={({ activeItem }) =>
!activeItem ? (
Expand Down

0 comments on commit 1a5d446

Please sign in to comment.