= ({ item }: Props) => {
+ const { t } = useTranslation();
+
+ const formattedFirstSeen = item.firstSeen
+ ? new Date(item.firstSeen).toLocaleString()
+ : null;
+
+ return <>
+
+
+ {item.label ?? item.address}
+
+
+ {/* Show the address if it has a label, otherwise this is unnecessary */}
+ {item.label && <>
+ {item.address}
+ >}
+
+ {/* Show the category if set */}
+ {item.category && <>
+ {item.category}
+ >}
+
+ {/* Show the name count */}
+
+ {t("myWallets.nameCount", { count: item.names })}
+
+
+ {/* Show the first seen date and time on a new line if set */}
+ {formattedFirstSeen && <>
+
+
+ {t("myWallets.firstSeen", { date: formattedFirstSeen })}
+
+ >}
+
+ >
+}
diff --git a/src/layouts/my-wallets/MyWalletsPage.tsx b/src/layouts/my-wallets/MyWalletsPage.tsx
index 07a9bca..7177961 100644
--- a/src/layouts/my-wallets/MyWalletsPage.tsx
+++ b/src/layouts/my-wallets/MyWalletsPage.tsx
@@ -6,8 +6,6 @@
import { formatKristValue, formatDateTime, formatNumber } from "@components/list-view/Formatters";
import { ListView } from "@components/list-view/ListView";
-import { KristValue } from "@components/krist-value/KristValue";
-
import { IconButton } from "@components/icon-button/IconButton";
import Button from "react-bootstrap/Button";
@@ -15,10 +13,12 @@
import { FilterSelect } from "@components/list-view/FilterSelect";
import { DateString } from "@krist/types/KristTypes";
+import { MyWalletsMobileItem } from "./MyWalletsMobileItem";
+
import { sleep } from "@utils";
// TODO: Temporary
-interface Wallet {
+export interface Wallet {
label?: string;
address: string;
balance: number;
@@ -77,6 +77,7 @@
/>
>}
columns={WALLET_COLUMNS}
+ renderMobileItem={(item: Wallet) => }
dataProvider={async (query: QueryStateBase) => {
// Provide the data to the list view
// TODO: temporary
diff --git a/src/scss/_theme.scss b/src/scss/_theme.scss
index efef820..49514bb 100644
--- a/src/scss/_theme.scss
+++ b/src/scss/_theme.scss
@@ -18,9 +18,14 @@
/* Typography */
$body-color: #eaf0fe;
$text-muted: #8991ab;
+$text-quiet: mix($body-color, $text-muted, 50%);
-$body-hover-color: mix($body-color, $text-muted, 50%);
-$body-hover-color: mix($body-color, $text-muted, 50%);
+$body-hover-color: $text-quiet;
+$body-hover-color: $text-quiet;
+
+.text-quiet {
+ color: $text-quiet;
+}
$font-family-base: "Lato", sans-serif;
@@ -67,6 +72,10 @@
}
}
+/* List group */
+$list-group-bg: transparent;
+$list-group-border-color: $border-color;
+
/* Navbar */
$navbar-dark-color: $body-color;
$navbar-dark-hover-color: $body-hover-color;
diff --git a/src/shared-components/krist-value/KristValue.tsx b/src/shared-components/krist-value/KristValue.tsx
index aa420f4..939b17e 100644
--- a/src/shared-components/krist-value/KristValue.tsx
+++ b/src/shared-components/krist-value/KristValue.tsx
@@ -2,13 +2,15 @@
import "./KristValue.scss";
-interface Props {
+interface OwnProps {
value: number;
long?: boolean;
};
-export const KristValue = ({ value, long }: Props): JSX.Element => (
-
+type Props = React.HTMLProps & OwnProps;
+
+export const KristValue = ({ value, long, ...props }: Props): JSX.Element => (
+
{value.toLocaleString()}
{long && KST}
diff --git a/src/shared-components/list-view/ListMobile.scss b/src/shared-components/list-view/ListMobile.scss
new file mode 100644
index 0000000..a57e7e2
--- /dev/null
+++ b/src/shared-components/list-view/ListMobile.scss
@@ -0,0 +1,8 @@
+@import "~scss/variables";
+
+.list-view .list-group {
+ .list-group-item {
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
diff --git a/src/shared-components/list-view/ListMobile.tsx b/src/shared-components/list-view/ListMobile.tsx
new file mode 100644
index 0000000..f3536f0
--- /dev/null
+++ b/src/shared-components/list-view/ListMobile.tsx
@@ -0,0 +1,34 @@
+import { KristValue } from "@components/krist-value/KristValue";
+import React, { Component, ReactNode } from "react";
+
+import { ListGroup } from "react-bootstrap";
+
+import { Columns, QueryStateBase, DataStateBase } from "./DataProvider";
+
+import "./ListMobile.scss";
+
+export type MobileItemRenderer = (item: T) => ReactNode;
+
+interface Props extends QueryStateBase, DataStateBase {
+ renderListItem: MobileItemRenderer;
+}
+
+export class ListMobile extends Component> {
+ render(): ReactNode {
+ const { renderListItem, loading, data } = this.props;
+
+ // Render skeleton items if the data is loading
+ if (loading || !data) return "loading"; /* TODO */
+
+ // TODO: handle potential edge case where loading = false, data = truthy
+ // TODO: handle errors
+
+ // Otherwise, render the data
+ return
+ {data.map((item, i) =>
+
+ {renderListItem(item)}
+ )}
+
+ }
+}
diff --git a/src/shared-components/list-view/ListTable.tsx b/src/shared-components/list-view/ListTable.tsx
index 8b21349..df55a6e 100644
--- a/src/shared-components/list-view/ListTable.tsx
+++ b/src/shared-components/list-view/ListTable.tsx
@@ -37,9 +37,12 @@
{/* Table rows */}
- {/* Render skeleton rows if the table is loading */}
+ {/* Render skeleton rows if the data is loading */}
{loading && }
+ {/* TODO: handle potential edge case where loading = false, data = truthy */}
+ {/* TODO: handle errors */}
+
{/* Otherwise, render the data */}
{!loading && data && data.map((row, i) => {
title?: string;
actions?: ReactNode;
@@ -21,25 +24,44 @@
columns: Columns;
dataProvider: DataProvider;
+
+ renderMobileItem: MobileItemRenderer;
}
-interface State extends QueryStateBase, DataStateBase {}
+interface State extends QueryStateBase, DataStateBase {
+ isMobile: boolean;
+}
export class ListView extends Component, State> {
constructor(props: Props) {
super(props);
this.state = {
- loading: true
+ 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();
}
- // Assign the new sort orderBy key and direction to the state and refresh the
- // data immediately.
+ 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, order?: SortDirection): void {
this.setState({
orderBy, order,
@@ -58,11 +80,29 @@
});
}
+ /** 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 ;
+ } else { // Not mobile, show a table
+ return ;
+ }
+ }
+
render(): ReactNode {
const {
title, actions, filters,
- page, pages,
- columns
+ page, pages
} = this.props;
return
@@ -92,18 +132,8 @@
}
- {/* Main table */}
-
+ {/* Render the data: list on mobile, table on desktop */}
+ {this.renderData()}
;
}
}