Last active
October 26, 2024 07:28
-
-
Save TrevorBenson/20268a35b213a766e753ca62eea60288 to your computer and use it in GitHub Desktop.
Patternfly web-component for sortable table with attributes of headers, rows, sort-index and sort-direction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import './App.css'; | |
import React from 'react'; | |
import { Table, Thead, Tr, Th, Tbody, Td, ThProps } from '@patternfly/react-table'; | |
import r2wc from "@r2wc/react-to-web-component"; | |
interface TableRow { | |
[key: string]: string | number; | |
}; | |
interface TableSortableProps { | |
headers: string; // JSON string representing an array of header objects | |
rows: string; // JSON string representing an array of row objects | |
sortIndex?: string | undefined; // JSON string representing the initial sort index | |
sortDirection?: string | undefined; // JSON string representing the initial sort direction | |
} | |
export const TableSortable: React.FunctionComponent<TableSortableProps> = React.memo(({ headers, rows, sortIndex, sortDirection }) => { | |
let headersJson: string[]; | |
let rowsJson: (string | number)[][]; | |
try { | |
headersJson = headers ? JSON.parse(headers) : []; | |
rowsJson = rows ? JSON.parse(rows) : []; | |
} catch (error) { | |
console.error('Error parsing JSON props:', error); | |
headersJson = []; | |
rowsJson = []; | |
} | |
const initialSortIndex = sortIndex !== undefined && !isNaN(Number(sortIndex)) ? JSON.parse(sortIndex) : 0; | |
const [activeSortIndex, setActiveSortIndex] = React.useState<number>(initialSortIndex); | |
const initialSortDirection = sortDirection === 'asc' || sortDirection === 'desc' ? sortDirection : 'asc'; | |
const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc'>(initialSortDirection); | |
const getSortableRowValues = (row: TableRow | (string | number)[]): (string | number)[] => { | |
if (Array.isArray(row)) { | |
return row; | |
} else { | |
return headersJson.map(header => row[header.key]); | |
} | |
}; | |
const sortRows = (a: TableRow | (string | number)[], b: TableRow | (string | number)[]): number => { | |
if (activeSortIndex === null) { | |
return 0; // or however you want to handle this case | |
} | |
const aValue = getSortableRowValues(a)[activeSortIndex]; | |
const bValue = getSortableRowValues(b)[activeSortIndex]; | |
if (typeof aValue === 'number') { | |
return activeSortDirection === 'asc' ? (aValue as number) - (bValue as number) : (bValue as number) - (aValue as number); | |
} else { | |
return activeSortDirection === 'asc' ? (aValue as string).localeCompare(bValue as string) : (bValue as string).localeCompare(aValue as string); | |
} | |
}; | |
const handleSort = (_event: React.MouseEvent, index: number, direction: 'asc' | 'desc'): void => { | |
setActiveSortIndex(index); | |
setActiveSortDirection(direction); | |
}; | |
const renderTableWithKeysAndLabels = () => ( | |
<Table aria-label="Sortable table" ouiaId="SortableTable"> | |
<Thead> | |
<Tr> | |
{headersJson.map((header, index) => ( | |
<Th | |
key={header.key} | |
sort={{ | |
sortBy: { | |
index: activeSortIndex, | |
direction: activeSortDirection, | |
defaultDirection: 'asc' | |
}, | |
onSort: (_event, _index, direction) => handleSort(_event, index, direction), | |
columnIndex: index | |
}} | |
> | |
{header.label} | |
</Th> | |
))} | |
</Tr> | |
</Thead> | |
<Tbody> | |
{rowsJson.sort(sortRows).map((row, rowIndex) => ( | |
<Tr key={rowIndex}> | |
{headersJson.map((header, colIndex) => ( | |
<Td key={colIndex}>{row[header.key]}</Td> | |
))} | |
</Tr> | |
))} | |
</Tbody> | |
</Table> | |
); | |
const renderTableWithLists = () => ( | |
<Table aria-label="Sortable table" ouiaId="SortableTable"> | |
<Thead> | |
<Tr> | |
{headersJson.map((header, index) => ( | |
<Th | |
key={index} | |
sort={{ | |
sortBy: { | |
index: activeSortIndex, | |
direction: activeSortDirection, | |
defaultDirection: 'asc' | |
}, | |
onSort: (_event, _index, direction) => handleSort(_event, index, direction), | |
columnIndex: index | |
}} | |
> | |
{header} | |
</Th> | |
))} | |
</Tr> | |
</Thead> | |
<Tbody> | |
{rowsJson.sort(sortRows).map((row, rowIndex) => ( | |
<Tr key={rowIndex}> | |
{row.map((value, colIndex) => ( | |
<Td key={colIndex}>{value}</Td> | |
))} | |
</Tr> | |
))} | |
</Tbody> | |
</Table> | |
); | |
return typeof headersJson[0] === 'string' ? renderTableWithLists() : renderTableWithKeysAndLabels(); | |
}); | |
TableSortable.defaultProps = { | |
headers: '["Default Header 1", "Default Header 2", "Default Header 3"]', | |
rows: '[["Default Row 1 Column 1", "Default Row 1 Column 2", "Default Row 1 Column 3"], ["Default Row 2 Column 1", "Default Row 2 Column 2", "Default Row 2 Column 3"]]' | |
}; | |
export default TableSortable; | |
const PfTableSortableHeadersRows = r2wc(TableSortable, { | |
props: { | |
headers: "string", | |
rows: "string", | |
sortIndex: "string", | |
sortDirection: "string", | |
}, | |
}) | |
customElements.define("pf-table-sortable-hr", PfTableSortableHeadersRows); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""FastAPI application with github-oauth.""" | |
import asyncio | |
import json | |
from os import access # noqa: F401 # pylint: disable=unused-import # type: ignore | |
import httpx | |
from fastapi import Cookie, Depends, FastAPI, HTTPException, Request, Response, Security | |
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse | |
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer | |
from fastapi.staticfiles import StaticFiles | |
from fastapi.templating import Jinja2Templates | |
app = FastAPI() | |
app.mount("/static", StaticFiles(directory="static"), name="static") | |
templates = Jinja2Templates(directory="templates") | |
@app.get("/table", response_class=HTMLResponse) | |
async def table_headers_rows(request: Request): | |
headers = ["Name", "Age", "Country"] | |
rows = [ | |
["John", 28, "USA"], | |
["Alice", 23, "Canada"], | |
["Bob", 31, "UK"], | |
] | |
return templates.TemplateResponse( | |
"index.html", {"request": request, "headers": headers, "rows": rows} | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>Rsbuild App</title> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width,initial-scale=1" /> | |
<script defer="defer" src="/static/js/lib-lodash.741c9a83.js"></script> | |
<script defer="defer" src="/static/js/lib-polyfill.9a13c3e5.js"></script> | |
<script defer="defer" src="/static/js/lib-react.6eeec448.js"></script> | |
<script defer="defer" src="/static/js/320.c03fd9b3.js"></script> | |
<script defer="defer" src="/static/js/index.17b941a7.js"></script> | |
<link href="/static/css/320.9e553c6e.css" rel="stylesheet" /> | |
<link href="/static/css/index.a11cfb11.css" rel="stylesheet" /> | |
</head> | |
<body> | |
<!-- <pf-table-sortable-hr | |
headers='[{"key": "name", "label": "Name"}, {"key": "age", "label": "Age"}, {"key": "birthplace", "label": "Birthplace"}]' | |
rows='[{"name": "Alice", "age": 23, "birthplace": "Santa Cruz, CA"}, {"name": "Bob", "age": 27, "birthplace": "San Diego, CA"}]' | |
sort-index='2'> | |
</pf-table-sortable-hr> --> | |
<pf-table-sortable-hr | |
headers="{{ headers|tojson }}" | |
rows="{{ rows|tojson }}" | |
sort-index="2" | |
> | |
</pf-table-sortable-hr> | |
</body> | |
</html> |
Notes
Scaffold a react typescpt project using:
npm create rsbuild@latest
npm install @r2wc/react-to-web-component @patternfly/react-core @patternfly/react-table --save
- In the App.tsx build your component, or import it from structure files with the compoment(s) defined.
- and follow https://github.com/bitovi/react-to-web-component?tab=readme-ov-file#basic-use to export it as a web component.
- Once done
npm run build
- Take the content of the build js file and import it in your existing app via
<script src="[...]">
, - Render your web component this way:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Overview
Using react-to-web-component / rsbuild to create Patternfly usable web components. This version accepts serialized headers and rows attributes. There is also control over the sort-index and sort-direction.
The headers and rows can be a list of strings, or alist of keys and labels. The Types contained in headers and rows must be in sync.
Building the web component
./src/App.tsx
and add the TypeScipt XMLAlternatively, testing can be performed via
npm run dev
before collecting the JavaScript of the web component.Making the web component accessible to your application
Example for FastAPI
./dist/static/
to the directory for StaticFiles() used with app.mount() for fastapi../dist/index.html
to the directory where you host html files for the application. In the example, this is a directory of Jinja2 templates so the directory used in Jinja2Templates().