Add sort for table
This commit is contained in:
parent
65465d813d
commit
d7cd3c56fd
|
@ -1,30 +1,100 @@
|
||||||
import "./table.css";
|
import "./table.css";
|
||||||
import { Search } from "lucide-react";
|
import { ArrowDownUp, Search } from "lucide-react";
|
||||||
import { Row, RowProps } from "./Row";
|
import { Row, RowProps } from "./Row";
|
||||||
import { Fragment, ReactElement } from "react";
|
import { Fragment, ReactElement, useState } from "react";
|
||||||
|
import { classnames } from "../utils/classnames";
|
||||||
|
|
||||||
|
type State = "asc" | "desc" | "";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
/**
|
/**
|
||||||
* List of header names
|
* List of header names
|
||||||
|
* Can be a string array ["id", "actions"]
|
||||||
|
* Or a tuple containing the sort function with the column
|
||||||
|
* index in argument
|
||||||
*/
|
*/
|
||||||
headers: string[];
|
headers: string[] | [string, ((state: State) => void)?][];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default: -1
|
||||||
|
*/
|
||||||
|
defaultSortIndex?: number;
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
||||||
rows: ReactElement<RowProps, typeof Row>[];
|
rows: ReactElement<RowProps, typeof Row>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Table({ headers, rows, className = "" }: Props) {
|
const nextState = (state: "asc" | "desc" | "") => {
|
||||||
|
switch (state) {
|
||||||
|
case "":
|
||||||
|
return "desc";
|
||||||
|
case "asc":
|
||||||
|
return "";
|
||||||
|
case "desc":
|
||||||
|
return "asc";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Table({
|
||||||
|
headers,
|
||||||
|
rows,
|
||||||
|
defaultSortIndex = -1,
|
||||||
|
className = "",
|
||||||
|
}: Props) {
|
||||||
|
const [sortSelected, setSortSelected] = useState([defaultSortIndex, "asc"]);
|
||||||
|
|
||||||
|
const onFilterSelected = (col: number) => {
|
||||||
|
const [currentCol, currentState] = sortSelected;
|
||||||
|
|
||||||
|
if (col !== currentCol) {
|
||||||
|
setSortSelected([col, "desc"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nxt = nextState(currentState as State);
|
||||||
|
|
||||||
|
if (nxt === "") {
|
||||||
|
setSortSelected([-1, ""]);
|
||||||
|
} else {
|
||||||
|
setSortSelected([col, nxt]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`table-container ${className}`}>
|
<div className={`table-container ${className}`}>
|
||||||
<table className={"table"}>
|
<table className={"table"}>
|
||||||
<thead className="table-thead">
|
<thead className="table-thead">
|
||||||
<tr className="table-theadTr">
|
<tr className="table-theadTr">
|
||||||
{headers.map((col) => (
|
{headers.map((col, index) => {
|
||||||
<th className="table-theadTh" key={col}>
|
const [name, sort] = Array.isArray(col) ? col : [col];
|
||||||
{col}
|
const state = index === sortSelected[0] ? sortSelected[1] : "";
|
||||||
</th>
|
const nxt = nextState(state as State);
|
||||||
))}
|
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
className={classnames(
|
||||||
|
["table-theadTh"],
|
||||||
|
["table-theadTh--clickable", !!sort]
|
||||||
|
)}
|
||||||
|
key={name}
|
||||||
|
onClick={() => {
|
||||||
|
onFilterSelected(index);
|
||||||
|
sort?.(nxt);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="table-theadTh-content">
|
||||||
|
<span>{name}</span>
|
||||||
|
{sort && (
|
||||||
|
<ArrowDownUp
|
||||||
|
className={"table-theadTh-icon--" + state}
|
||||||
|
size={"1rem"}
|
||||||
|
></ArrowDownUp>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: var(--codex-border-radius);
|
border-radius: var(--codex-border-radius);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-theadTr {
|
.table-theadTr {
|
||||||
|
@ -36,3 +37,34 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-theadTh--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-theadTh-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-theadTh-content svg {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-theadTh-content svg path {
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.35s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-theadTh-icon--desc path:nth-child(1),
|
||||||
|
.table-theadTh-icon--desc path:nth-child(2) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-theadTh-icon--asc path:nth-child(3),
|
||||||
|
.table-theadTh-icon--asc path:nth-child(4) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
|
@ -50,6 +50,31 @@ export const Scroll: Story = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Sort: Story = {
|
||||||
|
args: {
|
||||||
|
className: "tableSmall",
|
||||||
|
rows: [
|
||||||
|
<Row
|
||||||
|
cells={[
|
||||||
|
<Cell>Ox45678FDGHJKLBSA22</Cell>,
|
||||||
|
<Cell>My file</Cell>,
|
||||||
|
<Cell>1</Cell>,
|
||||||
|
<Cell>Some data</Cell>,
|
||||||
|
]}
|
||||||
|
></Row>,
|
||||||
|
<Row
|
||||||
|
cells={[
|
||||||
|
<Cell>Ox45678FDGHJKLBSA23</Cell>,
|
||||||
|
<Cell>My file</Cell>,
|
||||||
|
<Cell>2</Cell>,
|
||||||
|
<Cell>Some data</Cell>,
|
||||||
|
]}
|
||||||
|
></Row>,
|
||||||
|
],
|
||||||
|
headers: [["id"], ["title"], ["other", () => {}], ["actions"]],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const Empty: Story = {
|
export const Empty: Story = {
|
||||||
args: {
|
args: {
|
||||||
rows: [],
|
rows: [],
|
||||||
|
|
Loading…
Reference in New Issue