ElectronicBabylonianLiterature/ebl-frontend

View on GitHub
src/common/List.tsx

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
import React, { ReactNode } from 'react'
import { Badge, Button, Card, ListGroup } from 'react-bootstrap'
import _ from 'lodash'

import './List.css'
import { CollapsibleCard } from './CollabsibleCard'
import { produce } from 'immer'

function SizeBadge({
  collection,
}: {
  collection: unknown[]
}): JSX.Element | null {
  const size = collection.length
  return size > 0 ? (
    <Badge variant="light" pill>
      {size}
    </Badge>
  ) : null
}

function createDefaultValue(defaultValue) {
  if (_.isFunction(defaultValue)) {
    return defaultValue()
  } else if (_.isObjectLike(defaultValue)) {
    return _.cloneDeep(defaultValue)
  } else {
    return defaultValue
  }
}

function listController(ListView) {
  return function ListController({
    value,
    children,
    onChange,
    defaultValue,
    ...props
  }) {
    const add = (): void => {
      const newItem = createDefaultValue(defaultValue)
      onChange(
        produce(value, (draft) => {
          draft.push(newItem)
        })
      )
    }

    const delete_ = (index) => () => {
      onChange(
        produce(value, (draft) => {
          draft.splice(index, 1)
        })
      )
    }

    const update = (index) => (updated) => {
      onChange(
        produce(value, (draft) => {
          draft[index] = updated
        })
      )
    }

    return (
      <div className="List">
        <ListView
          {...props}
          onDelete={delete_}
          onAdd={add}
          elements={value.map((item, index) =>
            children(item, update(index), index)
          )}
        />
      </div>
    )
  }
}

function CardListView({
  label,
  noun,
  elements,
  ordered,
  collapsed,
  onAdd,
  onDelete,
}: {
  label: ReactNode
  noun: string
  elements: any[]
  ordered: boolean
  collapsed: boolean
  onAdd: () => void
  onDelete: (index: number) => () => void
}) {
  const fullLabel = !_.isNil(label) ? (
    <>
      {label} <SizeBadge collection={elements} />
    </>
  ) : null
  return (
    <CollapsibleCard label={fullLabel} collapsed={collapsed}>
      <ListGroup as={ordered ? 'ol' : 'ul'} variant="flush">
        {elements.map((element, index) => (
          <ListGroup.Item as="li" key={index}>
            {element}
            <Button
              onClick={onDelete(index)}
              size="sm"
              variant="outline-secondary"
            >
              Delete {noun}
            </Button>
          </ListGroup.Item>
        ))}
      </ListGroup>
      <Card.Body>
        <Button onClick={onAdd} size="sm" variant="outline-secondary">
          Add {noun}
        </Button>
      </Card.Body>
    </CollapsibleCard>
  )
}

export default listController(CardListView)