Newer
Older
CrypticOreWallet / src / shared-components / list-view / ListTable.tsx
@Drew Lemmy Drew Lemmy on 20 Sep 2020 4 KB fix: table layout tweaks
import React, { Component, ReactNode } from "react";
import { HeaderSpec, SortDirection } from "./ListView";

import Table from "react-bootstrap/Table";

import { ColumnHeader } from "./ColumnHeader";
import { KristValue } from "@components/krist-value/KristValue";

import "./ListTable.scss";
import { SkeletonText } from "@components/skeleton/SkeletonText";

// Default widths for the skeleton elements that look good enough
const SKELETON_EM_WIDTHS = [10, 7, 5, 4, 8, 10];
const SKELETON_ROWS = 5;

interface State<T> {
  orderBy?: Extract<keyof T, string>;
  order?: SortDirection;

  loading: boolean;
}

interface Props<T> {
  headers?: Map<Extract<keyof T, string>, HeaderSpec>;
}

export class ListTable<T> extends Component<Props<T>, State<T>> {
  constructor(props: Props<T>) {
    super(props);

    this.state = {
      orderBy: undefined,
      order: undefined,

      loading: true
    };
  }

  componentDidMount(): void {
    // TODO: temporary
    setTimeout(() => {
      this.setState({ loading: false });
    }, 6000);
  }

  setSort(orderBy?: Extract<keyof T, string>, order?: SortDirection): void {
    this.setState({
      orderBy, order
    });
  }

  generateSkeletonSizes(): Array<number | undefined> | undefined {
    const { headers } = this.props;
    const { loading } = this.state;

    // Generate the sizes of skeleton text
    const skeletonSizes = headers && loading 
      ? Array.from(headers, ([, headerSpec]) => 
        headerSpec.skeletonEmWidth || undefined) // If no width was defined, use undefined
      : undefined;

    // Fill any missing sizes with defaults
    if (skeletonSizes !== undefined) {
      for (let i = 0; i < Math.min(skeletonSizes.length, SKELETON_EM_WIDTHS.length); i++) {
        if (skeletonSizes[i] !== undefined) continue;
        skeletonSizes[i] = SKELETON_EM_WIDTHS[i];
      }
    }

    return skeletonSizes;
  }

  generateSkeletonRow(skeletonSizes: Array<number | undefined>, rowID: number): Array<ReactNode> {
    return skeletonSizes.map((width, j) =>
      <td key={`skeleton-row-${rowID}-col-${j}`}><SkeletonText emWidth={width} /></td>);
  }

  generateSkeletonRows(skeletonSizes: Array<number | undefined>): Array<ReactNode> {
    return new Array(SKELETON_ROWS).fill(null).map((_, i) => 
      <tr key={`skeleton-row-${i}`}>{this.generateSkeletonRow(skeletonSizes, i)}</tr>);
  }

  render(): ReactNode {
    const { headers } = this.props;
    const { orderBy, order, loading } = this.state;

    // Generate the skeleton rows
    const skeletonSizes = this.generateSkeletonSizes();
    const skeletonRows = skeletonSizes ? this.generateSkeletonRows(skeletonSizes) : null;

    // Render the table
    return <Table borderless hover className={loading ? "loading" : ""}>
      {/* Table headers, defined by the header map in the props */}
      {headers && <thead>
        <tr>
          {Array.from(headers, ([headerKey, headerSpec]) => 
            (<ColumnHeader
              key={headerKey} 
              headerKey={headerKey} 
              headerSpec={headerSpec}
              sortDirection={orderBy === headerKey ? order : undefined} 
              onSort={this.setSort.bind(this)}
            />))}
        </tr>
      </thead>}

      {/* Table rows */}
      <tbody>
        {/* Render skeleton rows if the table is loading loading */}
        {skeletonRows}

        {/* Otherwise, render the data */}
        {!loading && <>
          <tr>
            <td>Shop Wallet</td>
            <td className="text-nowrap">kreichdyes</td>
            <td className="text-nowrap"><KristValue value={15364} /></td>
            <td className="text-nowrap">12</td>
            <td className="text-nowrap">Shops</td>
            <td className="text-nowrap">2020/09/11 08:08</td>
          </tr>

          <tr>
            <td>Main Wallet</td>
            <td className="text-nowrap">khugepoopy</td>
            <td className="text-nowrap"><KristValue value={1024} /></td>
            <td className="text-nowrap">3</td>
            <td className="text-nowrap"></td>
            <td className="text-nowrap">2016/02/14 00:00</td>
          </tr>

          <tr>
            <td>Old Wallet</td>
            <td className="text-nowrap">kre3w0i79j</td>
            <td className="text-nowrap"><KristValue value={0} /></td>
            <td className="text-nowrap">0</td>
            <td className="text-nowrap"></td>
            <td className="text-nowrap">2015/02/14 00:00</td>
          </tr>
        </>}
      </tbody>
    </Table>;
  }
}