Skip to content

Commit 249c8bc

Browse files
authored
[Table] use cursor: pointer for clickable headers (#674)
* fix(Table): cursor: pointer for clickable headers * docs(Table): add story with sortable table
1 parent cec0b9e commit 249c8bc

File tree

2 files changed

+52
-3
lines changed

2 files changed

+52
-3
lines changed

src/components/Table/Table.stories.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from "react";
1+
import { useEffect, useMemo, useState } from "react";
22

33
import { Meta, StoryObj } from "@storybook/react-vite";
44

@@ -94,3 +94,44 @@ export const Selectable: StoryObj<typeof Table> = {
9494
);
9595
},
9696
};
97+
98+
export const Sortable: StoryObj<typeof Table> = {
99+
args: {
100+
headers,
101+
rows,
102+
},
103+
render: ({ rows, headers, ...props }) => {
104+
const [sort, setSort] = useState<[number, "asc" | "desc"]>([0, "asc"]);
105+
106+
const sortedHeaders = useMemo(
107+
() =>
108+
headers.map((header, headerIndex) => ({
109+
...header,
110+
isSortable: true,
111+
sortDir: sort[0] === headerIndex ? sort[1] : undefined,
112+
})),
113+
[headers, sort]
114+
);
115+
116+
const sortedRows = useMemo(
117+
() =>
118+
[...rows].sort((a, b) => {
119+
const [cellIdx, sortDir] = sort;
120+
const cellA = a.items[cellIdx]?.label?.toString() || "";
121+
const cellB = b.items[cellIdx]?.label?.toString() || "";
122+
const result = cellA.localeCompare(cellB, "en", { numeric: true });
123+
return sortDir === "asc" ? result : -result;
124+
}),
125+
[rows, sort]
126+
);
127+
128+
return (
129+
<Table
130+
{...props}
131+
headers={sortedHeaders}
132+
rows={sortedRows}
133+
onSort={(dir, _, idx) => void setSort([idx, dir])}
134+
/>
135+
);
136+
},
137+
};

src/components/Table/Table.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ const StyledHeader = styled.th<{ $size: TableSize }>`
3434
text-align: left;
3535
`;
3636

37-
const HeaderContentWrapper = styled.div`
37+
const HeaderContentWrapper = styled.div<{ $interactive: boolean }>`
3838
display: flex;
3939
align-items: center;
4040
justify-content: start;
4141
gap: inherit;
42+
43+
${({ $interactive }) => $interactive && "cursor: pointer;"}
4244
`;
4345

4446
const SortIcon = styled(Icon)<{ $sortDir: SortDir }>`
@@ -57,6 +59,9 @@ const TableHeader = ({
5759
...delegated
5860
}: Omit<TableHeaderType, "width"> & { onSort?: () => void; size: TableSize }) => {
5961
const isSorted = typeof sortDir === "string";
62+
const isInteractive = Boolean(
63+
typeof onClick === "function" || (isSortable && typeof onSort === "function")
64+
);
6065
const onHeaderClick = (e: MouseEvent<HTMLTableCellElement>): void => {
6166
if (typeof onClick === "function") {
6267
onClick(e);
@@ -70,7 +75,10 @@ const TableHeader = ({
7075
$size={size}
7176
{...delegated}
7277
>
73-
<HeaderContentWrapper onClick={onHeaderClick}>
78+
<HeaderContentWrapper
79+
onClick={onHeaderClick}
80+
$interactive={isInteractive}
81+
>
7482
{isSorted && isSortable && sortPosition == "start" && (
7583
<SortIcon
7684
$sortDir={sortDir}

0 commit comments

Comments
 (0)