Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .changeset/dry-clubs-hammer.md

This file was deleted.

115 changes: 115 additions & 0 deletions docs/pr-descriptions/73-datatable-ui-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# PR #73: Fix DataTable UI Issues - Sorting, Resizing, and Alignment

## 🎯 **Overview**

This hotfix resolves three critical DataTable UI issues reported by the web app team, improving the overall user experience and functionality of the DataTable component.

## πŸ› **Issues Fixed**

### Issue #70: Missing Sorting Indicators βœ…

- **Problem**: No visual indicators for sortable columns or current sort state
- **Root Cause**: Header groups were memoized, preventing sorting state changes from triggering re-renders
- **Solution**:
- Removed memoization from `headerGroups` to ensure sorting indicators update properly
- Implemented proper sorting icon logic with lucide-react chevrons:
- `ChevronsUpDown` (opacity 50%) for sortable but unsorted columns
- `ChevronUp` for ascending sort
- `ChevronDown` for descending sort
- No icon for non-sortable columns

### Issue #71: Number Column Right Alignment Not Working βœ…

- **Problem**: `meta.className: 'text-right'` was not being applied to table cells
- **Root Cause**: Table cell rendering was not applying the `meta.className` property
- **Solution**:
- Fixed table cell rendering to apply `meta.className` using `cn()` utility
- Added proper TypeScript interface for `ColumnMeta`
- Updated stories to demonstrate left, right, and center alignment

### Issue #72: Column Resizing Handles Not Working βœ…

- **Problem**: Resize handles were visible but not functional
- **Root Cause**: Missing column sizing state management and poor handle visibility
- **Solution**:
- Added `columnSizing` state and `onColumnSizingChange` callback
- Improved resize handle styling:
- Increased width from `w-1` to `w-2` for better visibility
- Changed background from `bg-border` to `bg-muted-foreground/20`
- Added smooth transitions and better hover effects

## πŸ”§ **Additional Improvements**

### Custom SVG Icon Replacement

- Replaced custom SVG icons with lucide-react icons across multiple components:
- **ThemeToggle**: `Sun` and `Moon` icons
- **NavItem**: `ChevronRight` icon
- **ErrorBoundary**: `AlertTriangle` icon
- **Benefits**: Better consistency, smaller bundle size, easier maintenance

### Enhanced DataTable Stories

- Updated column configurations with explicit sorting and alignment settings
- Added comprehensive examples demonstrating all features:
- ID & Age columns: Right-aligned numbers with sorting enabled
- Name & Email columns: Left-aligned text with sorting enabled
- Status column: Center-aligned badges with sorting disabled

## πŸ§ͺ **Testing**

- βœ… **All DataTable tests passing** (19 tests across 3 test files)
- βœ… **All UI kit tests passing** (1041 tests total)
- βœ… **Linter clean** (only existing warnings, no new errors)
- βœ… **Storybook rebuilt** with all fixes included
- βœ… **Build successful** for both ui-kit and showcase packages

## πŸ“Š **Verification**

The updated Storybook now properly demonstrates:

1. **βœ… Sorting Indicators**:

- Sortable columns show `ChevronsUpDown` when unsorted
- Active sorting shows `ChevronUp` or `ChevronDown`
- Non-sortable columns show no indicators
- **Icons now change when clicking column headers**

2. **βœ… Column Resizing**:

- Resize handles are now visible with improved styling
- Hover effects work properly
- Drag functionality is fully operational
- **Resize handles are now visible and functional**

3. **βœ… Text Alignment**:
- Numbers (ID, Age): Right-aligned
- Text (Names, Email): Left-aligned
- Status badges: Center-aligned
- **All alignments working correctly**

## πŸš€ **Impact**

- **User Experience**: Significantly improved DataTable usability with working sorting indicators and column resizing
- **Developer Experience**: Better alignment control and consistent icon usage across components
- **Maintenance**: Reduced custom code with standardized lucide-react icons
- **Performance**: Optimized rendering for sorting state changes

## πŸ“ **Breaking Changes**

None. All changes are backward compatible.

## πŸ”— **Related Issues**

- Closes #70: [DataTable] Missing Sorting Indicators
- Closes #71: [DataTable] Number Column Right Alignment Not Working
- Closes #72: [DataTable] Column Resizing Handles Not Working

## πŸ“‹ **Checklist**

- [x] All tests passing
- [x] Linter clean
- [x] Storybook updated and tested
- [x] Documentation updated (stories)
- [x] No breaking changes
- [x] Issues verified as resolved
7 changes: 7 additions & 0 deletions packages/showcase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @org/showcase

## 0.1.15

### Patch Changes

- Updated dependencies [35e2fbf]
- @etherisc/ui-kit@0.7.7

## 0.1.14

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/showcase/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@org/showcase",
"version": "0.1.14",
"version": "0.1.15",
"private": true,
"type": "module",
"scripts": {
Expand Down
12 changes: 12 additions & 0 deletions packages/ui-kit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# @etherisc/ui-kit

## 0.7.7

### Patch Changes

- 35e2fbf: Fix DataTable UI issues: sorting indicators, column resizing, and alignment

- Fix sorting indicators not updating when clicking column headers
- Fix column resizing handles not being visible or functional
- Fix number column right alignment not working with meta.className
- Replace custom SVG icons with lucide-react icons for consistency
- Improve DataTable stories with proper alignment examples

## 0.6.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@etherisc/ui-kit",
"version": "0.7.6",
"version": "0.7.7",
"type": "module",
"license": "Apache-2.0",
"main": "./dist/index.cjs",
Expand Down
158 changes: 70 additions & 88 deletions packages/ui-kit/src/components/data-display/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
PaginationState,
Updater,
} from "@tanstack/react-table";
import { ChevronUp, ChevronDown, ChevronsUpDown } from "lucide-react";
import { cn } from "../../../utils";
import { DataTablePagination } from "./DataTablePagination";

Expand All @@ -26,6 +27,11 @@ export interface PaginationConfig {
enableJumpToPage?: boolean;
}

// Extended meta interface for column definitions
interface ColumnMeta {
className?: string;
}

export interface DataTableProps<TData extends object, TValue = unknown> {
/**
* The data to display in the table
Expand Down Expand Up @@ -154,6 +160,7 @@ export const DataTable = React.memo(
enableKeyboardShortcuts = true,
}: DataTableProps<TData, TValue>) => {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnSizing, setColumnSizing] = useState({});

// Memoize columns to prevent re-renders when parent re-renders
const memoizedColumns = useMemo(() => columns, [columns]);
Expand Down Expand Up @@ -272,11 +279,15 @@ export const DataTable = React.memo(
// State management (used for controlled state)
state: {
sorting,
columnSizing,
...(isControlledPagination && smartPaginationConfig !== false
? { pagination: paginationState }
: {}),
},

// Column sizing callbacks
onColumnSizingChange: setColumnSizing,

// Callbacks
...(isControlledPagination && smartPaginationConfig !== false
? { onPaginationChange: handlePaginationChange }
Expand Down Expand Up @@ -315,60 +326,72 @@ export const DataTable = React.memo(
className="border-b transition-colors hover:bg-muted/50"
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="p-4">
<td
key={cell.id}
className={cn(
"p-4",
(cell.column.columnDef.meta as ColumnMeta)?.className,
)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
));
}, [currentRows, memoizedColumns.length]);

// Memoize header groups to prevent unnecessary re-renders
const headerGroups = useMemo(() => {
return table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
style={{
width: header.getSize(),
position: "relative",
}}
className="h-12 px-4 text-left align-middle font-medium text-foreground"
>
{header.isPlaceholder ? null : (
<div
className={cn(
"flex items-center gap-2",
header.column.getCanSort() && "cursor-pointer select-none",
)}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: <ArrowUpIcon className="h-4 w-4" />,
desc: <ArrowDownIcon className="h-4 w-4" />,
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
{enableResizing && header.column.getCanResize() && (
<div
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={cn(
"absolute right-0 top-0 h-full w-1 cursor-col-resize",
header.column.getIsResizing() ? "bg-primary" : "bg-border",
)}
/>
)}
</th>
))}
</tr>
));
}, [table, enableResizing]);
// Generate header groups (not memoized to ensure sorting indicators update)
const headerGroups = table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
style={{
width: header.getSize(),
position: "relative",
}}
className="h-12 px-4 text-left align-middle font-medium text-foreground"
>
{header.isPlaceholder ? null : (
<div
className={cn(
"flex items-center gap-2",
header.column.getCanSort() && "cursor-pointer select-none",
)}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{header.column.getCanSort() && (
<>
{{
asc: <ChevronUp className="h-4 w-4" />,
desc: <ChevronDown className="h-4 w-4" />,
}[header.column.getIsSorted() as string] ?? (
<ChevronsUpDown className="h-4 w-4 opacity-50" />
)}
</>
)}
</div>
)}
{enableResizing && header.column.getCanResize() && (
<div
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={cn(
"absolute right-0 top-0 h-full w-2 cursor-col-resize select-none",
"bg-muted-foreground/20 hover:bg-primary/70 active:bg-primary transition-colors",
header.column.getIsResizing() && "bg-primary",
)}
style={{ userSelect: "none" }}
/>
)}
</th>
))}
</tr>
));

return (
<div key={tableKey} className="w-full flex flex-col gap-4">
Expand Down Expand Up @@ -397,44 +420,3 @@ export const DataTable = React.memo(
) as <TData extends object, TValue = unknown>(
props: DataTableProps<TData, TValue> & { ref?: React.Ref<HTMLDivElement> },
) => React.JSX.Element;

// Icons for the table
function ArrowUpIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="m5 12 7-7 7 7" />
<path d="M12 19V5" />
</svg>
);
}

function ArrowDownIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M12 5v14" />
<path d="m19 12-7 7-7-7" />
</svg>
);
}
Loading
Loading