-
Notifications
You must be signed in to change notification settings - Fork 142
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
Adds Pagination to Data Table #1367
Changes from 9 commits
6019b4a
27228eb
cac66f9
da01366
5229ca7
3ff82f2
12401f9
d1377fa
068d1d8
aa51a5a
aef334d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ export interface Props { | |
/** A list of objects with two fields: `label`, which is a string representing the column header, and `value`, which can be a string, or a function that extracts the data needed to fill the table cell. */ | ||
fields: { | ||
label: string; | ||
displayLabel: string; | ||
value: string | ((k: any) => string | JSX.Element); | ||
}[]; | ||
/** A list of data that will be iterated through to create the columns described in `fields`. */ | ||
|
@@ -30,10 +31,11 @@ export interface Props { | |
sortFields: string[]; | ||
/** an optional list of string widths for each field/column. */ | ||
widths?: string[]; | ||
/** for passing pagination */ | ||
children?: any; | ||
} | ||
|
||
const EmptyRow = styled(TableRow)<{ colSpan: number }>` | ||
font-style: italic; | ||
td { | ||
text-align: center; | ||
} | ||
|
@@ -46,15 +48,14 @@ const TableButton = styled(Button)` | |
text-transform: none; | ||
} | ||
&.MuiButton-text { | ||
color: ${(props) => props.theme.colors.neutral30}; | ||
min-width: 0px; | ||
.selected { | ||
color: ${(props) => props.theme.colors.neutral40}; | ||
} | ||
} | ||
&.arrow { | ||
min-width: 0px; | ||
} | ||
&.selected { | ||
color: ${(props) => props.theme.colors.neutral40}; | ||
} | ||
`; | ||
|
||
/** Form DataTable */ | ||
|
@@ -64,6 +65,7 @@ function UnstyledDataTable({ | |
rows, | ||
sortFields, | ||
widths, | ||
children, | ||
}: Props) { | ||
const [sort, setSort] = React.useState(sortFields[0]); | ||
const [reverseSort, setReverseSort] = React.useState(false); | ||
|
@@ -73,22 +75,22 @@ function UnstyledDataTable({ | |
sorted.reverse(); | ||
} | ||
|
||
type labelProps = { label: string }; | ||
function SortableLabel({ label }: labelProps) { | ||
type labelProps = { label: string; displayLabel: string }; | ||
function SortableLabel({ label, displayLabel }: labelProps) { | ||
return ( | ||
<Flex align start> | ||
<TableButton | ||
color="inherit" | ||
variant="text" | ||
onClick={() => { | ||
setReverseSort(sort === label.toLowerCase() ? !reverseSort : false); | ||
setSort(label.toLowerCase()); | ||
setReverseSort(sort === label ? !reverseSort : false); | ||
setSort(label); | ||
}} | ||
> | ||
<h2>{label}</h2> | ||
<h2 className={sort === label ? "selected" : ""}>{displayLabel}</h2> | ||
</TableButton> | ||
<Spacer padding="xxs" /> | ||
{sort === label.toLowerCase() ? ( | ||
{sort === label ? ( | ||
<Icon | ||
type={IconType.ArrowUpwardIcon} | ||
size="base" | ||
|
@@ -119,10 +121,13 @@ function UnstyledDataTable({ | |
<TableRow> | ||
{_.map(fields, (f, i) => ( | ||
<TableCell style={widths && { width: widths[i] }} key={f.label}> | ||
{sortFields.includes(f.label.toLowerCase()) ? ( | ||
<SortableLabel label={f.label} /> | ||
{sortFields.includes(f.label) ? ( | ||
<SortableLabel | ||
label={f.label} | ||
displayLabel={f.displayLabel} | ||
/> | ||
) : ( | ||
<h2 className="thead">{f.label}</h2> | ||
<h2>{f.displayLabel}</h2> | ||
)} | ||
</TableCell> | ||
))} | ||
|
@@ -134,24 +139,41 @@ function UnstyledDataTable({ | |
) : ( | ||
<EmptyRow colSpan={fields.length}> | ||
<TableCell colSpan={fields.length}> | ||
<span style={{ fontStyle: "italic" }}>No rows</span> | ||
<Flex center align> | ||
<Icon | ||
color="neutral20" | ||
type={IconType.RemoveCircleIcon} | ||
size="base" | ||
/> | ||
<Spacer padding="xxs" /> | ||
<Text color="neutral30">No data</Text> | ||
</Flex> | ||
</TableCell> | ||
</EmptyRow> | ||
)} | ||
</TableBody> | ||
</Table> | ||
</TableContainer> | ||
<Spacer padding="xs" /> | ||
{/* optional pagination component */} | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
export const DataTable = styled(UnstyledDataTable)` | ||
h2 { | ||
font-size: 14px; | ||
font-weight: 600; | ||
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. I have played around with this in the inspector and for me this only renders bold (> 600) or regular (<600) – is semi-bold Proxima Nova not getting imported somewhere? 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. 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. It DEFINITELY changes but it is subtle for sure |
||
color: ${(props) => props.theme.colors.neutral30}; | ||
margin: 0px; | ||
} | ||
.thead { | ||
color: ${(props) => props.theme.colors.neutral30}; | ||
font-weight: 800; | ||
.MuiTableRow-root { | ||
transition: background 0.5s ease-in-out; | ||
} | ||
.MuiTableRow-root:not(.MuiTableRow-head):hover { | ||
background: ${(props) => props.theme.colors.neutral10}; | ||
transition: background 0.5s ease-in-out; | ||
} | ||
`; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,11 @@ type Props = { | |
}; | ||
|
||
function Link({ children, href, className, to = "", newTab, ...props }: Props) { | ||
const txt = <Text color="primary">{children}</Text>; | ||
const txt = ( | ||
<Text size="small" color="primary"> | ||
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. We want to change all |
||
{children} | ||
</Text> | ||
); | ||
|
||
if (href) { | ||
return ( | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,118 @@ | ||||||
import { FormControl, MenuItem, Select } from "@material-ui/core"; | ||||||
import * as React from "react"; | ||||||
import styled from "styled-components"; | ||||||
import Button from "./Button"; | ||||||
import Flex from "./Flex"; | ||||||
import Icon, { IconType } from "./Icon"; | ||||||
import Spacer from "./Spacer"; | ||||||
import Text from "./Text"; | ||||||
|
||||||
export interface Props { | ||||||
/** CSS MUI Overrides or other styling. */ | ||||||
className?: string; | ||||||
/** func for forward one page button */ | ||||||
onForward: () => void; | ||||||
/** func for skip to last page button */ | ||||||
onSkipForward: () => void; | ||||||
/** func for back one page button */ | ||||||
onBack: () => void; | ||||||
/** func for skip to start button */ | ||||||
onSkipBack: () => void; | ||||||
/** onChange func for perPage select */ | ||||||
onSelect: (value) => void; | ||||||
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. Let's give this 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. it's an event, and it needs to always be an event right? Is there a way to express that in Typescript? 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. Something like
Suggested change
You can also just leave it blank if you don't expect the user to do anything with the arg. |
||||||
/** options for perPage select */ | ||||||
perPageOptions?: number[]; | ||||||
/** starting index */ | ||||||
index: number; | ||||||
/** total rows */ | ||||||
length: number; | ||||||
/** all objects */ | ||||||
totalObjects: number; | ||||||
} | ||||||
|
||||||
function unstyledPagination({ | ||||||
className, | ||||||
onForward, | ||||||
onSkipForward, | ||||||
onBack, | ||||||
onSkipBack, | ||||||
onSelect, | ||||||
perPageOptions = [25, 50, 75, 100], | ||||||
index, | ||||||
length, | ||||||
totalObjects, | ||||||
}: Props) { | ||||||
return ( | ||||||
<Flex wide align end className={className}> | ||||||
<FormControl> | ||||||
<Flex align> | ||||||
<label htmlFor="pagination">Rows Per Page: </label> | ||||||
<Spacer padding="xxs" /> | ||||||
<Select | ||||||
id="pagination" | ||||||
variant="outlined" | ||||||
defaultValue={perPageOptions[0]} | ||||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => { | ||||||
onSelect(e.target.value); | ||||||
}} | ||||||
> | ||||||
{perPageOptions.map((option, index) => { | ||||||
return ( | ||||||
<MenuItem key={index} value={option}> | ||||||
{option} | ||||||
</MenuItem> | ||||||
); | ||||||
})} | ||||||
</Select> | ||||||
</Flex> | ||||||
</FormControl> | ||||||
<Spacer padding="base" /> | ||||||
<Text> | ||||||
{index + 1} - {index + length} out of {totalObjects} | ||||||
</Text> | ||||||
<Spacer padding="base" /> | ||||||
<Flex> | ||||||
<Button | ||||||
color="inherit" | ||||||
variant="text" | ||||||
aria-label="skip to first page" | ||||||
disabled={index === 0} | ||||||
onClick={() => onSkipBack()} | ||||||
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.
Suggested change
|
||||||
> | ||||||
<Icon type={IconType.SkipPreviousIcon} size="medium" /> | ||||||
</Button> | ||||||
<Button | ||||||
color="inherit" | ||||||
variant="text" | ||||||
aria-label="back one page" | ||||||
disabled={index === 0} | ||||||
onClick={() => onBack()} | ||||||
> | ||||||
<Icon type={IconType.NavigateBeforeIcon} size="medium" /> | ||||||
</Button> | ||||||
<Button | ||||||
color="inherit" | ||||||
variant="text" | ||||||
aria-label="forward one page" | ||||||
disabled={index + length >= totalObjects} | ||||||
onClick={() => onForward()} | ||||||
> | ||||||
<Icon type={IconType.NavigateNextIcon} size="medium" /> | ||||||
</Button> | ||||||
<Button | ||||||
color="inherit" | ||||||
variant="text" | ||||||
aria-label="skip to last page" | ||||||
disabled={index + length >= totalObjects} | ||||||
onClick={() => onSkipForward()} | ||||||
> | ||||||
<Icon type={IconType.SkipNextIcon} size="medium" /> | ||||||
</Button> | ||||||
</Flex> | ||||||
</Flex> | ||||||
); | ||||||
} | ||||||
|
||||||
export const Pagination = styled(unstyledPagination)``; | ||||||
|
||||||
export default Pagination; |
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.
There shouldn't be a
label
anddisplayLabel
prop here;label
is already mean to give control over what gets displayed.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.
For the _.sort function to work the labels have to be all lowercase to match the fields on the rows which meant of lot of string.toLowerCase()-ing which I h8ed.
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.
I would go with
string.toLowerCase()
myself. As it is in this PR, it is a leaky abstraction, and you are asking the user to think about how the sorting logic works.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.
The only other thing that's coming to mind is if someone wants to sort columns with a label that won't match when it's shifted to lowercase. Like "Last Commit" with
last_commit
as the key. It makes those columns not sortable right?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.
You can do stuff like
_.lowerCase(_.snakeCase(string))
if you want to normalize the values. I would have to see the full filtering implementation to say for sure.