Newer
Older
CrypticOreWallet / src / shared-components / list-view / ListView.tsx
@Drew Lemmy Drew Lemmy on 25 Sep 2020 3 KB feat: mobile list view
import React, { Component, ReactNode } from "react";

import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";

import { Columns, ColumnKey, SortDirection, DataProvider, QueryStateBase, DataStateBase } from "./DataProvider";
import { ListPagination } from "./ListPagination";
import { ListTable } from "./ListTable";
import { ListMobile, MobileItemRenderer } from "./ListMobile";

import "./ListView.scss";

export const MOBILE_BREAKPOINT = 768;

interface Props<T> {
  title?: string;
  actions?: ReactNode;
  filters?: ReactNode;

  page?: number;
  pages?: number;

  columns: Columns<T>;

  dataProvider: DataProvider<T>;

  renderMobileItem: MobileItemRenderer<T>;
}

interface State<T> extends QueryStateBase<T>, DataStateBase<T> {
  isMobile: boolean;
}

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

    this.state = {
      loading: true,
      isMobile: false
    };
  }

  // Arrow function to implicitly bind 'this'
  checkDimensions = (): void => {
    this.setState({
      isMobile: window.innerWidth < MOBILE_BREAKPOINT
    });
  }

  componentDidMount(): void {
    window.addEventListener("resize", this.checkDimensions);
    this.checkDimensions();

    this.loadData();
  }

  componentWillUnmount(): void {
    window.addEventListener("resize", this.checkDimensions);
  }

  /** Assign the new sort orderBy key and direction to the state and refresh the
   * data immediately. */
  setSort(orderBy?: ColumnKey<T>, order?: SortDirection): void {
    this.setState({
      orderBy, order,
      loading: true // Reload the data
    }, () => this.loadData());
  }

  /** Refresh the data with the latest query parameters */
  async loadData(): Promise<void> {
    const data = await this.props.dataProvider(this.state);

    this.setState({
      loading: false,
      total: data.total,
      data: data.data
    });
  }

  /** Render data based on whether or not this is on mobile */
  renderData(): ReactNode {
    const { columns, renderMobileItem } = this.props;
    const { isMobile } = this.state;

    if (isMobile) { // Mobile, show a list
      return <ListMobile
        {...this.state} // Apply the order, loading and data state props
        renderListItem={renderMobileItem}
      />;
    } else { // Not mobile, show a table
      return <ListTable 
        columns={columns}
        {...this.state} // Apply the order, loading and data state props
        setSort={this.setSort.bind(this)}
      />;
    }
  }

  render(): ReactNode {
    const { 
      title, actions, filters, 
      page, pages
    } = this.props;

    return <Container fluid className="py-4 list-view">
      {/* Main header row - wallet count and action buttons */}
      <Row className="mb-2">
        <Col className="d-flex align-items-center">
          {/* List title */}
          {title && <h3 className="flex-fill mb-0">{title}</h3>}

          {/* Optional action button row */}
          {actions && <div className="list-view-actions">{actions}</div>}
        </Col>
      </Row>

      {/* Search, filter and pagination row */}
      <Row className="mb-2">
        {/* List filters (e.g. search, category dropdown) */}
        {filters && 
          <Col className="list-view-filters">
            {filters}
          </Col>}

        {/* Pagination */}
        {page && pages && 
          <Col className="flex-grow-0">
            <ListPagination defaultPage={page} pages={pages} />
          </Col>}
      </Row>

      {/* Render the data: list on mobile, table on desktop */}
      {this.renderData()}
    </Container>;
  }
}