2
0
Files
bot/apps/builder/components/shared/TableList.tsx
2022-01-22 18:24:57 +01:00

115 lines
3.0 KiB
TypeScript

import { Box, Button, Fade, Flex, IconButton, Stack } from '@chakra-ui/react'
import { TrashIcon, PlusIcon } from 'assets/icons'
import { Draft } from 'immer'
import { Table } from 'models'
import React, { useEffect, useState } from 'react'
import { generate } from 'short-uuid'
import { useImmer } from 'use-immer'
export type TableListItemProps<T> = {
id: string
item: T
onItemChange: (item: T) => void
}
type Props<T> = {
initialItems?: Table<T>
onItemsChange: (items: Table<T>) => void
addLabel?: string
Item: (props: TableListItemProps<T>) => JSX.Element
ComponentBetweenItems?: (props: unknown) => JSX.Element
}
export const TableList = <T,>({
initialItems,
onItemsChange,
addLabel = 'Add',
Item,
ComponentBetweenItems = () => <></>,
}: Props<T>) => {
const [items, setItems] = useImmer(initialItems ?? { byId: {}, allIds: [] })
const [showDeleteId, setShowDeleteId] = useState<string | undefined>()
useEffect(() => {
if (items.allIds.length === 0) createItem()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
onItemsChange(items)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items])
const createItem = () => {
setItems((items) => {
const id = generate()
items.byId[id] = { id } as unknown as Draft<T>
items.allIds.push(id)
})
}
const updateItem = (itemId: string, updates: Partial<T>) =>
setItems((items) => {
items.byId[itemId] = {
...items.byId[itemId],
...updates,
}
})
const deleteItem = (itemId: string) => () => {
setItems((items) => {
delete items.byId[itemId]
const index = items.allIds.indexOf(itemId)
if (index !== -1) items.allIds.splice(index, 1)
})
}
const handleMouseEnter = (itemId: string) => () => setShowDeleteId(itemId)
const handleCellChange = (itemId: string) => (item: T) =>
updateItem(itemId, item)
const handleMouseLeave = () => setShowDeleteId(undefined)
return (
<Stack spacing="4">
{items.allIds.map((itemId, idx) => (
<Box key={itemId}>
{idx !== 0 && <ComponentBetweenItems />}
<Flex
pos="relative"
onMouseEnter={handleMouseEnter(itemId)}
onMouseLeave={handleMouseLeave}
>
<Item
id={itemId}
item={items.byId[itemId]}
onItemChange={handleCellChange(itemId)}
/>
<Fade in={showDeleteId === itemId}>
<IconButton
icon={<TrashIcon />}
aria-label="Remove cell"
onClick={deleteItem(itemId)}
pos="absolute"
left="-15px"
top="-15px"
size="sm"
shadow="md"
/>
</Fade>
</Flex>
</Box>
))}
<Button
leftIcon={<PlusIcon />}
onClick={createItem}
flexShrink={0}
colorScheme="blue"
>
{addLabel}
</Button>
</Stack>
)
}