Skip to content

Commit

Permalink
feat(Table): better sort indicator when a column is sorted (#507)
Browse files Browse the repository at this point in the history
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added an active state feature to button icons, enhancing visual
feedback when buttons are active.

- **Bug Fixes**
- Improved sorting functionality in tables by correctly setting active
states.

- **Tests**
	- Implemented new tests for active state styling of button icons.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
aversini authored Apr 16, 2024
1 parent ebac848 commit e52ca48
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 43 deletions.
134 changes: 104 additions & 30 deletions packages/documentation/src/Components/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,71 @@ const data = [
},
];

const nutritionData = [
{ id: 1, name: "Cupcake", calories: 305, fat: 3.7, carbs: 67, protein: 4.3 },
{ id: 2, name: "Donut", calories: 452, fat: 25.0, carbs: 51, protein: 4.9 },
{ id: 3, name: "Eclair", calories: 262, fat: 16.0, carbs: 24, protein: 6.0 },
{
id: 4,
name: "Frozen yoghurt",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
},
{
id: 5,
name: "Gingerbread",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
},
{
id: 6,
name: "Honeycomb",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
},
{
id: 7,
name: "Ice cream sandwich",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
},
{
id: 8,
name: "Jelly Bean",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
},
{ id: 9, name: "KitKat", calories: 518, fat: 26.0, carbs: 65, protein: 7.0 },
{
id: 10,
name: "Lollipop",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
},
{
id: 11,
name: "Marshmallow",
calories: 318,
fat: 0,
carbs: 81,
protein: 2.0,
},
{ id: 12, name: "Nougat", calories: 360, fat: 19.0, carbs: 9, protein: 37.0 },
{ id: 13, name: "Oreo", calories: 437, fat: 18.0, carbs: 63, protein: 4.0 },
];

export const Basic: Story<any> = (args) => {
return (
<div className="min-h-10">
Expand Down Expand Up @@ -265,35 +330,31 @@ export const WithRowNumbers: Story<any> = (args) => {

export const Sortable: Story<any> = (args) => {
const [sortState, setSortState] = useState<{
cell: string;
cell: string | number;
direction:
| typeof TableCellSortDirections.ASC
| typeof TableCellSortDirections.DESC
| false;
}>({ direction: false, cell: "" });

const sortedData = data.sort((a, b) => {
const sortedData = nutritionData.sort((a, b) => {
switch (sortState.cell) {
case "actor":
case "character":
case "name":
if (sortState.direction === TableCellSortDirections.ASC) {
return a[sortState.cell].localeCompare(b[sortState.cell]);
} else if (sortState.direction === TableCellSortDirections.DESC) {
return b[sortState.cell].localeCompare(a[sortState.cell]);
}
break;

case "timestamp":
case "calories":
case "fat":
case "carbs":
case "protein":
if (sortState.direction === TableCellSortDirections.ASC) {
return (
new Date(a[sortState.cell]).getTime() -
new Date(b[sortState.cell]).getTime()
);
return a[sortState.cell] - b[sortState.cell];
} else if (sortState.direction === TableCellSortDirections.DESC) {
return (
new Date(b[sortState.cell]).getTime() -
new Date(a[sortState.cell]).getTime()
);
return b[sortState.cell] - a[sortState.cell];
}
break;

Expand All @@ -320,52 +381,65 @@ export const Sortable: Story<any> = (args) => {
return (
<div className="min-h-10">
<div className="flex flex-wrap gap-2">
<Table caption="Dune" {...args}>
<Table caption="Nutrition Facts" {...args}>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCellSort
scope="col"
cellId="timestamp"
align="left"
cellId="calories"
align="right"
sortDirection={sortState.direction}
sortedCell={sortState.cell}
onClick={() => {
onClickSort("timestamp");
onClickSort("calories");
}}
>
Date
Calories
</TableCellSort>
<TableCellSort
cellId="character"
align="left"
cellId="fat"
align="right"
sortDirection={sortState.direction}
sortedCell={sortState.cell}
onClick={() => {
onClickSort("character");
onClickSort("fat");
}}
>
Character
Fat
</TableCellSort>
<TableCellSort
cellId="actor"
align="left"
cellId="carbs"
align="right"
sortDirection={sortState.direction}
sortedCell={sortState.cell}
onClick={() => {
onClickSort("actor");
onClickSort("carbs");
}}
>
Actor
Carbs
</TableCellSort>
<TableCellSort
cellId="protein"
align="right"
sortDirection={sortState.direction}
sortedCell={sortState.cell}
onClick={() => {
onClickSort("protein");
}}
>
Protein
</TableCellSort>
</TableRow>
</TableHead>

<TableBody>
{sortedData.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.timestamp}</TableCell>
<TableCell>{row.character}</TableCell>
<TableCell>{row.actor}</TableCell>
<TableCell>{row.name}</TableCell>
<TableCell className="text-right">{row.calories}</TableCell>
<TableCell className="text-right">{row.fat}</TableCell>
<TableCell className="text-right">{row.carbs}</TableCell>
<TableCell className="text-right">{row.protein}</TableCell>
</TableRow>
))}
</TableBody>
Expand Down
46 changes: 34 additions & 12 deletions packages/ui-components/src/components/Button/ButtonIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const ButtonIcon = React.forwardRef<HTMLButtonElement, ButtonIconProps>(
spacing,
noBackground = false,
align = "center",
active = false,

...otherProps
},
Expand Down Expand Up @@ -53,19 +54,40 @@ export const ButtonIcon = React.forwardRef<HTMLButtonElement, ButtonIconProps>(
"text-copy-light dark:text-copy-accent-dark": mode === "system" && !raw,
});

const activeClass = clsx(
"focus-within:static",
"after:absolute",
"after:content-['']",
"after:border-b-2",
"after:z-[-1px]",
"after:bottom-[-4px]",
"after:left-0",
"after:right-0",
{
relative: active,
"after:border-table-dark": mode === "dark",
"after:border-table-light": mode === "light",
"after:border-table-dark dark:after:border-table-light":
mode === "system",
"after:border-table-light dark:after:border-table-dark":
mode === "alt-system",
},
);
return (
<button
ref={ref}
className={buttonClass}
disabled={disabled}
type={type}
aria-label={ariaLabel || label}
{...otherProps}
>
{labelLeft && <span className="pr-2">{labelLeft}</span>}
<div className={iconClass}>{children}</div>
{labelRight && <span className="pl-2">{labelRight}</span>}
</button>
<div className={activeClass}>
<button
ref={ref}
className={buttonClass}
disabled={disabled}
type={type}
aria-label={ariaLabel || label}
{...otherProps}
>
{labelLeft && <span className="pr-2">{labelLeft}</span>}
<div className={iconClass}>{children}</div>
{labelRight && <span className="pl-2">{labelRight}</span>}
</button>
</div>
);
},
);
Expand Down
6 changes: 6 additions & 0 deletions packages/ui-components/src/components/Button/ButtonTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ export type ButtonIconProps = {
* The children to render.
*/
children: React.ReactNode;
/**
* Prop to signal if the Button is active.
* This is an internal prop and should not be used by consumers.
* @private
*/
active?: boolean;
/**
* Cell content alignment.
* @default "center"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,56 @@ describe("ButtonIcon modifiers", () => {
expect(buttonClass).toContain("disabled:cursor-not-allowed");
});

it("should render an active light button icon", async () => {
render(
<ButtonIcon mode="light" active>
<IconSettings />
</ButtonIcon>,
);
const button = await screen.findByRole("button");
const parent = button.parentElement;

if (parent) {
expectToHaveClasses(parent, [
"focus-within:static",
"after:absolute",
"after:content-['']",
"after:border-b-2",
"after:z-[-1px]",
"after:bottom-[-4px]",
"after:left-0",
"after:right-0",
"relative",
"after:border-table-light",
]);
}
});

it("should render an active dark button icon", async () => {
render(
<ButtonIcon mode="dark" active>
<IconSettings />
</ButtonIcon>,
);
const button = await screen.findByRole("button");
const parent = button.parentElement;

if (parent) {
expectToHaveClasses(parent, [
"focus-within:static",
"after:absolute",
"after:content-['']",
"after:border-b-2",
"after:z-[-1px]",
"after:bottom-[-4px]",
"after:left-0",
"after:right-0",
"relative",
"after:border-table-dark",
]);
}
});

it("should render a fullWidth button icon", async () => {
render(
<ButtonIcon fullWidth>
Expand Down
3 changes: 2 additions & 1 deletion packages/ui-components/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ export const TableCellSort = ({
{...otherProps}
>
<ButtonIcon
className="rounded-none"
active={sortedCell === cellId}
className="rounded-none text-sm"
onClick={onClick}
align={align}
noBorder
Expand Down

0 comments on commit e52ca48

Please sign in to comment.