diff --git a/package-lock.json b/package-lock.json index 4c36969bae..768b949d78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@faker-js/faker": "^8.1.0", "@mui/icons-material": "^5.14.7", "@mui/material": "^5.14.7", "@sillsdev/scripture": "^1.4.0", + "@tanstack/react-table": "^8.10.3", "async-mutex": "^0.4.0", "chalk": "^4.1.2", "chokidar": "^3.5.3", @@ -3385,6 +3387,21 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@faker-js/faker": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.1.0.tgz", + "integrity": "sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, "node_modules/@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", @@ -7784,6 +7801,37 @@ "node": ">=10" } }, + "node_modules/@tanstack/react-table": { + "version": "8.10.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.10.3.tgz", + "integrity": "sha512-Qya1cJ+91arAlW7IRDWksRDnYw28O446jJ/ljkRSc663EaftJoBCAU10M+VV1K6MpCBLrXq1BD5IQc1zj/ZEjA==", + "dependencies": { + "@tanstack/table-core": "8.10.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.10.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.10.3.tgz", + "integrity": "sha512-hJ55YfJlWbfzRROfcyA/kC1aZr/shsLA8XNAwN8jXylhYWGLnPmiJJISrUfj4dMMWRiFi0xBlnlC7MLH+zSrcw==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@teamsupercell/typings-for-css-modules-loader": { "version": "2.5.2", "dev": true, @@ -28246,6 +28294,11 @@ "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", "dev": true }, + "@faker-js/faker": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.1.0.tgz", + "integrity": "sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ==" + }, "@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", @@ -31122,6 +31175,19 @@ "defer-to-connect": "^2.0.0" } }, + "@tanstack/react-table": { + "version": "8.10.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.10.3.tgz", + "integrity": "sha512-Qya1cJ+91arAlW7IRDWksRDnYw28O446jJ/ljkRSc663EaftJoBCAU10M+VV1K6MpCBLrXq1BD5IQc1zj/ZEjA==", + "requires": { + "@tanstack/table-core": "8.10.3" + } + }, + "@tanstack/table-core": { + "version": "8.10.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.10.3.tgz", + "integrity": "sha512-hJ55YfJlWbfzRROfcyA/kC1aZr/shsLA8XNAwN8jXylhYWGLnPmiJJISrUfj4dMMWRiFi0xBlnlC7MLH+zSrcw==" + }, "@teamsupercell/typings-for-css-modules-loader": { "version": "2.5.2", "dev": true, diff --git a/package.json b/package.json index b68c1cfae6..452a412286 100644 --- a/package.json +++ b/package.json @@ -91,9 +91,11 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@faker-js/faker": "^8.1.0", "@mui/icons-material": "^5.14.7", "@mui/material": "^5.14.7", "@sillsdev/scripture": "^1.4.0", + "@tanstack/react-table": "^8.10.3", "async-mutex": "^0.4.0", "chalk": "^4.1.2", "chokidar": "^3.5.3", diff --git a/src/renderer/components/basic-list/basic-list.component.scss b/src/renderer/components/basic-list/basic-list.component.scss new file mode 100644 index 0000000000..904baed9dd --- /dev/null +++ b/src/renderer/components/basic-list/basic-list.component.scss @@ -0,0 +1,11 @@ +.basic-list-table { + overflow: auto; + + .table-header { + text-align: left; + } +} + +.basic-list-expand-button { + cursor: pointer; +} diff --git a/src/renderer/components/basic-list/basic-list.component.tsx b/src/renderer/components/basic-list/basic-list.component.tsx new file mode 100644 index 0000000000..54deb4483a --- /dev/null +++ b/src/renderer/components/basic-list/basic-list.component.tsx @@ -0,0 +1,167 @@ +import { useState } from 'react'; +import { SavedTabInfo, TabInfo } from '@shared/data/web-view.model'; +import { + ExpandedState, + useReactTable, + getCoreRowModel, + getExpandedRowModel, + ColumnDef, + flexRender, +} from '@tanstack/react-table'; +import { Canon } from '@sillsdev/scripture'; +import { faker } from '@faker-js/faker'; + +import './basic-list.component.scss'; + +export const TAB_TYPE_BASIC_LIST = 'basic-list'; + +type CheckResult = { + book: string; + chapter?: number; + verse?: number; + issueDescription: string; + subRows?: CheckResult[]; +}; + +const newCheckResult = (bookId: string): CheckResult => ({ + book: Canon.bookIdToEnglishName(bookId), + chapter: faker.number.int(40), + verse: faker.number.int(40), + issueDescription: 'Basic check issue description', +}); + +const makeMockCheckResults = () => { + return Canon.allBookIds.map((bookId) => { + const numberOfIssues: number = faker.number.int({ min: 1, max: 10 }); + const bookResults: CheckResult = { + book: Canon.bookIdToEnglishName(bookId), + issueDescription: `${numberOfIssues} issues`, + }; + const subResults: CheckResult[] = []; + for (let i = 0; i < numberOfIssues; i++) { + subResults.push(newCheckResult(bookId)); + } + bookResults.subRows = subResults; + return bookResults; + }); +}; + +const columns: ColumnDef[] = [ + { + header: 'Scripture reference', + columns: [ + { + accessorKey: 'book', + header: ({ table }) => ( + <> + {' '} + Book + + ), + cell: ({ row, getValue }) => ( +
+ {row.getCanExpand() && ( + + )}{' '} + {getValue()} +
+ ), + }, + { + accessorFn: (row) => row.chapter, + id: 'chapter', + cell: (info) => info.getValue(), + header: ({ table }) => <> {table.getIsSomeRowsExpanded() && Chapter} , + }, + { + accessorFn: (row) => row.verse, + id: 'verse', + cell: (info) => info.getValue(), + header: ({ table }) => <> {table.getIsSomeRowsExpanded() && Verse} , + }, + ], + }, + { + header: 'Issue', + columns: [ + { + accessorKey: 'issueDescription', + header: () => 'Description', + footer: (props) => props.column.id, + }, + ], + }, +]; + +export default function BasicList() { + const [expanded, setExpanded] = useState({}); + const [data] = useState(() => makeMockCheckResults()); + const table = useReactTable({ + data, + columns, + state: { + expanded, + }, + onExpandedChange: setExpanded, + getSubRows: (row) => row.subRows, + getCoreRowModel: getCoreRowModel(), + getExpandedRowModel: getExpandedRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getVisibleCells().map((cell) => { + return ( + + ); + })} + + ); + })} + +
+ {header.isPlaceholder ? null : ( +
{flexRender(header.column.columnDef.header, header.getContext())}
+ )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ); +} + +export function loadBasicListTab(savedTabInfo: SavedTabInfo): TabInfo { + return { + ...savedTabInfo, + tabTitle: 'Basic List', + content: , + }; +} diff --git a/src/renderer/components/docking/platform-dock-layout.component.tsx b/src/renderer/components/docking/platform-dock-layout.component.tsx index e7ca5663f4..63e2751ff2 100644 --- a/src/renderer/components/docking/platform-dock-layout.component.tsx +++ b/src/renderer/components/docking/platform-dock-layout.component.tsx @@ -64,6 +64,10 @@ import { TAB_TYPE_RUN_BASIC_CHECKS, loadRunBasicChecksTab, } from '@renderer/components/run-basic-checks-dialog/run-basic-checks-tab.component'; +import { + TAB_TYPE_BASIC_LIST, + loadBasicListTab, +} from '@renderer/components/basic-list/basic-list.component'; import { hasDialogRequest, resolveDialogRequest } from '@renderer/services/dialog.service-host'; import { DialogData } from '@shared/models/dialog-options.model'; import DIALOGS from '@renderer/components/dialogs'; @@ -109,6 +113,7 @@ const tabLoaderMap = new Map([ [TAB_TYPE_OPEN_MULTIPLE_PROJECTS_DIALOG, loadOpenMultipleProjectsTab], [TAB_TYPE_EXTENSION_MANAGER, loadExtensionManagerTab], [TAB_TYPE_RUN_BASIC_CHECKS, loadRunBasicChecksTab], + [TAB_TYPE_BASIC_LIST, loadBasicListTab], ...Object.entries(DIALOGS).map( ([dialogTabType, dialogDefinition]) => // The default implementation of `loadDialog` uses `this`, so bind it to the definition diff --git a/src/renderer/testing/test-layout.data.ts b/src/renderer/testing/test-layout.data.ts index 8b74defbb5..7dcebd1c41 100644 --- a/src/renderer/testing/test-layout.data.ts +++ b/src/renderer/testing/test-layout.data.ts @@ -8,6 +8,7 @@ import { TAB_TYPE_TEST } from '@renderer/testing/test-panel.component'; // import { TAB_TYPE_OPEN_MULTIPLE_PROJECTS_DIALOG } from '@renderer/components/project-dialogs/open-multiple-projects-tab.component'; // import { TAB_TYPE_EXTENSION_MANAGER } from '@renderer/components/extension-manager/extension-manager-tab.component'; import { TAB_TYPE_RUN_BASIC_CHECKS } from '@renderer/components/run-basic-checks-dialog/run-basic-checks-tab.component'; +import { TAB_TYPE_BASIC_LIST } from '@renderer/components/basic-list/basic-list.component'; export const FIRST_TAB_ID = 'About'; @@ -31,6 +32,9 @@ const testLayout: LayoutBase = { { tabs: [{ id: 'Test Buttons', tabType: TAB_TYPE_BUTTONS }] as SavedTabInfo[], }, + { + tabs: [{ id: 'Basic List', tabType: TAB_TYPE_BASIC_LIST }] as SavedTabInfo[], + }, ], }, floatbox: {