-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: use react table to render resources subpage #313
Changes from all commits
a141612
3b345ca
2e43e43
00f5395
a3f8c37
ddf4dda
6a6dacb
182e6a0
ea79c03
b161a11
1ebc22b
ab8d212
d959262
39030cb
56b7c08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
karrui marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import type { LayoutProps, TableProps } from "@chakra-ui/react" | ||
import type { Table as ReactTable } from "@tanstack/react-table" | ||
import { | ||
Box, | ||
Flex, | ||
Spinner, | ||
Table, | ||
Tbody, | ||
Td, | ||
Text, | ||
Th, | ||
Thead, | ||
Tr, | ||
useMultiStyleConfig, | ||
} from "@chakra-ui/react" | ||
import { flexRender } from "@tanstack/react-table" | ||
|
||
import { DatatablePagination } from "./DatatablePagination" | ||
|
||
export interface DatatableProps<D> extends TableProps { | ||
instance: ReactTable<D> | ||
/** | ||
* If provided, this number will be used for pagination instead of retrieving | ||
* from react-table's filtered row count. | ||
*/ | ||
totalRowCount?: number | ||
pagination?: boolean | ||
/** | ||
* If provided, this string will be used to display the total row count. | ||
*/ | ||
totalRowCountString?: string | ||
isFetching?: boolean | ||
emptyPlaceholder?: React.ReactElement | ||
overflow?: LayoutProps["overflow"] | ||
} | ||
|
||
export function createAccessor<T>(props: (keyof T)[]) { | ||
return (row: T): string => { | ||
return props.map((prop) => String(row[prop])).join(" ") | ||
} | ||
} | ||
|
||
export const Datatable = <T extends object>({ | ||
instance, | ||
isFetching, | ||
pagination, | ||
totalRowCount, | ||
totalRowCountString, | ||
emptyPlaceholder, | ||
overflow = "auto", | ||
...tableProps | ||
}: DatatableProps<T>): JSX.Element => { | ||
const { rows } = instance.getRowModel() | ||
const styles = useMultiStyleConfig("Table", tableProps) | ||
|
||
return ( | ||
<Flex flexDirection="column" layerStyle="shadow" pos="relative"> | ||
{isFetching && ( | ||
<> | ||
<Flex | ||
// white alpha to denote loading | ||
bg="whiteAlpha.800" | ||
bottom={0} | ||
left={0} | ||
p="1rem" | ||
pos="absolute" | ||
right={0} | ||
top={0} | ||
zIndex="1" | ||
karrui marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/> | ||
<Flex | ||
bottom={0} | ||
flex={1} | ||
left={0} | ||
pos="fixed" | ||
right={0} | ||
top={0} | ||
w="100vw" | ||
zIndex={2} | ||
karrui marked this conversation as resolved.
Show resolved
Hide resolved
|
||
> | ||
<Box m="auto"> | ||
<Spinner /> | ||
</Box> | ||
</Flex> | ||
</> | ||
)} | ||
Comment on lines
+58
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this component has to care about
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cannot use suspense. We do not want the full fallback. We want to show the previous items in the table still, which suspense will not do (since it will swap out the current rendered table items with the suspense item completely). The loading state is a semi-transparent overlay. |
||
<Box overflow={overflow} sx={styles.container}> | ||
<Table sx={{ tableLayout: "fixed" }} {...tableProps} pos="relative"> | ||
<Thead> | ||
{instance.getHeaderGroups().map((headerGroup) => ( | ||
<Tr | ||
key={headerGroup.id} | ||
// To toggle _groupHover styles to show divider when header is hovered. | ||
data-group | ||
borderBottomWidth="1px" | ||
> | ||
{headerGroup.headers.map((header) => ( | ||
<Th | ||
key={header.id} | ||
pos="relative" | ||
px={0} | ||
style={{ | ||
width: header.getSize(), | ||
}} | ||
> | ||
<Flex align="center"> | ||
{flexRender( | ||
header.column.columnDef.header, | ||
header.getContext(), | ||
)} | ||
</Flex> | ||
</Th> | ||
))} | ||
</Tr> | ||
))} | ||
</Thead> | ||
<Tbody> | ||
{rows.length === 0 && emptyPlaceholder} | ||
{rows.map((row) => { | ||
return ( | ||
<Tr key={row.id} borderBottomWidth="1px"> | ||
{row.getVisibleCells().map((cell) => { | ||
return ( | ||
<Td key={cell.id} verticalAlign="center"> | ||
{flexRender( | ||
cell.column.columnDef.cell, | ||
cell.getContext(), | ||
)} | ||
</Td> | ||
) | ||
})} | ||
</Tr> | ||
) | ||
})} | ||
</Tbody> | ||
</Table> | ||
</Box> | ||
<Flex py="1rem" gap="1rem"> | ||
{totalRowCountString && ( | ||
<Text textStyle="caption-2" color="base.content.medium"> | ||
{totalRowCountString} | ||
</Text> | ||
)} | ||
{pagination && ( | ||
<Flex ml="auto"> | ||
<DatatablePagination | ||
instance={instance} | ||
totalRowCount={totalRowCount} | ||
/> | ||
</Flex> | ||
)} | ||
karrui marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</Flex> | ||
</Flex> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Pagination } from "@opengovsg/design-system-react" | ||
import { type Table } from "@tanstack/react-table" | ||
|
||
export interface DataTablePaginationProps<D> { | ||
instance: Table<D> | ||
totalRowCount?: number | ||
} | ||
|
||
export const DatatablePagination = <T extends object>({ | ||
instance, | ||
totalRowCount: totalRowCountProp, | ||
}: DataTablePaginationProps<T>): JSX.Element => { | ||
const paginationState = instance.getState().pagination | ||
const totalRowCount = | ||
totalRowCountProp ?? instance.getFilteredRowModel().rows.length | ||
|
||
return ( | ||
<Pagination | ||
currentPage={paginationState.pageIndex + 1} | ||
onPageChange={(newPage) => { | ||
instance.setPageIndex(newPage - 1) | ||
}} | ||
pageSize={10} | ||
totalCount={totalRowCount} | ||
/> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Flex, Stack, Td, Text, Tr } from "@chakra-ui/react" | ||
|
||
export const EmptyTablePlaceholder = ({ | ||
entityName, | ||
hasSearchTerm, | ||
}: { | ||
entityName: string | ||
hasSearchTerm: boolean | ||
}) => { | ||
return ( | ||
<Tr aria-hidden> | ||
<Td colSpan={8}> | ||
<Flex align="center" justify="center" p="2rem"> | ||
<Stack align="center" spacing="0.375rem"> | ||
<Text textStyle="subhead-4"> | ||
No {entityName} | ||
{hasSearchTerm ? " found" : ""} | ||
</Text> | ||
{hasSearchTerm && ( | ||
<Text textStyle="body-2">Try different search terms</Text> | ||
)} | ||
</Stack> | ||
</Flex> | ||
</Td> | ||
</Tr> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { memo } from "react" | ||
import NextLink from "next/link" | ||
import { Link, Text, VStack } from "@chakra-ui/react" | ||
|
||
export interface InfoCellProps { | ||
caption?: string | null | ||
subcaption?: string | null | ||
href?: string | ||
} | ||
|
||
export const InfoCell = memo( | ||
karrui marked this conversation as resolved.
Show resolved
Hide resolved
|
||
({ caption, subcaption, href }: InfoCellProps): JSX.Element => { | ||
return ( | ||
<VStack align="start" spacing="0.25rem"> | ||
{href ? ( | ||
<Link | ||
as={NextLink} | ||
color="base.content.strong" | ||
href={href} | ||
textStyle="subhead-2" | ||
> | ||
{caption} | ||
</Link> | ||
) : ( | ||
<Text textStyle="body-2">{caption}</Text> | ||
)} | ||
<Text textStyle="body-2">{subcaption}</Text> | ||
</VStack> | ||
) | ||
}, | ||
(prevProps, nextProps) => { | ||
return ( | ||
prevProps.caption === nextProps.caption && | ||
prevProps.subcaption === nextProps.subcaption | ||
) | ||
}, | ||
) | ||
|
||
InfoCell.displayName = "InfoCell" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import type { TextProps } from "@chakra-ui/react" | ||
import React from "react" | ||
import { Text } from "@chakra-ui/react" | ||
|
||
export const TableCell = ({ children, ...props }: TextProps) => { | ||
return <Text {...props}>{children}</Text> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import type { BoxProps } from "@chakra-ui/react" | ||
import React from "react" | ||
import { Box } from "@chakra-ui/react" | ||
|
||
export const TableHeader = ({ children, ...props }: BoxProps) => { | ||
return ( | ||
<Box px="1rem" {...props}> | ||
{children} | ||
</Box> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from "./Datatable" | ||
export * from "./TableCell" | ||
export * from "./TableHeader" | ||
export * from "./InfoCell" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { MenuItemProps as ChakraMenuItemProps } from "@chakra-ui/react" | ||
import { useMemo } from "react" | ||
import { MenuItem as ChakraMenuItem, cssVar } from "@chakra-ui/react" | ||
|
||
const $bg = cssVar("menu-bg") | ||
|
||
export interface MenuItemProps extends ChakraMenuItemProps { | ||
colorScheme?: "critical" | ||
} | ||
|
||
export const MenuItem = ({ | ||
colorScheme, | ||
...menuItemProps | ||
}: MenuItemProps): JSX.Element => { | ||
// Unable to use useMultiStyleConfig here because Menu parent still controls | ||
// other styles such as size and placement | ||
const extraStyles = useMemo(() => { | ||
if (!colorScheme) return {} | ||
switch (colorScheme) { | ||
case "critical": { | ||
return { | ||
bg: $bg.reference, | ||
color: "interaction.critical.default", | ||
_hover: { | ||
[$bg.variable]: `colors.interaction.muted.critical.hover`, | ||
}, | ||
_focus: { | ||
[$bg.variable]: `colors.interaction.muted.critical.hover`, | ||
_active: { | ||
[$bg.variable]: `colors.interaction.muted.critical.active`, | ||
}, | ||
}, | ||
_focusVisible: { | ||
_active: { | ||
[$bg.variable]: `colors.interaction.muted.critical.active`, | ||
}, | ||
}, | ||
_active: { | ||
[$bg.variable]: `colors.interaction.muted.critical.active`, | ||
}, | ||
} | ||
} | ||
} | ||
}, [colorScheme]) | ||
|
||
return <ChakraMenuItem {...menuItemProps} sx={extraStyles} /> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { MenuItem } from "./MenuItem" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sehyunidaaa - seekign input on this!