Skip to content

Commit

Permalink
work
Browse files Browse the repository at this point in the history
  • Loading branch information
vassbence committed Sep 15, 2024
1 parent beffbae commit e15810d
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 92 deletions.
79 changes: 61 additions & 18 deletions src/components/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import React, { useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Table, TableSelect, TableSort } from './index.js'
import { Badge } from '../Badge/index.js'
import { Menu } from '../Menu/index.js'
import { IconButton } from '../IconButton/index.js'
import { prettyNumber } from '@based/pretty-number'
import { prettyDate } from '@based/pretty-date'

// TODO BUG:
// when table is sorted (eg desc createdAt) and new items are added somehow we run into duplicate IDs and that freaks out react. so somehow an entry with the same id (i guess it's the same entry) is in more than 1 chunk.

// a problemat az okozza, hogy ha pl be van toltve 20 chunk (20 subscription), es sorton vagyunk, es hozzaadok uj elemeket, akkor ezek az uj elemek ugyebar az osszes chunkon valtozast okoznak. ezek a valtozasok random sorrendben erkeznek be a reacthez, raadasul egyesevel, es amikor egyesevel erkeznek be siman benne van, hogy egy elem ami epp chunk hataron volt mostmar egy uj chunkba kerul, arrol megjon az update, de az elozoben amiben volt meg nem jott update, igy meg is van a duplikalt ID.

import { Text } from '../Text/index.js'
import { useQuery } from '@based/react'
import { useClient } from '@based/react'

export default {
title: 'Table (WIP)',
Expand All @@ -21,27 +26,60 @@ export const Default = () => {
const [sort, setSort] = useState<TableSort>()
const [select, setSelect] = useState<TableSelect>()

const { data } = useQuery('db', {
files: {
$all: true,
$list: {
$find: {
$traverse: 'children',
$filter: {
$field: 'type',
$operator: '=',
$value: 'file',
const accessFn = (data) => data.files
const client = useClient()
const [fetching, setFetching] = useState(false)
const [chunks, setChunks] = useState<any[]>([])
const data = chunks.flatMap((chunk) => accessFn(chunk))

function fetchChunk() {
if (fetching) return
setFetching(true)

const index = chunks.length

client
.query('db', {
files: {
$all: true,
$list: {
$limit: 24,
$offset: data.length,
$find: {
$traverse: 'children',
$filter: {
$field: 'type',
$operator: '=',
$value: 'file',
},
},
...(sort && {
$sort: { $field: sort.key, $order: sort.direction },
}),
},
},
...(sort && { $sort: { $field: sort.key, $order: sort.direction } }),
},
},
})
})
.subscribe((chunk) => {
console.log('update', index)
if (accessFn(chunk).length) {
setChunks((prevChunks) => {
const newArray = [...prevChunks]
newArray[index] = chunk
return newArray
})
}
setFetching(false)
})
}

useEffect(() => {
fetchChunk()
}, [sort])

return (
<div style={{ height: '100svh' }}>
<Table
data={data?.files}
data={data}
columns={[
{ key: 'id', header: 'ID' },
{ key: 'name', header: 'Name' },
Expand Down Expand Up @@ -122,9 +160,14 @@ export const Default = () => {
},
]}
sort={sort}
onSortChange={setSort}
onSortChange={(value) => {
setSort(value)
setFetching(false)
setChunks([])
}}
select={select}
onSelectChange={setSelect}
onScrolledToBottom={fetchChunk}
/>
</div>
)
Expand Down
70 changes: 55 additions & 15 deletions src/components/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { Text } from '../Text/index.js'
import { Icon } from '../Icon/index.js'
import { CheckboxInput } from '../CheckboxInput/index.js'
import { styled } from 'inlines'
import { useVirtualizer } from '../../hooks/useVirtualizer.js'

// TODO add pagination/infinite loading logic
// TODO finish pagination/infinite loading
// TODO better API for rowActions
// TODO row onclick
// TODO API for row onclick
// TODO better padding when not selectable on the left (contact design)

type TableInternal = {
Expand All @@ -33,6 +32,7 @@ type TableProps = {
onSortChange?: (sort?: TableSort) => void
select?: TableSelect
onSelectChange?: (select?: TableSelect) => void
onScrolledToBottom?: () => void
}

function Table({
Expand All @@ -42,18 +42,59 @@ function Table({
onSortChange,
select,
onSelectChange,
onScrolledToBottom,
}: TableProps) {
const [hover, setHover] = useState<string>()
const [forceHover, setForceHover] = useState<string>()

const ITEM_HEIGHT = 44
const ITEM_COUNT = data.length
const [firstVisibleItemIndex, setFirstVisibleItemIndex] = useState(0)
const [lastVisibleItemIndex, setLastVisibleItemIndex] = useState(0)
const scrollElementRef = useRef<HTMLDivElement>()
const { firstVisibleItemIndex, lastVisibleItemIndex } = useVirtualizer({
scrollElementRef,
itemCount: data.length,
itemHeight: ITEM_HEIGHT,
})
const ROW_HEIGHT = 44

useLayoutEffect(() => {
if (!scrollElementRef.current) return () => {}

function handleScroll() {
const { scrollTop, scrollHeight, clientHeight } = scrollElementRef.current

// handling virtualization
const numberOfItemsVisibleAtOnce = Math.ceil(clientHeight / ROW_HEIGHT)
const firstVisibleItemIndex = Math.floor(scrollTop / ROW_HEIGHT)
const lastVisibleItemIndex =
firstVisibleItemIndex + numberOfItemsVisibleAtOnce
const overScan = Math.ceil(numberOfItemsVisibleAtOnce / 2)

setFirstVisibleItemIndex(Math.max(0, firstVisibleItemIndex - overScan))
setLastVisibleItemIndex(
Math.min(data.length, lastVisibleItemIndex + overScan),
)

// handling infinity loading
const pixelsToBottom = scrollHeight - scrollTop - clientHeight
if (
scrollTop > 0 &&
pixelsToBottom < (numberOfItemsVisibleAtOnce * ROW_HEIGHT) / 2
) {
onScrolledToBottom()
}
}

handleScroll()
scrollElementRef.current.addEventListener('scroll', handleScroll)
const resizeObserver = new ResizeObserver(handleScroll)
resizeObserver.observe(scrollElementRef.current)

return () => {
scrollElementRef.current.removeEventListener('scroll', handleScroll)
resizeObserver.disconnect()
}
}, [scrollElementRef, data, onScrolledToBottom])

useLayoutEffect(() => {
if (!scrollElementRef.current) return

scrollElementRef.current.scrollTop = 0
}, [sort])

return (
<styled.div
Expand All @@ -71,7 +112,6 @@ function Table({
<table
style={{
width: '100%',
height: '100%',
borderSpacing: 0,
tableLayout: 'fixed',
}}
Expand Down Expand Up @@ -185,7 +225,7 @@ function Table({
<td
style={{
padding: 0,
height: firstVisibleItemIndex * ITEM_HEIGHT,
height: firstVisibleItemIndex * ROW_HEIGHT,
}}
/>
</tr>
Expand Down Expand Up @@ -265,12 +305,12 @@ function Table({
))}
</tr>
))}
{lastVisibleItemIndex < ITEM_COUNT && (
{lastVisibleItemIndex < data.length && (
<tr>
<td
style={{
padding: 0,
height: (ITEM_COUNT - lastVisibleItemIndex) * ITEM_HEIGHT,
height: (data.length - lastVisibleItemIndex) * ROW_HEIGHT,
}}
/>
</tr>
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useRerender.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useState } from 'react'

export function useRerender() {
const [, setState] = useState<Record<string, never>>()
const [, setState] = useState({})

const rerender = useCallback(() => {
setState({})
Expand Down
57 changes: 0 additions & 57 deletions src/hooks/useVirtualizer.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export * from './components/SelectInput/index.js'
export * from './components/DateInput/index.js'
export * from './components/FileInput/index.js'
export * from './components/Table/index.js'
export * from './hooks/useVirtualizer.js'
export * from './hooks/useTheme.js'
export * from './hooks/useRerender.js'
export * from './utils/colors.js'
Expand Down

0 comments on commit e15810d

Please sign in to comment.