Skip to content

Commit f42db73

Browse files
committed
BN-47 | Add. Column Sorting
1 parent 7cecb0c commit f42db73

File tree

5 files changed

+454
-29
lines changed

5 files changed

+454
-29
lines changed

docs/expandable-data-table-guide.md

Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,20 @@ The ExpandableDataTable component accepts a generic type parameter `T` which rep
4646

4747
### Props
4848

49-
| Prop | Type | Description | Required |
50-
| --------------------- | ------------------------------------------- | ------------------------------------------------------ | -------- |
51-
| tableTitle | string | Title for the table, displayed in the Accordion header | Yes |
52-
| rows | T[] | Array of data to display in the table | Yes |
53-
| headers | DataTableHeader[] | Column definitions for the table | Yes |
54-
| renderCell | (row: T, cellId: string) => React.ReactNode | Function to render the content of each cell | Yes |
55-
| renderExpandedContent | (row: T) => React.ReactNode | Function to render the content of expanded rows | Yes |
56-
| loading | boolean | Whether the table is in a loading state | No |
57-
| error | unknown | Error object to display in the error state | No |
58-
| ariaLabel | string | Accessibility label for the table | No |
59-
| emptyStateMessage | string | Message to display when there are no rows | No |
60-
| className | string | Custom CSS class for the component | No |
49+
| Prop | Type | Description | Required |
50+
| --------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------- | -------- |
51+
| tableTitle | string | Title for the table, displayed in the Accordion header | Yes |
52+
| rows | T[] | Array of data to display in the table | Yes |
53+
| headers | DataTableHeader[] | Column definitions for the table | Yes |
54+
| sortable | boolean[] | Array of boolean values indicating which columns are sortable. Maps to headers array. | No |
55+
| renderCell | (row: T, cellId: string) => React.ReactNode | Function to render the content of each cell | Yes |
56+
| renderExpandedContent | (row: T) => React.ReactNode | Function to render the content of expanded rows | Yes |
57+
| loading | boolean | Whether the table is in a loading state | No |
58+
| error | unknown | Error object to display in the error state | No |
59+
| ariaLabel | string | Accessibility label for the table | No |
60+
| emptyStateMessage | string | Message to display when there are no rows | No |
61+
| className | string | Custom CSS class for the component | No |
62+
| rowClassNames | string[] | Array of CSS class names to apply to specific rows | No |
6163

6264
### Type Definitions
6365

@@ -66,13 +68,15 @@ interface ExpandableDataTableProps<T> {
6668
tableTitle: string;
6769
rows: T[];
6870
headers: DataTableHeader[];
71+
sortable?: boolean[];
6972
renderCell: (row: T, cellId: string) => React.ReactNode;
7073
renderExpandedContent: (row: T) => React.ReactNode;
7174
loading?: boolean;
7275
error?: unknown;
7376
ariaLabel?: string;
7477
emptyStateMessage?: string;
7578
className?: string;
79+
rowClassNames?: string[];
7680
}
7781

7882
// Carbon Design System type
@@ -86,9 +90,87 @@ interface DataTableHeader {
8690

8791
- `loading`: `false`
8892
- `error`: `null`
93+
- `sortable`: Defaults to all columns being sortable (`headers.map(() => true)`)
8994
- `ariaLabel`: Same as `tableTitle` if provided
9095
- `emptyStateMessage`: `'No data available'`
9196
- `className`: `'expandable-data-table-item'`
97+
- `rowClassNames`: `[]`
98+
99+
## Sorting Configuration
100+
101+
The ExpandableDataTable component provides built-in sorting functionality that can be configured using the `sortable` prop. This prop allows you to specify which columns should be sortable and which should not.
102+
103+
### Default Sorting Behavior
104+
105+
By default, all columns in the table are sortable. This means users can click on any column header to sort the table by that column in ascending or descending order.
106+
107+
```tsx
108+
// All columns are sortable by default
109+
<ExpandableDataTable
110+
tableTitle="Sample Table"
111+
rows={data}
112+
headers={headers}
113+
renderCell={renderCell}
114+
renderExpandedContent={renderExpandedContent}
115+
/>
116+
```
117+
118+
### Configuring Sortable Columns
119+
120+
You can customize which columns are sortable by providing a `sortable` array. This array should contain boolean values that map to each header in the `headers` array.
121+
122+
```tsx
123+
const headers = [
124+
{ key: "name", header: "Name" },
125+
{ key: "status", header: "Status" },
126+
{ key: "date", header: "Date" },
127+
];
128+
129+
// Only the Name and Date columns will be sortable
130+
const sortable = [true, false, true];
131+
132+
<ExpandableDataTable
133+
tableTitle="Sample Table"
134+
rows={data}
135+
headers={headers}
136+
sortable={sortable}
137+
renderCell={renderCell}
138+
renderExpandedContent={renderExpandedContent}
139+
/>;
140+
```
141+
142+
### Handling Edge Cases
143+
144+
The component handles various edge cases gracefully:
145+
146+
1. **Shorter sortable array**: If the `sortable` array is shorter than the `headers` array, the remaining columns will default to not being sortable.
147+
148+
```tsx
149+
// Only the Name column will be sortable, Status and Date will not be sortable
150+
const sortable = [true];
151+
```
152+
153+
2. **Longer sortable array**: If the `sortable` array is longer than the `headers` array, the extra values will be ignored.
154+
155+
```tsx
156+
// Only the first three values will be used, extra values are ignored
157+
const sortable = [true, false, true, true, true];
158+
```
159+
160+
3. **Empty sortable array**: If an empty array is provided, no columns will be sortable.
161+
162+
```tsx
163+
// No columns will be sortable
164+
const sortable = [];
165+
```
166+
167+
4. **Non-boolean values**: Non-boolean values in the `sortable` array will be coerced to boolean.
168+
169+
```tsx
170+
// Values will be coerced to boolean (truthy/falsy)
171+
const sortable = [1, 0, "true"];
172+
// Equivalent to [true, false, true]
173+
```
92174

93175
## States and Rendering
94176

@@ -498,25 +580,25 @@ const renderRobustCell = (row: Item, cellId: string) => {
498580

499581
The ExpandableDataTable provides several predefined CSS classes for styling individual cells, across a row:
500582

501-
| Class Name | Color Code | Use Case |
502-
|---------------|------------|-----------|
503-
| criticalCell | #da1e28 | For highlighting critical or error values |
504-
| successCell | #198038 | For highlighting successful or positive values |
505-
| warningCell | #f1c21b | For highlighting warning states |
506-
| alertCell | #ff832b | For highlighting items needing attention |
583+
| Class Name | Color Code | Use Case |
584+
| ------------ | ---------- | ---------------------------------------------- |
585+
| criticalCell | #da1e28 | For highlighting critical or error values |
586+
| successCell | #198038 | For highlighting successful or positive values |
587+
| warningCell | #f1c21b | For highlighting warning states |
588+
| alertCell | #ff832b | For highlighting items needing attention |
507589

508590
### Usage in renderCell
509591

510592
```tsx
511593
const renderCell = (row: Item, cellId: string) => {
512594
switch (cellId) {
513-
case 'status':
595+
case "status":
514596
return (
515-
<div className={row.status === 'Critical' ? 'criticalCell' : ''}>
597+
<div className={row.status === "Critical" ? "criticalCell" : ""}>
516598
{row.status}
517599
</div>
518600
);
519-
case 'value':
601+
case "value":
520602
if (row.value > threshold) {
521603
return <span className="alertCell">{row.value}</span>;
522604
}
@@ -531,11 +613,11 @@ You can combine these cell styles with Carbon Design System components:
531613
```tsx
532614
const renderCell = (row: Item, cellId: string) => {
533615
switch (cellId) {
534-
case 'status':
616+
case "status":
535617
return (
536-
<Tag
537-
type={row.status === 'Critical' ? 'red' : 'green'}
538-
className={row.status === 'Critical' ? 'criticalCell' : ''}
618+
<Tag
619+
type={row.status === "Critical" ? "red" : "green"}
620+
className={row.status === "Critical" ? "criticalCell" : ""}
539621
>
540622
{row.status}
541623
</Tag>
@@ -612,13 +694,15 @@ export const ExpandableDataTable = <T extends { id?: string }>({
612694
tableTitle,
613695
rows,
614696
headers,
697+
sortable = headers.map(() => true),
615698
renderCell,
616699
renderExpandedContent,
617700
loading = false,
618701
error = null,
619702
ariaLabel = tableTitle,
620703
emptyStateMessage = "No data available",
621704
className = "expandable-data-table-item",
705+
rowClassNames = [],
622706
}: ExpandableDataTableProps<T>) => {
623707
// Component implementation
624708
};
@@ -631,13 +715,15 @@ interface ExpandableDataTableProps<T> {
631715
tableTitle: string;
632716
rows: T[];
633717
headers: DataTableHeader[];
718+
sortable?: boolean[];
634719
renderCell: (row: T, cellId: string) => React.ReactNode;
635720
renderExpandedContent: (row: T) => React.ReactNode;
636721
loading?: boolean;
637722
error?: unknown;
638723
ariaLabel?: string;
639724
emptyStateMessage?: string;
640725
className?: string;
726+
rowClassNames?: string[];
641727
}
642728
```
643729

src/components/allergies/AllergiesTable.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ const AllergiesTable: React.FC = () => {
1919
const headers = useMemo(
2020
() => [
2121
{ key: 'display', header: 'Allergy' },
22+
{ key: 'severity', header: 'Severity' },
2223
{ key: 'manifestation', header: 'Reaction(s)' },
2324
{ key: 'status', header: 'Status' },
24-
{ key: 'severity', header: 'Severity' },
2525
{ key: 'recorder', header: 'Provider' },
2626
{ key: 'recordedDate', header: 'Recorded Date' },
2727
],
2828
[],
2929
);
3030

31+
const sortable = useMemo(() => [true, false, false, true, true, true], []);
32+
3133
// Format allergies for display
3234
const formattedAllergies = useMemo(() => {
3335
if (!allergies || allergies.length === 0) return [];
@@ -96,6 +98,7 @@ const AllergiesTable: React.FC = () => {
9698
tableTitle="Allergies"
9799
rows={formattedAllergies}
98100
headers={headers}
101+
sortable={sortable}
99102
renderCell={renderCell}
100103
renderExpandedContent={renderExpandedContent}
101104
loading={loading}

src/components/expandableDataTable/ExpandableDataTable.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface ExpandableDataTableProps<T> {
2323
tableTitle: string;
2424
rows: T[];
2525
headers: DataTableHeader[];
26+
sortable?: boolean[];
2627
renderCell: (row: T, cellId: string) => React.ReactNode;
2728
renderExpandedContent: (row: T) => React.ReactNode;
2829
loading?: boolean;
@@ -37,6 +38,7 @@ export const ExpandableDataTable = <T extends { id?: string }>({
3738
tableTitle,
3839
rows,
3940
headers,
41+
sortable = headers.map(() => true),
4042
renderCell,
4143
renderExpandedContent,
4244
loading = false,
@@ -103,7 +105,7 @@ export const ExpandableDataTable = <T extends { id?: string }>({
103105
<div className={className} data-testid="expandable-data-table">
104106
<Accordion>
105107
<AccordionItem title={tableTitle}>
106-
<DataTable rows={dataTableRows} headers={headers} isSortable>
108+
<DataTable rows={dataTableRows} headers={headers}>
107109
{({
108110
rows: tableRows,
109111
headers: tableHeaders,
@@ -121,9 +123,12 @@ export const ExpandableDataTable = <T extends { id?: string }>({
121123
<TableHead>
122124
<TableRow>
123125
<TableExpandHeader />
124-
{tableHeaders.map((header) => (
126+
{tableHeaders.map((header, index) => (
125127
<TableHeader
126-
{...getHeaderProps({ header })}
128+
{...getHeaderProps({
129+
header,
130+
isSortable: sortable[index] ?? false,
131+
})}
127132
key={generateId()}
128133
>
129134
{header.header}

0 commit comments

Comments
 (0)