Skip to content

Commit

Permalink
Format amounts with sup fraction digits
Browse files Browse the repository at this point in the history
Makes the fractional digits display like exponents. The idea to
de-emphasize the fractional part without completely hiding it as it
could be looking incorrect. And to make it look like prices are often
being displayed in supermarkets and alike.
  • Loading branch information
xMartin committed May 31, 2023
1 parent 6dc8792 commit e4bd937
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 28 deletions.
33 changes: 30 additions & 3 deletions src/components/__snapshots__/main.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,16 @@ exports[`renders summary and transaction list 1`] = `
<td
className="amount"
>
-11.7
<span
className="amount-integer"
>
-11
</span>
<small
className="amount-fraction"
>
70
</small>
</td>
</tr>
<tr
Expand All @@ -203,7 +212,16 @@ exports[`renders summary and transaction list 1`] = `
<td
className="amount"
>
11.7
<span
className="amount-integer"
>
11
</span>
<small
className="amount-fraction"
>
70
</small>
</td>
</tr>
</tbody>
Expand Down Expand Up @@ -250,7 +268,16 @@ exports[`renders summary and transaction list 1`] = `
<td
className="total"
>
22.8
<span
className="amount-integer"
>
22
</span>
<small
className="amount-fraction"
>
80
</small>
</td>
</tr>
</tbody>
Expand Down
108 changes: 108 additions & 0 deletions src/components/amount.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import Amount from "./amount";

it("renders 0 correctly", () => {
const { asFragment } = render(<Amount>{0}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
const fraction = asFragment()
.querySelector(".amount-fraction")
?.innerHTML.trim();
expect(integer).toBe("0");
expect(fraction).toBe("00");
});

it("renders positive two-digit integer correctly", () => {
const { asFragment } = render(<Amount>{15}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
const fraction = asFragment()
.querySelector(".amount-fraction")
?.innerHTML.trim();
expect(integer).toBe("15");
expect(fraction).toBe("00");
});

it("renders negative integer correctly", () => {
const { asFragment } = render(<Amount>{-8}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
const fraction = asFragment()
.querySelector(".amount-fraction")
?.innerHTML.trim();
expect(integer).toBe("-8");
expect(fraction).toBe("00");
});

it("renders number with 1 fraction digit correctly", () => {
const { asFragment } = render(<Amount>{28.4}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
const fraction = asFragment()
.querySelector(".amount-fraction")
?.innerHTML.trim();
expect(integer).toBe("28");
expect(fraction).toBe("40");
});

it("renders number with 2 fraction digits correctly", () => {
const { asFragment } = render(<Amount>{231.87}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
const fraction = asFragment()
.querySelector(".amount-fraction")
?.innerHTML.trim();
expect(integer).toBe("231");
expect(fraction).toBe("87");
});

it("renders number with 3 fraction digits rounded up correctly", () => {
const { asFragment } = render(<Amount>{0.336}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
const fraction = asFragment()
.querySelector(".amount-fraction")
?.innerHTML.trim();
expect(integer).toBe("0");
expect(fraction).toBe("34");
});

it("renders number with 3 fraction digits rounded down correctly", () => {
const { asFragment } = render(<Amount>{-72.168}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
const fraction = asFragment()
.querySelector(".amount-fraction")
?.innerHTML.trim();
expect(integer).toBe("-72");
expect(fraction).toBe("17");
});

it("hides fraction if it's zero and hide zero flag is set", () => {
const { asFragment } = render(<Amount hideZeroFraction>{-28}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
expect(integer).toBe("-28");
expect(asFragment().querySelector(".amount-fraction")).toBeNull();
});

it("shows fraction if hide zero flag is set but it's not zero", () => {
const { asFragment } = render(<Amount hideZeroFraction>{-12.8}</Amount>);
const integer = asFragment()
.querySelector(".amount-integer")
?.innerHTML.trim();
const fraction = asFragment()
.querySelector(".amount-fraction")
?.innerHTML.trim();
expect(integer).toBe("-12");
expect(fraction).toBe("80");
});
28 changes: 28 additions & 0 deletions src/components/amount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { memo, FunctionComponent } from "react";

interface Props {
children: number;
hideZeroFraction?: boolean;
}

const Amount: FunctionComponent<Props> = ({
children: value,
hideZeroFraction,
}) => {
const integer = Math.trunc(value);
const fraction = Math.round(Math.abs(value % 1) * 100);
const paddedFraction = fraction < 10 ? `0${fraction}` : fraction;

const showFraction = !hideZeroFraction || paddedFraction !== "00";

return (
<>
<span className="amount-integer">{integer}</span>
{showFraction && (
<small className="amount-fraction">{paddedFraction}</small>
)}
</>
);
};

export default memo(Amount);
5 changes: 4 additions & 1 deletion src/components/summary.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { memo, FunctionComponent } from "react";
import { Account } from "../types";
import Amount from "./amount";

interface Props {
accounts: Account[];
Expand Down Expand Up @@ -46,7 +47,9 @@ const Summary: FunctionComponent<Props> = ({ accounts }) => (
{formatData(accounts).map((account) => (
<tr key={account.participant} style={account.style}>
<th className="account">{account.participant}</th>
<td className="amount">{account.amount}</td>
<td className="amount">
<Amount>{account.amount}</Amount>
</td>
</tr>
))}
</tbody>
Expand Down
32 changes: 14 additions & 18 deletions src/components/transactionlistitem.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import React, { FunctionComponent, memo, ReactElement } from "react";
import React, { FunctionComponent, memo } from "react";
import { Transaction, TransactionType } from "../types";
import Amount from "./amount";

interface Props {
transaction: Transaction;
onDetailsClick: (tabId: string, transactionId: string) => void;
}

interface ViewData {
title: string;
payments?: string | ReactElement;
participants?: string;
total?: number;
}

const formatData = (data: Transaction) => {
const round = (amount: number) => Math.round(amount * 100) / 100;

const result: ViewData = {
title: data.description,
};

const paymentsList = data.participants
.filter((participant) => {
return data.transactionType === TransactionType.DIRECT
Expand Down Expand Up @@ -55,8 +45,7 @@ const formatData = (data: Transaction) => {

total += payment.amount;
});
result.payments = <strong>{payments}</strong>;
result.total = round(total);
total = round(total);

const participantsList = data.participants
.map((participant) => participant.participant)
Expand All @@ -72,9 +61,14 @@ const formatData = (data: Transaction) => {

participants += participant + ", ";
});
result.participants = participants.substring(0, participants.length - 2);
participants = participants.substring(0, participants.length - 2);

return result;
return {
title: data.description,
payments,
participants,
total,
};
};

const TransactionListItem: FunctionComponent<Props> = ({
Expand All @@ -94,11 +88,13 @@ const TransactionListItem: FunctionComponent<Props> = ({
<td className="title">
{data.title}
<div className="payments">
{data.payments}
<strong>{data.payments}</strong>
{data.participants}
</div>
</td>
<td className="total">{data.total}</td>
<td className="total">
<Amount hideZeroFraction>{data.total}</Amount>
</td>
</tr>
</tbody>
</table>
Expand Down
12 changes: 6 additions & 6 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ button.selected {
background: var(--button-selected-background-color);
}

.amount-fraction {
font-size: 60%;
vertical-align: top;
margin-left: 0.2em;
}

select {
cursor: pointer;
margin: 0;
Expand Down Expand Up @@ -598,12 +604,6 @@ input[type="number"] {
width: 100%;
padding: 0;
}
#transactions .transaction .total {
line-height: 1.1em;
white-space: nowrap;
padding: 0;
vertical-align: middle;
}
#transactions .transaction .payments {
margin-top: 2px;
font-size: 12px;
Expand Down

0 comments on commit e4bd937

Please sign in to comment.