Practice words
diff --git a/frontend/src/styles/popups.scss b/frontend/src/styles/popups.scss
index b5178db43a68..d14736c2d1c5 100644
--- a/frontend/src/styles/popups.scss
+++ b/frontend/src/styles/popups.scss
@@ -259,74 +259,6 @@ body.darkMode {
}
}
-#pbTablesModal {
- .modal {
- max-width: 100%;
- overflow-y: scroll;
- table {
- border-spacing: 0;
- border-collapse: collapse;
- color: var(--text-color);
-
- tbody {
- clip-path: inset(0);
- }
-
- td {
- padding: 0.5rem 0.5rem;
- }
-
- .modesticky {
- position: sticky;
- top: calc(1rem - 2px);
- z-index: 2;
- }
-
- thead {
- color: var(--sub-color);
- font-size: 0.75rem;
- position: sticky;
- top: -2rem;
- background-color: var(--bg-color) !important;
- z-index: 3;
- }
-
- tbody tr.odd {
- background: var(--sub-alt-color);
- }
-
- tbody tr.even {
- background: var(--bg-color);
- }
-
- td.infoIcons span {
- margin: 0 0.1rem;
- }
- .miniResultChartButton {
- opacity: 0.25;
- transition: 0.25s;
- cursor: pointer;
- &:hover {
- opacity: 1;
- }
- }
- .sub {
- opacity: 0.5;
- }
- td {
- text-align: right;
- }
- td:nth-child(6),
- td:nth-child(7) {
- text-align: center;
- }
- tbody td:nth-child(1) {
- font-size: 1.5rem;
- }
- }
- }
-}
-
#importExportSettingsModal {
.modal {
max-width: 900px;
diff --git a/frontend/src/ts/components/common/AnimatedModal.tsx b/frontend/src/ts/components/common/AnimatedModal.tsx
index 76b0a81bcc9d..8489d8b81f9b 100644
--- a/frontend/src/ts/components/common/AnimatedModal.tsx
+++ b/frontend/src/ts/components/common/AnimatedModal.tsx
@@ -330,7 +330,7 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement {
>
+
diff --git a/frontend/src/ts/components/modals/PbTablesModal.tsx b/frontend/src/ts/components/modals/PbTablesModal.tsx
new file mode 100644
index 000000000000..02878de8d132
--- /dev/null
+++ b/frontend/src/ts/components/modals/PbTablesModal.tsx
@@ -0,0 +1,180 @@
+import { Mode2, Mode, PersonalBest } from "@monkeytype/schemas/shared";
+import { createColumnHelper } from "@tanstack/solid-table";
+import { format as formatDate } from "date-fns/format";
+import { createMemo, createSignal, JSXElement } from "solid-js";
+
+import { getConfig } from "../../config/store";
+import * as DB from "../../db";
+import { pbTablesMode } from "../../states/pb-tables-modal";
+import { cn } from "../../utils/cn";
+import { Formatting } from "../../utils/format";
+import { getLanguageDisplayString } from "../../utils/strings";
+import { AnimatedModal } from "../common/AnimatedModal";
+import { Fa } from "../common/Fa";
+import { DataTable, DataTableColumnDef } from "../ui/table/DataTable";
+
+type PBWithMode2 = PersonalBest & {
+ mode2: Mode2
;
+};
+
+type PBRow = PBWithMode2 & {
+ isGroupStart: boolean;
+};
+
+function buildRows(mode: Mode): PBRow[] {
+ const allmode2 = DB.getSnapshot()?.personalBests?.[mode] as
+ | Record, PBWithMode2[]>
+ | undefined;
+ if (allmode2 === undefined) return [];
+
+ const list: PBWithMode2[] = [];
+ Object.keys(allmode2).forEach((key) => {
+ let pbs = allmode2[key] ?? [];
+ pbs = [...pbs].sort((a, b) => b.wpm - a.wpm);
+ pbs.forEach((pb) => {
+ list.push({ ...pb, mode2: key });
+ });
+ });
+
+ const rows: PBRow[] = [];
+ let currentMode2: Mode2 | undefined;
+
+ list.forEach((pb) => {
+ const isGroupStart = currentMode2 !== pb.mode2;
+ currentMode2 = pb.mode2;
+ rows.push({ ...pb, isGroupStart });
+ });
+
+ return rows;
+}
+
+function getColumns(options: {
+ format: Formatting;
+ mode: Mode;
+}): DataTableColumnDef[] {
+ const defineColumn = createColumnHelper().accessor;
+ const { format: f, mode: m } = options;
+
+ const columns = [
+ defineColumn("mode2", {
+ header: m,
+ cell: (info) => info.getValue(),
+ meta: {
+ align: "right",
+ cellMeta: (info) => ({
+ class: cn(
+ "text-xl font-light text-text/40",
+ info.row.isGroupStart && "font-normal text-text",
+ ),
+ }),
+ },
+ }),
+ defineColumn("wpm", {
+ header: () => (
+ <>
+ {f.typingSpeedUnit}
+
+ accuracy
+ >
+ ),
+ cell: (info) => (
+ <>
+ {f.typingSpeed(info.getValue())}
+
+ {f.accuracy(info.row.original.acc)}
+ >
+ ),
+ meta: { align: "right" },
+ }),
+ defineColumn("raw", {
+ header: () => (
+ <>
+ raw
+
+ consistency
+ >
+ ),
+ cell: (info) => (
+ <>
+ {f.typingSpeed(info.getValue())}
+
+
+ {f.percentage(info.row.original.consistency)}
+
+ >
+ ),
+ meta: { align: "right" },
+ }),
+ defineColumn("difficulty", {
+ header: "difficulty",
+ cell: (info) => info.getValue(),
+ meta: { align: "right" },
+ }),
+ defineColumn("language", {
+ header: "language",
+ cell: (info) => {
+ const lang = info.getValue();
+ return lang ? getLanguageDisplayString(lang) : "-";
+ },
+ meta: { align: "right" },
+ }),
+ defineColumn("punctuation", {
+ header: "punctuation",
+ cell: (info) => (info.getValue() ? : null),
+ meta: { align: "center" },
+ }),
+ defineColumn("numbers", {
+ header: "numbers",
+ cell: (info) => (info.getValue() ? : null),
+ meta: { align: "center" },
+ }),
+ defineColumn("lazyMode", {
+ header: "lazy mode",
+ cell: (info) => (info.getValue() ? : null),
+ meta: { align: "center" },
+ }),
+ defineColumn("timestamp", {
+ header: "date",
+ cell: (info) =>
+ info.getValue() ? (
+ <>
+ {formatDate(info.getValue(), "dd MMM yyyy")}
+
+ {formatDate(info.getValue(), "HH:mm")}
+ >
+ ) : (
+ <>
+ -
+ -
+ >
+ ),
+ meta: { align: "right" },
+ }),
+ ];
+
+ return columns.map((it) => ({ ...it, enableSorting: false }));
+}
+
+export function PbTablesModal(): JSXElement {
+ const [rows, setRows] = createSignal([]);
+ const columns = createMemo(() =>
+ getColumns({ format: new Formatting(getConfig), mode: pbTablesMode() }),
+ );
+
+ return (
+ {
+ setRows(buildRows(pbTablesMode()));
+ }}
+ >
+
+
+ );
+}
diff --git a/frontend/src/ts/components/pages/profile/UserProfile.tsx b/frontend/src/ts/components/pages/profile/UserProfile.tsx
index 3bc907fb4b1e..e3f34558b6f1 100644
--- a/frontend/src/ts/components/pages/profile/UserProfile.tsx
+++ b/frontend/src/ts/components/pages/profile/UserProfile.tsx
@@ -7,7 +7,7 @@ import { formatDate } from "date-fns/format";
import { createMemo, For, JSXElement, Show } from "solid-js";
import { getConfig } from "../../../config/store";
-import * as PbTablesModal from "../../../modals/pb-tables";
+import { showPbTablesModal } from "../../../states/pb-tables-modal";
import { Formatting } from "../../../utils/format";
import { formatTopPercentage } from "../../../utils/misc";
import { Button } from "../../common/Button";
@@ -31,13 +31,13 @@ export function UserProfile(props: {
/>
-
-
(props: {
+function PbCard(props: {
mode: M;
mode2: string[];
pbs: PersonalBests[M];
@@ -176,7 +176,7 @@ function PbTable(props: {
balloon={{ text: "Show all personal bests", position: "left" }}
class="h-full rounded-none rounded-r text-sub hover:text-bg"
fa={{ icon: "fa-ellipsis-v" }}
- onClick={() => PbTablesModal.show(props.mode)}
+ onClick={() => showPbTablesModal(props.mode)}
/>
diff --git a/frontend/src/ts/modals/pb-tables.ts b/frontend/src/ts/modals/pb-tables.ts
deleted file mode 100644
index 81d0da3b7426..000000000000
--- a/frontend/src/ts/modals/pb-tables.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import * as DB from "../db";
-import { format } from "date-fns/format";
-import { getLanguageDisplayString } from "../utils/strings";
-import { Config } from "../config/store";
-import Format from "../singletons/format";
-import AnimatedModal from "../utils/animated-modal";
-import { Mode, Mode2, PersonalBest } from "@monkeytype/schemas/shared";
-
-type PBWithMode2 = {
- mode2: Mode2;
-} & PersonalBest;
-
-function update(mode: Mode): void {
- const modalEl = modal.getModal();
-
- const tableEl = modalEl.qs("table");
- tableEl?.qsa("tbody").remove();
- modalEl.qs("thead td:first-child")?.setText(mode);
- modalEl.qs("thead span.unit")?.setText(Config.typingSpeedUnit);
-
- const snapshot = DB.getSnapshot();
- if (!snapshot) return;
-
- const allmode2 = snapshot.personalBests?.[mode] as
- | Record
- | undefined;
-
- if (allmode2 === undefined) return;
-
- const list: PBWithMode2[] = [];
- Object.keys(allmode2).forEach(function (key) {
- let pbs = allmode2[key] ?? [];
- pbs = pbs.sort(function (a, b) {
- return b.wpm - a.wpm;
- });
- pbs.forEach(function (pb) {
- pb.mode2 = key;
- list.push(pb);
- });
- });
-
- let mode2memory: Mode2 | null = null;
- let currentTbody: HTMLTableSectionElement | null = null;
- let rowIndex: number = 1;
-
- list.forEach((pb) => {
- const isNewGroup = mode2memory !== pb.mode2 || currentTbody === null;
- if (isNewGroup) {
- currentTbody = document.createElement("tbody");
- tableEl?.append(currentTbody);
- }
- let dateText = `-
-`;
- const date = new Date(pb.timestamp);
- if (pb.timestamp) {
- dateText = `${format(date, "dd MMM yyyy")}
${format(
- date,
- "HH:mm",
- )}
`;
- }
- currentTbody?.insertAdjacentHTML(
- "beforeend",
- `
-
- ${
- isNewGroup
- ? `
- |
- ${pb.mode2}
- |
- `
- : " | "
- }
-
- ${Format.typingSpeed(pb.wpm)}
-
- ${Format.accuracy(pb.acc)}
- |
-
- ${Format.typingSpeed(pb.raw)}
-
- ${Format.percentage(pb.consistency)}
- |
- ${pb.difficulty} |
- ${pb.language ? getLanguageDisplayString(pb.language) : "-"} |
- ${pb.punctuation ? '' : ""} |
- ${pb.numbers ? '' : ""} |
- ${pb.lazyMode ? '' : ""} |
- ${dateText} |
-
- `,
- );
- mode2memory = pb.mode2;
- rowIndex++;
- });
-}
-
-export function show(mode: Mode): void {
- void modal.show({
- beforeAnimation: async () => {
- update(mode);
- },
- });
-}
-
-const modal = new AnimatedModal({
- dialogId: "pbTablesModal",
-});
diff --git a/frontend/src/ts/states/modals.ts b/frontend/src/ts/states/modals.ts
index 2fc42734ceeb..c6cffb19e40c 100644
--- a/frontend/src/ts/states/modals.ts
+++ b/frontend/src/ts/states/modals.ts
@@ -24,6 +24,7 @@ export type ModalId =
| "ShareTestSettings"
| "CustomWordAmount"
| "MobileTestConfig"
+ | "PbTables"
| "MiniResultChartModal"
| "Cookies"
| "AddPresetModal"
diff --git a/frontend/src/ts/states/pb-tables-modal.ts b/frontend/src/ts/states/pb-tables-modal.ts
new file mode 100644
index 000000000000..c1fe4c8921a6
--- /dev/null
+++ b/frontend/src/ts/states/pb-tables-modal.ts
@@ -0,0 +1,13 @@
+import { createSignal } from "solid-js";
+import { Mode } from "@monkeytype/schemas/shared";
+
+import { showModal } from "./modals";
+
+const [pbTablesMode, setPbTablesMode] = createSignal("time");
+
+export { pbTablesMode };
+
+export function showPbTablesModal(mode: Mode): void {
+ setPbTablesMode(mode);
+ showModal("PbTables");
+}