feat(island): ✨ add BlogCardList
to fetch and display news progressively
This commit is contained in:
parent
72281ae551
commit
c1eeb42f21
89
islands/BlogCardList.tsx
Normal file
89
islands/BlogCardList.tsx
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import { BlogCard, BlogProps } from ':components/BlogBlocks.tsx'
|
||||||
|
import Suspense from ':islands/Suspens.tsx'
|
||||||
|
import { requestApiStream } from ':src/utils.ts'
|
||||||
|
import { Signal, useSignal } from '@preact/signals'
|
||||||
|
import type { JSX } from 'preact'
|
||||||
|
import { useEffect } from 'preact/hooks'
|
||||||
|
|
||||||
|
function fillList(
|
||||||
|
list: Signal<JSX.Element[]>,
|
||||||
|
{ limit, ac }: { limit?: number; ac?: AbortController },
|
||||||
|
) {
|
||||||
|
;(async () => {
|
||||||
|
const newsList = requestApiStream<void, BlogProps>(
|
||||||
|
'news/fetchAll',
|
||||||
|
'GET',
|
||||||
|
)
|
||||||
|
|
||||||
|
for await (const news of newsList) {
|
||||||
|
list.value = [
|
||||||
|
...list.value,
|
||||||
|
BlogCard({ ...news, lastUpdate: new Date(news.lastUpdate) }),
|
||||||
|
]
|
||||||
|
if (limit && list.value.length >= limit) break
|
||||||
|
}
|
||||||
|
ac?.abort()
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlogCardList(
|
||||||
|
{ limit, usePlaceholder }: { usePlaceholder?: boolean; limit?: number },
|
||||||
|
) {
|
||||||
|
const list = useSignal<JSX.Element[]>([])
|
||||||
|
const ac = new AbortController()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fillList(list, { limit, ac })
|
||||||
|
})
|
||||||
|
|
||||||
|
if (limit && usePlaceholder) {
|
||||||
|
const placeholders = Array
|
||||||
|
.from({ length: limit })
|
||||||
|
.map((_, index) => (
|
||||||
|
<Suspense
|
||||||
|
loader={<Placeholder />}
|
||||||
|
fallback={Fallback}
|
||||||
|
signal={ac.signal}
|
||||||
|
>
|
||||||
|
{updateFromList(list, index)}
|
||||||
|
</Suspense>
|
||||||
|
))
|
||||||
|
return <>{placeholders}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{list}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Placeholder() {
|
||||||
|
return (
|
||||||
|
<div class='components__blog_block components__blog_block--card components__blog_block--placeholder'>
|
||||||
|
<h3>Chargement ...</h3>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Fallback() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class='components__blog_block components__blog_block--card components__blog_block--fallback'
|
||||||
|
inert
|
||||||
|
>
|
||||||
|
<h3>Pas de news disponible</h3>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFromList(
|
||||||
|
list: Signal<JSX.Element[]>,
|
||||||
|
index: number,
|
||||||
|
): Promise<JSX.Element> {
|
||||||
|
const { promise, resolve } = Promise.withResolvers<JSX.Element>()
|
||||||
|
list.subscribe((value) => {
|
||||||
|
const selected = value.at(index)
|
||||||
|
if (selected) {
|
||||||
|
resolve(selected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return promise
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
import { AutoGrid } from ':components/AutoGrid.tsx'
|
import { AutoGrid } from ':components/AutoGrid.tsx'
|
||||||
import { BlogCard, blogMock } from ':components/BlogCard.tsx'
|
import BlogCardList from ':islands/BlogCardList.tsx'
|
||||||
|
|
||||||
export default function Blog() {
|
export default function Blog() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Nos articles</h1>
|
<h1>Nos articles</h1>
|
||||||
<AutoGrid columnWidth='15rem'>
|
<AutoGrid columnWidth='15rem'>
|
||||||
{blogMock.map(BlogCard)}
|
<BlogCardList />
|
||||||
</AutoGrid>
|
</AutoGrid>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue