Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Table): implement table component in Design System #1438

Merged
merged 17 commits into from
Dec 10, 2024

Conversation

arturfracala
Copy link
Collaborator

@arturfracala arturfracala commented Nov 26, 2024

Resolves: #{issue-number}

Description

The Table component is a customizable solution for displaying tabular data. It supports features like sorting, column resizing, row selection, and striped row styling.

Storybook

https://feature-DPS-4377--613a8e945a5665003a05113b.chromatic.com

Checklist

Obligatory:

  • Self review (use this as your final check for proposed changes before requesting the review)
  • Add correct label
  • Assign pull request with the correct issue

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a versatile Table component with support for sorting, selection, and resizable columns.
    • Added TableHeader, TableBody, and TableRow components for structured table rendering.
    • Implemented responsive styling for different table sizes and interactive elements.
    • Added a custom hook useTable to manage row selection logic.
    • Introduced a new MultiSelect story for selecting multiple rows in Storybook.
  • Styling Enhancements

    • Added comprehensive styles for table components, including hover effects and alternating row colors.
    • Introduced mixins for consistent sizing, pinning styles, and striping effects.
  • Documentation

    • Created a new documentation file for the Table component, detailing its features and usage examples.
    • Added Storybook stories to showcase various configurations and functionalities of the Table component.
  • Type Definitions

    • Added TypeScript interfaces and types for better type safety and component props management.

Copy link
Contributor

coderabbitai bot commented Nov 26, 2024

Walkthrough

This pull request introduces a comprehensive table component for a React application, consisting of several files that define its structure, styling, and functionality. Key components include Table, TableBody, TableHeader, and TableRow, along with associated styles and hooks for managing state and interactions. The styles are modularized using SCSS, and the table supports features such as sorting, selection, and responsive design.

Changes

File Change Summary
Table.module.scss New styles for table component, including base and modifier classes for sizes and selection.
Table.tsx New Table component with props for data handling, sorting, and selection features.
TableBody.tsx New TableBody component for rendering table rows dynamically based on provided data.
TableHeader.module.scss New styles for TableHeader, including hover effects and layout management.
TableHeader.tsx New TableHeader component for rendering the table header with sorting and resizing capabilities.
TableRow.module.scss New styles for table rows, including hover effects.
TableRow.tsx New TableRow component for rendering individual rows with selection support.
hooks.ts New useTable custom hook for managing row selection logic.
index.ts Exports for Table, ITableProps, Column, and useTable for consolidated access.
pin.scss New mixin generatePin for sticky header styling.
size.scss New mixin table-size for managing table dimensions and padding.
types.ts New TypeScript types and interfaces for table structure and props.
TableBodyProps and TableRowProps interfaces New interfaces for defining props in TableBody and TableRow components.
TableHeaderProps interface New interface for defining props in TableHeader component.
TableSize, PinOptions, and SortOrder enums New types and enums for managing table sizes, pinning options, and sorting directions.

Assessment against linked issues

Objective Addressed Explanation
Implement table component in Design System (DPS-4377)

Suggested reviewers

  • vladko-uxds
  • VadymBezpalko

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@arturfracala arturfracala changed the title (DPS-4377) Implement table component in Design System feat(Table): implement table component in Design System Nov 26, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (22)
packages/react-components/src/components/Table/styles/size.scss (2)

1-10: LGTM with suggestions for improved robustness

Consider adding parameter validation and handling nested tables:

@mixin table-size($height: 48px, $padding-v: 8px, $padding-h: 16px) {
  // Scope to immediate children to handle nested tables
  > tr {
    height: $height;
  }

  > tr > th > span:first-child,
  > tr > td {
    padding: $padding-v $padding-h;
  }
}

1-4: Consider using CSS custom properties for dynamic updates

Replace hardcoded values with CSS custom properties for better maintainability:

@mixin table-size($height, $padding-v, $padding-h) {
  tr {
    height: var(--table-row-height, $height);
  }
}
packages/react-components/src/components/Table/TableRow.module.scss (1)

4-11: Consider optimizing border handling.

The current border implementation might create double borders between stacked rows. Consider using border-collapse: collapse in the parent table or using a more robust border strategy.

 .#{$base-class} {
-  border-bottom: 1px solid var(--border-basic-tertiary);
+  &:not(:last-child) {
+    border-bottom: 1px solid var(--border-basic-tertiary);
+  }
   background-color: var(--background-01);

   &:not(.#{$table-class}__header):hover {
     background-color: var(--surface-primary-hover);
   }
 }
packages/react-components/src/components/Table/styles/pin.scss (1)

1-9: Consider using z-index variable for better maintainability

The implementation looks good, but consider extracting the z-index value to a variable to maintain consistency across components.

+$table-header-z-index: 5;
+
 @mixin generatePin($type) {
   @if $type == 'header' {
     thead {
       position: sticky;
       top: 0;
-      z-index: 5;
+      z-index: $table-header-z-index;
       box-shadow: 0 12px 12px -7px #1313172e;
     }
   }
packages/react-components/src/components/Table/types.ts (3)

1-6: Consider adding width configuration to Column type

The Column type should include width-related properties to support column resizing functionality mentioned in ITableProps.

 export type Column<T> = {
   key: keyof T;
   header: string;
   sortable?: boolean;
   sortValue?: (item: T) => string | number;
+  width?: number | string;
+  minWidth?: number;
+  maxWidth?: number;
 };

8-10: Consider expanding PinOptions for column pinning

For advanced table functionality, consider supporting left/right column pinning.

-export type PinOptions = 'header';
+export type PinOptions = 'header' | 'left' | 'right';

31-34: Add JSDoc comments for better documentation

Consider adding documentation to explain the purpose of null key.

+/**
+ * Configuration for table sorting
+ * @property key - Column key to sort by, null means no sorting
+ * @property direction - Sort direction (ascending/descending/none)
+ */
 export interface SortConfig<T> {
   key: keyof T | null;
   direction: SortOrder;
 }
packages/react-components/src/components/Table/TableBody.tsx (1)

4-12: Add TypeScript documentation and improve type safety.

Consider adding JSDoc comments and refining types:

+/** Props for the TableBody component */
 interface TableBodyProps<T> {
+  /** Array of data items to display in the table */
   data: T[];
+  /** Column configuration array */
   columns: Column<T>[];
+  /** Function to get unique identifier for each row */
   getRowId: (row: T) => string | number;
+  /** Array of column widths */
   columnWidths: (number | undefined)[];
+  /** Enable row selection */
   selectable?: boolean;
+  /** Check if a row is selected */
-  isSelected: (id: string | number) => boolean;
+  isSelected: selectable extends true ? (id: string | number) => boolean : never;
+  /** Toggle row selection */
-  toggleRowSelection: (id: string | number) => void;
+  toggleRowSelection: selectable extends true ? (id: string | number) => void : never;
 }
packages/react-components/src/components/Table/TableRow.tsx (2)

9-9: Consider using a more specific baseClass name

The current baseClass name 'row' is too generic and could clash with other CSS classes. Consider using 'tableRow' or 'dsTableRow' to be more specific to the Design System context.

-const baseClass = 'row';
+const baseClass = 'tableRow';

11-19: Add JSDoc documentation for the interface and consider stricter typing

The interface would benefit from documentation explaining its purpose and props. Also, consider using a more specific type for rowId to ensure consistency.

+/**
+ * Props for the TableRow component
+ * @template T - The type of data represented in the row
+ */
 interface TableRowProps<T> {
+  /** The data object for this row */
   row: T;
+  /** Column definitions specifying how to render each field */
   columns: Column<T>[];
+  /** Width in pixels for each column */
   columnWidths: (number | undefined)[];
+  /** Whether the row can be selected */
   selectable?: boolean;
+  /** Function to check if a row is selected */
   isSelected: (id: string | number) => boolean;
+  /** Function to toggle row selection */
   toggleRowSelection: (id: string | number) => void;
-  rowId: string | number;
+  /** Unique identifier for the row */
+  rowId: string;
 }
packages/react-components/src/components/Table/TableHeader.module.scss (2)

27-31: Avoid using !important

Using !important breaks CSS specificity chains. Consider increasing selector specificity instead.

-        background-color: var(--background-01) !important;
+.#{$base-class} th#{&} {
+        background-color: var(--background-01);
+}

11-11: Use design system tokens for font-size

Replace hardcoded 13px with an appropriate design system token for consistent typography.

-  font-size: 13px;
+  font-size: var(--font-size-sm);
packages/react-components/src/components/Table/Table.module.scss (4)

1-9: Add border-spacing fallback for better browser compatibility

 .#{$base-class} {
   width: 100%;
+  border-spacing: 0;
   border-collapse: collapse;

10-20: Document size mixin parameters

Add comments explaining the parameters (height, padding, font-size) for better maintainability.

 &--small {
+  // @param1: row-height (48px)
+  // @param2: cell-padding (8px)
+  // @param3: font-size (16px)
   @include size.table-size(48px, 8px, 16px);
 }

32-49: Simplify nested selectors for better maintainability

 &__selected {
   display: flex;
   gap: var(--spacing-1);
   align-items: center;
   padding: var(--spacing-2);
   font-weight: 500;

-  > p {
+  p {
     margin: 0;
-    font-weight: 500;
   }

-  > div:first-child {
+  .icon-wrapper {
     display: flex;
     align-items: center;
     margin-right: var(--spacing-1);
   }
 }

65-73: Add vertical alignment for consistent cell content

 td,
 th {
   text-align: left;
+  vertical-align: middle;
 }
packages/react-components/src/components/Table/hooks.ts (1)

41-49: Optimize performance for large datasets

Consider memoizing allRowIds to avoid recreating the Set on every toggle.

+ const allRowIds = useMemo(
+   () => new Set(data.map(getRowId)),
+   [data, getRowId]
+ );

  const toggleSelectAll = useCallback(() => {
-   const allRowIds = new Set(data.map(getRowId));
    const allSelected = selectedRows?.size === data.length;

    const updatedSelectedRows = allSelected
      ? new Set<string | number>()
-     : allRowIds;
+     : new Set(allRowIds);
    onSelectionChange?.(updatedSelectedRows);
-   }, [data, getRowId, selectedRows, onSelectionChange]);
+   }, [allRowIds, selectedRows, onSelectionChange]);
packages/react-components/src/components/Table/TableHeader.tsx (2)

21-31: Add JSDoc comments to document the interface props.

While the types are well-defined, adding JSDoc comments would improve maintainability and developer experience.

+/**
+ * Props for the TableHeader component
+ * @template T - The type of data being displayed in the table
+ */
 interface TableHeaderProps<T> {
+  /** Array of column definitions */
   columns: Column<T>[];
+  /** Current sort configuration */
   sortConfig: { key: keyof T | null; direction: SortOrder };
+  /** Callback for handling sort changes */
   handleSort: (key: keyof T) => void;
   resizable?: boolean;
   handleMouseDown: (index: number, event: React.MouseEvent) => void;
   columnRefs: React.RefObject<(HTMLTableCellElement | null)[]>;
   selectable?: boolean;
   hoveredColumnIndex: number | null;
   setHoveredColumnIndex: (index: number | null) => void;
 }

63-64: Memoize event handlers to prevent unnecessary re-renders.

The onClick handler is recreated on every render.

+const handleColumnSort = React.useCallback(
+  (key: keyof T) => () => handleSort(key),
+  [handleSort]
+);

-onClick={() => handleSort(column.key)}
+onClick={handleColumnSort(column.key)}
packages/react-components/src/components/Table/Table.tsx (3)

65-65: Translate inline comment to English.

The comment is in Polish. Please use English for all comments for consistency.


147-150: Implement indeterminate state for partial selection.

When some rows are selected but not all, the select-all checkbox should display an indeterminate state to improve user experience.


39-40: Simplify object properties using shorthand syntax.

You can use property shorthand for selectedRows and onSelectionChange.

Apply this diff:

          selectedRows: selectedRows,
          onSelectionChange: onSelectionChange,
+         // Simplify to:
+         selectedRows,
+         onSelectionChange,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 75e7e14 and 1b59bfa.

📒 Files selected for processing (13)
  • packages/react-components/src/components/Table/Table.module.scss (1 hunks)
  • packages/react-components/src/components/Table/Table.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableBody.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableRow.tsx (1 hunks)
  • packages/react-components/src/components/Table/hooks.ts (1 hunks)
  • packages/react-components/src/components/Table/index.ts (1 hunks)
  • packages/react-components/src/components/Table/styles/pin.scss (1 hunks)
  • packages/react-components/src/components/Table/styles/size.scss (1 hunks)
  • packages/react-components/src/components/Table/types.ts (1 hunks)
  • packages/react-components/src/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/react-components/src/components/Table/index.ts
🔇 Additional comments (13)
packages/react-components/src/components/Table/TableRow.module.scss (1)

1-2: LGTM! Clear and maintainable variable naming.

The SCSS variables follow a clear naming convention that aligns with component structure.

packages/react-components/src/components/Table/types.ts (1)

12-16: LGTM!

The SortOrder enum is well-defined with clear values.

packages/react-components/src/components/Table/TableBody.tsx (1)

1-2: LGTM! Clean and focused imports.

packages/react-components/src/components/Table/TableHeader.module.scss (2)

12-12: Verify accessibility requirements

user-select: none might impact accessibility. Ensure this aligns with accessibility requirements, especially for screen readers and keyboard navigation.


1-3: Verify usage of imported style modules

The imported pin and size modules don't appear to be used in this file.

✅ Verification successful

Imports are correctly used in the table component

The imported pin and size modules are properly utilized:

  • size module provides table sizing mixins used in --small, --medium, and --large modifiers
  • pin module provides header pinning functionality used in --pinned_header modifier
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of pin and size styles in related files
rg -l "pin\." "packages/react-components/src/components/Table/"
rg -l "size\." "packages/react-components/src/components/Table/"

Length of output: 258


Script:

#!/bin/bash
# Check the content of Table.module.scss to see how pin and size are used
cat "packages/react-components/src/components/Table/Table.module.scss"

Length of output: 1177


Script:

#!/bin/bash
# Check if the imported style modules exist and their content
cat "packages/react-components/src/components/Table/styles/pin.scss" 2>/dev/null
cat "packages/react-components/src/components/Table/styles/size.scss" 2>/dev/null

Length of output: 566

packages/react-components/src/components/Table/Table.module.scss (3)

22-24: Verify sticky header browser support

The sticky header implementation needs verification across different browsers.

✅ Verification successful

Sticky header implementation is browser-safe

The implementation uses standard position: sticky without vendor prefixes, which is well-supported across modern browsers (IE11+). The mixin includes proper z-indexing and visual treatment with box-shadow.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any browser compatibility CSS fixes
rg -g '*.scss' -A 3 'position:\s*sticky' 

Length of output: 396


Script:

#!/bin/bash
# Check for browser prefixes and compatibility fixes in the pin mixin
cat packages/react-components/src/components/Table/styles/pin.scss

# Check for any browser compatibility comments or documentation
rg -g '*.{scss,css}' -B 2 -A 2 '@supports' 

Length of output: 379


26-30: Consider RTL support for text alignment

The text-align: start is good for RTL support, but ensure the Design System has RTL testing in place.

✅ Verification successful

RTL support looks good with text-align: start

Using text-align: start is the correct approach for RTL support as it automatically handles text direction based on the document's direction. No additional RTL-specific styles are needed at the component level.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for RTL related styles or utilities
rg -g '*.scss' 'rtl:|dir=rtl|[^-]right-to-left'

Length of output: 49


Script:

#!/bin/bash
# Let's check for any RTL-related configurations or utilities in the codebase
rg -i "rtl|right.?to.?left|direction|text-align|dir=" --type css --type scss --type ts --type tsx

# Also check for any RTL testing setup
fd -e test.js -e test.tsx -e spec.js -e spec.tsx -X rg -i "rtl|right.?to.?left|direction"

# Check package.json for RTL-related dependencies or scripts
fd "package.json" -X rg -i "rtl|direction"

Length of output: 263


Script:

#!/bin/bash
# Search for RTL related content with correct file type flags
rg -i "rtl|right.?to.?left|direction|text-align|dir=" -t css -t js -t jsx -t ts -t tsx

# Look specifically for text-align properties in style files
rg "text-align" -t css -t js -t jsx -t ts -t tsx -A 1 -B 1

Length of output: 203


51-59: Ensure sufficient color contrast for accessibility

Verify that the background colors provide sufficient contrast with text colors.

packages/react-components/src/components/Table/hooks.ts (2)

3-15: Well-structured TypeScript interfaces!

The interfaces are well-defined with proper generics and optional properties, providing good type safety and flexibility.


17-57: Clean and well-structured hook implementation!

The hook follows React best practices with proper memoization and clear separation of concerns.

packages/react-components/src/index.ts (1)

49-49: LGTM! Export follows established patterns.

The Table component export maintains consistency with other components and is correctly placed in alphabetical order.

✅ Verification successful

✅ Table component implementation verified

The Table component and its subcomponents are properly implemented at the expected location with all necessary files present.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the Table component implementation exists
# Expected: Find the Table component implementation files

fd -t f "Table" packages/react-components/src/components/Table/

Length of output: 508

packages/react-components/src/components/Table/TableHeader.tsx (2)

1-20: LGTM! Well-organized imports and consistent BEM naming.

The code follows best practices for import organization and CSS class naming conventions.


50-50: Add type guard for column.key to String conversion.

The current implementation assumes column.key can be safely converted to string.

position: sticky;
top: 0;
z-index: 5;
box-shadow: 0 12px 12px -7px #1313172e;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use design system color tokens for consistency

Replace the hardcoded shadow color with a design system token.

-      box-shadow: 0 12px 12px -7px #1313172e;
+      box-shadow: 0 12px 12px -7px var(--shadow-color);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 18 to 85
export interface ITableProps<T> {
data: T[];
columns: Column<T>[];
stripped?: boolean;
size?: TableSize;
pin?: PinOptions;
getRowId: (item: T) => string | number;
resizable?: boolean;
selectable?: true;
selectedRows?: Set<string | number>;
onSelectionChange?: (selectedIds: Set<string | number>) => void;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix selectable prop type and add conditional requirements

The selectable prop should be boolean, and selectedRows should be required when selectable is true.

 export interface ITableProps<T> {
   data: T[];
   columns: Column<T>[];
   stripped?: boolean;
   size?: TableSize;
   pin?: PinOptions;
   getRowId: (item: T) => string | number;
   resizable?: boolean;
-  selectable?: true;
+  selectable?: boolean;
-  selectedRows?: Set<string | number>;
-  onSelectionChange?: (selectedIds: Set<string | number>) => void;
+  selectedRows: selectable extends true ? Set<string | number> : never;
+  onSelectionChange: selectable extends true ? (selectedIds: Set<string | number>) => void : never;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface ITableProps<T> {
data: T[];
columns: Column<T>[];
stripped?: boolean;
size?: TableSize;
pin?: PinOptions;
getRowId: (item: T) => string | number;
resizable?: boolean;
selectable?: true;
selectedRows?: Set<string | number>;
onSelectionChange?: (selectedIds: Set<string | number>) => void;
}
export interface ITableProps<T> {
data: T[];
columns: Column<T>[];
stripped?: boolean;
size?: TableSize;
pin?: PinOptions;
getRowId: (item: T) => string | number;
resizable?: boolean;
selectable?: boolean;
selectedRows: selectable extends true ? Set<string | number> : never;
onSelectionChange: selectable extends true ? (selectedIds: Set<string | number>) => void : never;
}

Comment on lines 25 to 36
{data.map((row) => (
<TableRow
key={getRowId(row)}
row={row}
columns={columns}
columnWidths={columnWidths}
selectable={selectable}
isSelected={isSelected}
toggleRowSelection={toggleRowSelection}
rowId={getRowId(row)}
/>
))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize row rendering and add error handling.

  1. Cache row ID to avoid double calculation
  2. Consider adding error boundary and empty state handling
  3. Add virtualization for large datasets
-      {data.map((row) => (
+      {data.map((row) => {
+        const rowId = getRowId(row);
+        return (
         <TableRow
-          key={getRowId(row)}
+          key={rowId}
           row={row}
           columns={columns}
           columnWidths={columnWidths}
           selectable={selectable}
           isSelected={isSelected}
           toggleRowSelection={toggleRowSelection}
-          rowId={getRowId(row)}
+          rowId={rowId}
         />
-      ))}
+      })}

Consider using react-window or react-virtualized for handling large datasets efficiently.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 23 to 42
return (
<tbody>
{data.map((row) => (
<TableRow
key={getRowId(row)}
row={row}
columns={columns}
columnWidths={columnWidths}
selectable={selectable}
isSelected={isSelected}
toggleRowSelection={toggleRowSelection}
rowId={getRowId(row)}
/>
))}
</tbody>
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Add ARIA roles and test coverage for table components

  • Add role="rowgroup" to <tbody> element
  • Add role="row" to <tr> elements in TableRow and TableHeader
  • Create test file TableBody.test.tsx to cover row rendering and selection logic
🔗 Analysis chain

Add accessibility attributes and ensure test coverage.

  1. Add ARIA roles and attributes for better screen reader support
  2. Ensure test coverage for selection and rendering logic

Let me search for test coverage and accessibility patterns in a different way to make a more informed decision.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for test file existence and accessibility attributes in related components
fd "TableBody.test.tsx" && \
rg -l "role=\"" packages/react-components/src/components/Table/

Length of output: 88


Script:

#!/bin/bash
# Check for test files and accessibility patterns in Table components
fd "Table.*test" && \
ast-grep --pattern 'role=' && \
rg -A 2 '<tbody' packages/react-components/src/components/Table/ && \
rg -A 2 '<tr' packages/react-components/src/components/Table/

Length of output: 981

Comment on lines 32 to 40
{selectable && (
<td>
<Checkbox
checked={isSelected(rowId)}
onChange={() => toggleRowSelection(rowId)}
/>
</td>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance selection accessibility

Add keyboard navigation and ARIA attributes for better accessibility support.

 {selectable && (
-  <td>
+  <td
+    role="cell"
+    aria-selected={isSelected(rowId)}
+  >
     <Checkbox
       checked={isSelected(rowId)}
       onChange={() => toggleRowSelection(rowId)}
+      aria-label={`Select row ${rowId}`}
     />
   </td>
 )}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 40 to 54
{columns.map((column, index) => (
<td
key={String(column.key)}
style={{ width: `${columnWidths[index]}px` }}
>
{row[column.key] as React.ReactNode}
</td>
))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add safety checks and improve accessibility

The current implementation has several potential issues:

  1. No handling for undefined column widths
  2. Unsafe type casting
  3. Missing accessibility attributes

Apply these improvements:

 {columns.map((column, index) => (
   <td
     key={String(column.key)}
-    style={{ width: `${columnWidths[index]}px` }}
+    style={{ width: columnWidths[index] ? `${columnWidths[index]}px` : 'auto' }}
+    role="cell"
+    data-column={String(column.key)}
   >
-    {row[column.key] as React.ReactNode}
+    {column.render ? column.render(row) : String(row[column.key] ?? '')}
   </td>
 ))}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +44 to +53
&:hover,
&:active {
background-color: var(--action-primary-default);
height: 100vh;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Reconsider resizer hover height

Setting height to 100vh on hover could interfere with other page elements. Consider using a fixed height or percentage of the table height.

-      height: 100vh;
+      height: 100%;
+      transform: scaleY(1.5);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
&:hover,
&:active {
background-color: var(--action-primary-default);
height: 100vh;
}
&:hover,
&:active {
background-color: var(--action-primary-default);
height: 100%;
transform: scaleY(1.5);
}

Comment on lines 28 to 39
const toggleRowSelection = useCallback(
(id: string | number) => {
const updatedSelectedRows = new Set(selectedRows);
if (updatedSelectedRows.has(id)) {
updatedSelectedRows.delete(id);
} else {
updatedSelectedRows.add(id);
}
onSelectionChange?.(updatedSelectedRows);
},
[selectedRows, onSelectionChange]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix potential runtime error in toggleRowSelection

The function assumes selectedRows exists when creating updatedSelectedRows, but it's an optional prop.

Apply this fix:

  const toggleRowSelection = useCallback(
    (id: string | number) => {
-     const updatedSelectedRows = new Set(selectedRows);
+     const updatedSelectedRows = new Set(selectedRows || []);
      if (updatedSelectedRows.has(id)) {
        updatedSelectedRows.delete(id);
      } else {
        updatedSelectedRows.add(id);
      }
      onSelectionChange?.(updatedSelectedRows);
    },
    [selectedRows, onSelectionChange]
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const toggleRowSelection = useCallback(
(id: string | number) => {
const updatedSelectedRows = new Set(selectedRows);
if (updatedSelectedRows.has(id)) {
updatedSelectedRows.delete(id);
} else {
updatedSelectedRows.add(id);
}
onSelectionChange?.(updatedSelectedRows);
},
[selectedRows, onSelectionChange]
);
const toggleRowSelection = useCallback(
(id: string | number) => {
const updatedSelectedRows = new Set(selectedRows || []);
if (updatedSelectedRows.has(id)) {
updatedSelectedRows.delete(id);
} else {
updatedSelectedRows.add(id);
}
onSelectionChange?.(updatedSelectedRows);
},
[selectedRows, onSelectionChange]
);

Comment on lines 49 to 98
<th
key={String(column.key)}
ref={(el) => {
if (columnRefs.current) {
columnRefs.current[index] = el;
}
}}
className={cx({
[styles[`${headerCellClass}--hoverDisabled`]]: !column.sortable,
})}
onMouseEnter={() => setHoveredColumnIndex(index)}
onMouseLeave={() => setHoveredColumnIndex(null)}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add ARIA attributes for better accessibility.

The table header cells should have proper ARIA attributes for sorting.

 <th
   key={String(column.key)}
   ref={(el) => {
     if (columnRefs.current) {
       columnRefs.current[index] = el;
     }
   }}
+  role="columnheader"
+  aria-sort={
+    sortConfig.key === column.key
+      ? sortConfig.direction.toLowerCase()
+      : undefined
+  }
   className={cx({
     [styles[`${headerCellClass}--hoverDisabled`]]: !column.sortable,
   })}
   onMouseEnter={() => setHoveredColumnIndex(index)}
   onMouseLeave={() => setHoveredColumnIndex(null)}
 >

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +116 to +146
return [...data].sort((a, b) => {
const sortValueA = column.sortValue
? column.sortValue(a)
: a[sortConfig.key as unknown as keyof T];
const sortValueB = column.sortValue
? column.sortValue(b)
: b[sortConfig.key as unknown as keyof T];

if (sortValueA < sortValueB) {
return sortConfig.direction === SortOrder.Ascending ? -1 : 1;
}
if (sortValueA > sortValueB) {
return sortConfig.direction === SortOrder.Ascending ? 1 : -1;
}

return 0;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle undefined or null values during sorting.

Ensure sortValueA and sortValueB are handled when they are undefined or null to prevent runtime errors.

Apply this diff to enhance robustness:

        return [...data].sort((a, b) => {
          const sortValueA = column.sortValue
            ? column.sortValue(a)
            : a[sortConfig.key as unknown as keyof T];
          const sortValueB = column.sortValue
            ? column.sortValue(b)
            : b[sortConfig.key as unknown as keyof T];

+         if (sortValueA == null) return 1;
+         if (sortValueB == null) return -1;

          if (sortValueA < sortValueB) {
            return sortConfig.direction === SortOrder.Ascending ? -1 : 1;
          }

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (11)
packages/react-components/src/components/Table/types.ts (2)

10-10: Consider expanding PinOptions

Current implementation only supports header pinning. Consider adding 'footer' and 'column' options for future use cases.


66-69: Strengthen type safety for sort configuration

Consider constraining the key type to only sortable columns.

 export interface SortConfig<T> {
-  key: keyof T | null;
+  key: Extract<keyof T, Column<T>['key']> | null;
   direction: SortOrder;
 }
packages/react-components/src/components/Table/Table.mdx (6)

7-7: Add proper heading structure for navigation

Replace the plain text navigation with proper heading structure for better accessibility.

-[Intro](#Intro) | [Component API](#ComponentAPI) | [Content Spec](#ContentSpec)
+## Navigation
+- [Intro](#Intro)
+- [Component API](#ComponentAPI)
+- [Content Spec](#ContentSpec)

20-26: Add type documentation and validation

The Data type would benefit from JSDoc comments and validation constraints for each field.

+/**
+ * Represents a row in the table
+ * @typedef {Object} Data
+ */
 type Data = {
+  /** Unique identifier for the row */
   id: number;
+  /** Full name of the person */
   name: string;
+  /** Age in years (must be positive) */
   age: number;
+  /** Job role or position */
   role: string;
+  /** Action button or component */
   action: React.ReactNode;
 };

51-57: Simplify action column configuration

The action column has redundant properties. The sortValue is unnecessary when sortable is false.

 {
   key: 'action',
   header: 'Action',
-  sortable: false,
-  sortValue: undefined,
 },

59-88: Enhance sample data variety

Consider diversifying the sample data to demonstrate:

  • Different action button states (disabled, loading)
  • Empty or null values
  • Long text handling

92-95: Expand usage examples

Consider adding examples demonstrating:

  • Row selection
  • Column resizing
  • Custom sorting
  • Different size variants

103-109: Add context for Figma documentation link

Provide a brief description of what users will find in the Figma documentation (visual specs, design tokens, etc.).

+<p>View detailed visual specifications, measurements, and design tokens in our Figma documentation.</p>
 <a
   className="sb-unstyled"
   href="https://www.figma.com/design/9FDwjR8lYvincseDkKypC4/Component-Documentations?node-id=21446-18742&node-type=canvas&t=XufCAXr9dcNjfXDk-0"
   target="_blank"
 >
   Go to Figma documentation
 </a>
packages/react-components/src/components/Table/Table.stories.tsx (3)

16-22: Add JSDoc documentation for the Data type.

Adding documentation will help other developers understand the purpose and usage of each field in the Data type.

+/**
+ * Represents a row in the table with basic user information and an action button.
+ */
 type Data = {
+  /** Unique identifier for the row */
   id: number;
+  /** User's full name */
   name: string;
+  /** User's age in years */
   age: number;
+  /** User's role in the organization */
   role: string;
+  /** Action button or other interactive element */
   action: React.ReactNode;
 };

85-304: Extract repeated getRowId function.

The getRowId function is duplicated across all stories. Consider moving it to a shared location.

+const getRowId = (row: Data) => row.id;
+
 export const Default: StoryFn = () => {
-  const getRowId = (row: Data) => row.id;
   return (
     <Table size="medium" data={data} columns={columns} getRowId={getRowId} />
   );
 };

11-14: Add accessibility testing stories.

Consider adding stories that demonstrate and test accessibility features such as keyboard navigation, screen reader support, and ARIA attributes.

Would you like me to help create accessibility testing stories for the Table component?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1b59bfa and ba60c5f.

📒 Files selected for processing (3)
  • packages/react-components/src/components/Table/Table.mdx (1 hunks)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
  • packages/react-components/src/components/Table/types.ts (1 hunks)
🔇 Additional comments (2)
packages/react-components/src/components/Table/types.ts (2)

12-16: LGTM!

The enum values are well-defined and follow conventional naming.


54-63: Fix selectable prop type and add conditional requirements

The selectable prop should be boolean, and selectedRows should be required when selectable is true.

Comment on lines +1 to +8
export type Column<T> = {
key: keyof T;
header: string;
sortable?: boolean;
sortValue?: (item: T) => string | number;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add type constraint for sortable columns

When sortable is true, sortValue should be required to ensure proper sorting implementation.

 export type Column<T> = {
   key: keyof T;
   header: string;
   sortable?: boolean;
-  sortValue?: (item: T) => string | number;
+  sortValue?: sortable extends true ? (item: T) => string | number : never;
 };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 130 to 272
},
{
id: 7,
name: 'Sam Green',
age: 45,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 8,
name: 'Emily Clark',
age: 22,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 9,
name: 'Chris Adams',
age: 36,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 10,
name: 'Sophia Turner',
age: 29,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 11,
name: 'James Taylor',
age: 39,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 12,
name: 'Olivia Lewis',
age: 24,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 13,
name: 'Daniel Evans',
age: 32,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 14,
name: 'Mia Harris',
age: 31,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 15,
name: 'Noah King',
age: 40,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 16,
name: 'Liam Scott',
age: 23,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 17,
name: 'Ethan White',
age: 26,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 18,
name: 'Emma Hall',
age: 35,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 19,
name: 'Benjamin Young',
age: 41,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 20,
name: 'Ava Martinez',
age: 21,
role: 'Intern',
action: <Button>Edit</Button>,
},
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor duplicate data definition.

The dataForPinnExample is largely a duplicate of the data array. Consider creating a helper function to generate test data.

+const generateTestData = (count: number): Data[] => {
+  const roles = ['Developer', 'Designer', 'Manager', 'Intern'];
+  return Array.from({ length: count }, (_, i) => ({
+    id: i + 1,
+    name: `User ${i + 1}`,
+    age: 20 + Math.floor(Math.random() * 30),
+    role: roles[i % roles.length],
+    action: <Button>Edit</Button>,
+  }));
+};
+
-const data: Data[] = [/* ... */];
-const dataForPinnExample: Data[] = [/* ... */];
+const data = generateTestData(4);
+const dataForPinnExample = generateTestData(20);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const dataForPinnExample: Data[] = [
{
id: 1,
name: 'John Doe',
age: 28,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 2,
name: 'Jane Smith',
age: 34,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 3,
name: 'Alice Johnson',
age: 42,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 4,
name: 'Mike Williams',
age: 25,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 5,
name: 'David Brown',
age: 30,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 6,
name: 'Laura Wilson',
age: 27,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 7,
name: 'Sam Green',
age: 45,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 8,
name: 'Emily Clark',
age: 22,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 9,
name: 'Chris Adams',
age: 36,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 10,
name: 'Sophia Turner',
age: 29,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 11,
name: 'James Taylor',
age: 39,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 12,
name: 'Olivia Lewis',
age: 24,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 13,
name: 'Daniel Evans',
age: 32,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 14,
name: 'Mia Harris',
age: 31,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 15,
name: 'Noah King',
age: 40,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 16,
name: 'Liam Scott',
age: 23,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 17,
name: 'Ethan White',
age: 26,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 18,
name: 'Emma Hall',
age: 35,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 19,
name: 'Benjamin Young',
age: 41,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 20,
name: 'Ava Martinez',
age: 21,
role: 'Intern',
action: <Button>Edit</Button>,
},
];
const generateTestData = (count: number): Data[] => {
const roles = ['Developer', 'Designer', 'Manager', 'Intern'];
return Array.from({ length: count }, (_, i) => ({
id: i + 1,
name: `User ${i + 1}`,
age: 20 + Math.floor(Math.random() * 30),
role: roles[i % roles.length],
action: <Button>Edit</Button>,
}));
};
const data = generateTestData(4);
const dataForPinnExample = generateTestData(20);

@vladko-uxds vladko-uxds self-requested a review November 27, 2024 09:12
@vladko-uxds
Copy link

vladko-uxds commented Nov 27, 2024

Great work!
Few things for fix:

  • Header height for all size -> 48px
  • Header font size - paragraph -sm-bold
  • Header: spacing between label and sorting icon -> 8px (token)
  • Cell sizes, heights: Small - 48px, Medium-56px, Large-64px
  • Cell text style: -paragraph-md
  • Pinning: shadow as token -> Pinned header --shadow-fixed-bottom(token), Left column (--shadow-fixed-right'), Right column (--shadow-fixed-left`)

@vladko-uxds
Copy link

vladko-uxds commented Nov 27, 2024

Hover on sorting icon in some columns make shifts

Monosnap.screencast.2024-11-27.10-39-53.mp4

@vladko-uxds
Copy link

Resizing affects both sides instead of only the side being adjusted, and it also triggers changes in the widths of other columns.

resize.mp4

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (1)
packages/react-components/src/components/Table/Table.spec.tsx (1)

13-24: Enhance test data coverage

While the current test data is good for basic cases, consider adding edge cases:

  • Empty strings
  • Very long text content
  • Special characters
  • Extreme number values
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ba60c5f and 1f6bc12.

📒 Files selected for processing (1)
  • packages/react-components/src/components/Table/Table.spec.tsx (1 hunks)

Comment on lines 26 to 40
describe('<Table> component', () => {
const getRowId = (row: Data) => row.id;

it('should render a table with provided data and columns', () => {
const { getByRole, getAllByRole } = render(
<Table data={data} columns={columns} getRowId={getRowId} />
);

const table = getByRole('table');
expect(table).toBeInTheDocument();

const rows = getAllByRole('row');
expect(rows.length).toBe(data.length + 1);
});

Copy link
Contributor

@coderabbitai coderabbitai bot Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add tests for table dimensions

Based on the PR objectives, we need tests to verify:

  • Header height (36px)
  • Cell sizes (Small: 48px, Medium: 56px, Large: 64px)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We verify styling using Chromatic, and our eyes :) Not needed to cover it by the test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcinsawicki Understood. We'll rely on Chromatic and visual inspection for styling verification; tests for styling dimensions in the Table component are not necessary.


✏️ Learnings added
Learnt from: marcinsawicki
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.spec.tsx:26-40
Timestamp: 2024-12-04T07:42:16.153Z
Learning: In the Design System project, for the `Table` component implemented in React, styling is verified using Chromatic and visual inspection; tests for styling dimensions such as header height and cell sizes are not necessary in `Table.spec.tsx`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 41 to 49
it('should allow row selection if selectable prop is enabled', () => {
const onSelectionChange = vi.fn();
const { getAllByRole } = render(
<Table
data={data}
columns={columns}
getRowId={getRowId}
selectable={true}
onSelectionChange={onSelectionChange}
/>
);

const checkboxes = getAllByRole('checkbox');
expect(checkboxes.length).toBe(data.length + 1);

fireEvent.click(checkboxes[1]);
expect(onSelectionChange).toHaveBeenCalledWith(new Set([1]));
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add tests for remaining table features

The selection test is well-implemented, but we're missing tests for:

  • Column resizing functionality
  • Pinned headers with shadows
  • Spacing between label and sorting icon (8px)
  • Text styles (paragraph-md, paragraph-sm-bold)

Comment on lines +1 to +11
import { render, fireEvent, vi } from 'test-utils';

import { Table } from './Table';
import { Column } from './types';

type Data = {
id: number;
name: string;
age: number;
role: string;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

🛠️ Refactor suggestion

Add test coverage for sorting functionality

The Table component has robust sorting implementation with:

  • Column-level sort configuration
  • Custom sort value functions
  • Multi-direction sorting (Ascending/Descending/None)

But the test file lacks coverage for these features. Add tests for:

  • handleSort functionality when clicking column headers
  • Sort direction cycling
  • Custom sortValue function behavior
  • Verify sorted data order
🔗 Analysis chain

Add test coverage for sortable columns

The Data type and imports are well-structured, but given that all columns are marked as sortable (lines 14-17), we should add tests for sorting functionality.

Consider adding these test cases:

  • Clicking column header changes sort direction
  • Verify sort order for different data types (string, number)
  • Test multiple column sort if supported
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if sorting tests exist in other test files
rg -l "sort.*test|test.*sort" --type ts --type tsx

Length of output: 80


Script:

#!/bin/bash
# Let's try a different approach to find sorting-related tests
rg -l "sort" --type typescript --type-add 'typescript:*.{ts,tsx}'

# Also check the Table component implementation for sortable props
ast-grep --pattern 'interface $_ {
  $$$
  sortable?: $_
  $$$
}'

# And check for sort-related functions
ast-grep --pattern 'function $_($$_) {
  $$$
  sort$$$
}'

Length of output: 954


Script:

#!/bin/bash
# Let's check the actual content of Table-related files for sort functionality
rg "sort" -C 3 packages/react-components/src/components/Table/types.ts
rg "sort" -C 3 packages/react-components/src/components/Table/Table.tsx
rg "test|it\(" -C 3 packages/react-components/src/components/Table/Table.spec.tsx

Length of output: 2905

Copy link
Contributor

@VadymBezpalko VadymBezpalko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job! 👏 I approve this Pull Request, but please pay attention to Vlad's comments regarding the UX part. And please check the comments from CodeRabbit as well 🐰.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
packages/react-components/src/components/Table/TableRow.tsx (1)

12-20: Consider using a more specific type for rowId

The rowId prop accepts both string and number, which could lead to inconsistent handling. Consider using a single type or creating a specific ID type.

-  rowId: string | number;
+  rowId: string;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1f6bc12 and 1d76a16.

📒 Files selected for processing (4)
  • packages/react-components/src/components/Table/TableHeader.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.tsx (1 hunks)
  • packages/react-components/src/components/Table/styles/size.scss (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/react-components/src/components/Table/TableHeader.module.scss
  • packages/react-components/src/components/Table/TableHeader.tsx
  • packages/react-components/src/components/Table/styles/size.scss
🔇 Additional comments (1)
packages/react-components/src/components/Table/TableRow.tsx (1)

33-40: Enhance selection accessibility

The checkbox implementation needs accessibility improvements.

Comment on lines 41 to 54
{columns.map((column, index) => (
<td
key={String(column.key)}
style={{ width: `${columnWidths[index]}px` }}
>
<Text size="md" noMargin>
{row[column.key] as React.ReactNode}
</Text>
</td>
))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add width fallback and improve type safety

The current implementation needs improvements:

  1. Handle undefined column widths gracefully
  2. Add type-safe cell rendering
 {columns.map((column, index) => (
   <td
     key={String(column.key)}
-    style={{ width: `${columnWidths[index]}px` }}
+    style={{ width: columnWidths[index] ? `${columnWidths[index]}px` : 'auto' }}
   >
     <Text size="md" noMargin>
-      {row[column.key] as React.ReactNode}
+      {column.render?.(row[column.key]) ?? String(row[column.key] ?? '')}
     </Text>
   </td>
 ))}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{columns.map((column, index) => (
<td
key={String(column.key)}
style={{ width: `${columnWidths[index]}px` }}
>
<Text size="md" noMargin>
{row[column.key] as React.ReactNode}
</Text>
</td>
))}
{columns.map((column, index) => (
<td
key={String(column.key)}
style={{ width: columnWidths[index] ? `${columnWidths[index]}px` : 'auto' }}
>
<Text size="md" noMargin>
{column.render?.(row[column.key]) ?? String(row[column.key] ?? '')}
</Text>
</td>
))}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a1d7436 and 8b3d7c8.

📒 Files selected for processing (1)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
🔇 Additional comments (1)
packages/react-components/src/components/Table/Table.stories.tsx (1)

54-83: 🛠️ Refactor suggestion

Standardize button sizes and refactor duplicate data

  1. The Button size prop is inconsistent between data arrays ('xcompact' vs default)
  2. Data arrays contain duplicate structure
+const generateTestData = (count: number, buttonSize: string = 'xcompact'): Data[] => {
+  const roles = ['Developer', 'Designer', 'Manager', 'Intern'];
+  return Array.from({ length: count }, (_, i) => ({
+    id: i + 1,
+    name: `User ${i + 1}`,
+    age: 20 + Math.floor(Math.random() * 30),
+    role: roles[i % roles.length],
+    action: <Button size={buttonSize}>Edit</Button>,
+  }));
+};

-const data: Data[] = [/* ... */];
-const dataForPinnExample: Data[] = [/* ... */];
+const data = generateTestData(4);
+const dataForPinnExample = generateTestData(20);

Also applies to: 130-271

Comment on lines 24 to 56
const columns: Column<(typeof data)[0]>[] = [
{
key: 'id',
header: 'ID',
sortable: true,
},
{
key: 'name',
header: 'Name',
sortable: true,
sortValue: (item: Data) => item.name.toLowerCase(),
},
{
key: 'age',
header: 'Age',
sortable: true,
},
{
key: 'role',
header: 'Role',
sortable: true,
sortValue: (item: Data) => item.role,
},
{
key: 'action',
header: 'Action',
sortable: false,
sortValue: undefined,
},
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Align header specifications with design requirements

According to the design requirements:

  • Header height should be 48px
  • Header font should use paragraph-sm-bold
  • Add 8px spacing between label and sorting icon

Comment on lines 85 to 193
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 12,
name: 'Olivia Lewis',
age: 24,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 13,
name: 'Daniel Evans',
age: 32,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 14,
name: 'Mia Harris',
age: 31,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 15,
name: 'Noah King',
age: 40,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 16,
name: 'Liam Scott',
age: 23,
role: 'Intern',
action: <Button>Edit</Button>,
},
{
id: 17,
name: 'Ethan White',
age: 26,
role: 'Developer',
action: <Button>Edit</Button>,
},
{
id: 18,
name: 'Emma Hall',
age: 35,
role: 'Designer',
action: <Button>Edit</Button>,
},
{
id: 19,
name: 'Benjamin Young',
age: 41,
role: 'Manager',
action: <Button>Edit</Button>,
},
{
id: 20,
name: 'Ava Martinez',
age: 21,
role: 'Intern',
action: <Button>Edit</Button>,
},
];

export const Pinning: StoryFn = () => {
const getRowId = (row: Data) => row.id;

return (
<Table
size="medium"
data={dataForPinnExample}
columns={columns}
getRowId={getRowId}
pin="header"
/>
);
};

export const Selecting: StoryFn = () => {
const getRowId = (row: Data) => row.id;
const [selectedRows, setSelectedRows] = React.useState<Set<string | number>>(
new Set()
);

return (
<Table
size="medium"
data={data}
columns={columns}
getRowId={getRowId}
selectable
selectedRows={selectedRows}
onSelectionChange={setSelectedRows}
/>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement standardized cell sizes

According to design requirements, cell heights should be:

  • Small: 48px
  • Medium: 56px
  • Large: 64px

Current implementation doesn't enforce these sizes.

Comment on lines 273 to 161
export const Pinning: StoryFn = () => {
const getRowId = (row: Data) => row.id;

return (
<Table
size="medium"
data={dataForPinnExample}
columns={columns}
getRowId={getRowId}
pin="header"
/>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add shadow tokens for pinned elements

Add the following shadow tokens:

  • --shadow-fixed-bottom for pinned header
  • --shadow-fixed-right for left column
  • --shadow-fixed-left for right column

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8b3d7c8 and f6a9708.

📒 Files selected for processing (3)
  • packages/react-components/src/components/Table/Table.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-components/src/components/Table/Table.spec.tsx
  • packages/react-components/src/components/Table/Table.stories.tsx
🔇 Additional comments (3)
packages/react-components/src/components/Table/Table.tsx (3)

127-143: Handle undefined or null values during sorting

The sorting logic needs to handle null/undefined values to prevent runtime errors.


22-22: Verify size prop default value

According to design requirements, table cells should follow specific heights:

  • Small: 48px
  • Medium: 56px
  • Large: 64px

Please ensure these dimensions are properly implemented in the CSS.

✅ Verification successful

Size prop default value aligns with design requirements

The CSS implementation in Table.module.scss and styles/size.scss correctly implements the required dimensions:

  • Small: 48px (default)
  • Medium: 56px
  • Large: 64px
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the CSS implements the required dimensions
rg -A 2 "table--small|table--medium|table--large" packages/react-components/src/components/Table/Table.module.scss

Length of output: 116


Script:

#!/bin/bash
# Let's check if the file exists first and see its content
fd "Table.module.scss" --type f

# Check for any height-related styles in the table component
rg -i "height|px" packages/react-components/src/components/Table/

Length of output: 2265


184-194: Verify header styling implementation

Design requirements specify:

  • Header height: 48px
  • Font size: paragraph-sm-bold
  • 8px spacing between label and sorting icon

Please ensure these are implemented in TableHeader component.

✅ Verification successful

Header styling requirements met

The implementation matches the design requirements:

  • Header height: 48px (using --spacing-12)
  • Font size: Using Text component with size="sm" and bold props
  • Icon spacing: 8px gap (using --spacing-2 in content class)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check TableHeader styling implementation
rg -A 3 "header|sort-icon" packages/react-components/src/components/Table/TableHeader.module.scss

Length of output: 198


Script:

#!/bin/bash
# Let's check the TableHeader component implementation for styling details
ast-grep --pattern 'const TableHeader = ($_) => {
  $$$
}'

# Also check for any style imports and className usage
rg -l "TableHeader.module.scss" packages/react-components/src/components/Table/
rg "className.*header" packages/react-components/src/components/Table/TableHeader.tsx

# Check for design tokens or variables that might define these values
fd "tokens|variables|theme" --extension scss,css packages/react-components/src/

Length of output: 373


Script:

#!/bin/bash
# Let's check the actual content of TableHeader component and its styles
cat packages/react-components/src/components/Table/TableHeader.tsx
cat packages/react-components/src/components/Table/TableHeader.module.scss

# Check for any typography or spacing tokens that might be used
rg -A 2 "paragraph-sm-bold|--spacing" packages/react-components/src/

Length of output: 65184

Comment on lines +55 to +92
const handleMouseDown = (index: number, event: React.MouseEvent) => {
event.preventDefault();
const startX = event.clientX;
const startWidth =
columnWidths[index] || (event.currentTarget as HTMLElement).offsetWidth;
const nextWidth = columnWidths[index + 1] || 0;

const SCALING_FACTOR = 1.05;

const handleMouseMove = (moveEvent: MouseEvent) => {
const delta = (moveEvent.clientX - startX) * SCALING_FACTOR;

setColumnWidths((prevWidths) => {
const newWidths = [...prevWidths];
const newWidth = Math.max(50, startWidth + delta);
const adjustedNextWidth = Math.max(50, nextWidth - delta);

newWidths[index] = newWidth;

if (index + 1 < newWidths.length) {
newWidths[index + 1] = adjustedNextWidth;
}

return newWidths;
});
};

const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};

document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Modify column resizing behavior

Current implementation affects both the resized column and its neighbor. According to feedback, resizing should only affect the column being adjusted.

Remove the adjacent column width adjustment:

 setColumnWidths((prevWidths) => {
   const newWidths = [...prevWidths];
   const newWidth = Math.max(50, startWidth + delta);
-  const adjustedNextWidth = Math.max(50, nextWidth - delta);
   
   newWidths[index] = newWidth;
-  if (index + 1 < newWidths.length) {
-    newWidths[index + 1] = adjustedNextWidth;
-  }
   
   return newWidths;
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleMouseDown = (index: number, event: React.MouseEvent) => {
event.preventDefault();
const startX = event.clientX;
const startWidth =
columnWidths[index] || (event.currentTarget as HTMLElement).offsetWidth;
const nextWidth = columnWidths[index + 1] || 0;
const SCALING_FACTOR = 1.05;
const handleMouseMove = (moveEvent: MouseEvent) => {
const delta = (moveEvent.clientX - startX) * SCALING_FACTOR;
setColumnWidths((prevWidths) => {
const newWidths = [...prevWidths];
const newWidth = Math.max(50, startWidth + delta);
const adjustedNextWidth = Math.max(50, nextWidth - delta);
newWidths[index] = newWidth;
if (index + 1 < newWidths.length) {
newWidths[index + 1] = adjustedNextWidth;
}
return newWidths;
});
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
const handleMouseDown = (index: number, event: React.MouseEvent) => {
event.preventDefault();
const startX = event.clientX;
const startWidth =
columnWidths[index] || (event.currentTarget as HTMLElement).offsetWidth;
const nextWidth = columnWidths[index + 1] || 0;
const SCALING_FACTOR = 1.05;
const handleMouseMove = (moveEvent: MouseEvent) => {
const delta = (moveEvent.clientX - startX) * SCALING_FACTOR;
setColumnWidths((prevWidths) => {
const newWidths = [...prevWidths];
const newWidth = Math.max(50, startWidth + delta);
newWidths[index] = newWidth;
return newWidths;
});
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please verify this comment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcinsawicki, I've verified the comment and the suggested code change is correct. Making this modification will ensure that column resizing only affects the column being adjusted, aligning with the expected behavior.


✏️ Learnings added
Learnt from: marcinsawicki
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.tsx:57-91
Timestamp: 2024-12-04T07:51:00.400Z
Learning: In the `Table` component, when implementing column resizing, ensure that resizing affects only the column being resized and does not adjust adjacent columns.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works much worse with this change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (3)
packages/react-components/src/components/Table/TableBody.tsx (1)

4-12: Consider strengthening columnWidths type

The type could be more specific to ensure columnWidths array length matches columns array length.

-  columnWidths: (number | undefined)[];
+  columnWidths: { [K in keyof T[]]?: number };
packages/react-components/src/components/Table/Table.mdx (1)

11-11: Document known limitations and behavior notes.

Add notes about:

  • Sorting icon spacing (8px between label and icon)
  • Column resizing behavior (currently affects both sides)
packages/react-components/src/components/Table/hooks.ts (1)

55-55: Default selectedCount to zero when undefined

Consider defaulting selectedCount to 0 to avoid potential undefined values.

Apply this diff if you agree:

-    selectedCount: selectedRows?.size,
+    selectedCount: selectedRows?.size ?? 0,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f6a9708 and 70726be.

📒 Files selected for processing (16)
  • packages/react-components/src/components/Table/Table.mdx (1 hunks)
  • packages/react-components/src/components/Table/Table.module.scss (1 hunks)
  • packages/react-components/src/components/Table/Table.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableBody.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableRow.tsx (1 hunks)
  • packages/react-components/src/components/Table/hooks.ts (1 hunks)
  • packages/react-components/src/components/Table/index.ts (1 hunks)
  • packages/react-components/src/components/Table/styles/pin.scss (1 hunks)
  • packages/react-components/src/components/Table/styles/size.scss (1 hunks)
  • packages/react-components/src/components/Table/types.ts (1 hunks)
  • packages/react-components/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/react-components/src/components/Table/Table.module.scss
  • packages/react-components/src/components/Table/Table.spec.tsx
  • packages/react-components/src/components/Table/Table.stories.tsx
  • packages/react-components/src/components/Table/Table.tsx
  • packages/react-components/src/components/Table/TableHeader.module.scss
  • packages/react-components/src/components/Table/TableHeader.tsx
  • packages/react-components/src/components/Table/TableRow.module.scss
  • packages/react-components/src/components/Table/TableRow.tsx
  • packages/react-components/src/components/Table/index.ts
  • packages/react-components/src/components/Table/styles/pin.scss
  • packages/react-components/src/components/Table/styles/size.scss
  • packages/react-components/src/components/Table/types.ts
  • packages/react-components/src/index.ts
🔇 Additional comments (7)
packages/react-components/src/components/Table/TableBody.tsx (3)

1-3: LGTM!

Imports are clean and specific to the component's needs.


24-41: Consider performance optimization for large datasets

For tables with many rows, consider implementing virtualization to improve rendering performance.

Consider using react-window or react-virtualized libraries for efficient rendering of large datasets.


37-37: ⚠️ Potential issue

Avoid redundant rowId calculation

The rowId is already calculated on line 26, reuse it instead of calling getRowId again.

-            rowId={getRowId(row)}
+            rowId={rowId}
packages/react-components/src/components/Table/Table.mdx (1)

1-8: LGTM! Clean imports and clear navigation structure.

packages/react-components/src/components/Table/hooks.ts (3)

23-26: Efficient memoization with useCallback

Great use of useCallback to memoize isSelected, optimizing rendering performance.


28-39: Robust handling of selectedRows in toggleRowSelection

Initializing updatedSelectedRows with selectedRows || [] ensures stability when selectedRows is undefined.


41-49: Well-implemented toggleSelectAll logic

The toggle functionality correctly handles selecting and deselecting all rows, accounting for current selection state.


## Intro <a id="Intro" />

`Table` component is a flexible and interactive table designed for displaying structured data. It supports key features such as sorting, column resizing, row selection, and a customizable layout.
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add specific sizing and styling specifications to the introduction.

Based on the PR requirements, include the following specifications:

  • Header height: 48px
  • Cell sizes: Small (48px), Medium (56px), Large (64px)
  • Header font: paragraph-sm-bold
  • Cell text style: paragraph-md

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcinsawicki Understood.

(_/)
( •_•)


✏️ Learnings added
Learnt from: marcinsawicki
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.mdx:11-11
Timestamp: 2024-12-04T07:31:14.203Z
Learning: When reviewing documentation files for the Table component, avoid suggesting adding specific sizing and styling specifications to the introduction unless explicitly requested.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 18 to 97
```jsx

type Data = {
id: number;
name: string;
age: number;
role: string;
action: React.ReactNode;
};

const columns: Column<(typeof data)[0]>[] = [
{
key: 'id',
header: 'ID',
sortable: true,
sortValue: (item: Data) => item.id,
},
{
key: 'name',
header: 'Name',
sortable: true,
sortValue: (item: Data) => item.name.toLowerCase(),
},
{
key: 'age',
header: 'Age',
sortable: true,
sortValue: (item: Data) => item.age,
},
{
key: 'role',
header: 'Role',
sortable: true,
sortValue: (item: Data) => item.role,
},
{
key: 'action',
header: 'Action',
},
];

const data: Data[] = [
{
id: 1,
name: 'John Doe',
age: 28,
role: 'Developer',
action: <Button size="xcompact">Edit</Button>,
},
{
id: 2,
name: 'Jane Smith',
age: 34,
role: 'Designer',
action: <Button size="xcompact">Edit</Button>,
},
{
id: 3,
name: 'Alice Johnson',
age: 42,
role: 'Manager',
action: <Button size="xcompact">Edit</Button>,
},
{
id: 4,
name: 'Mike Williams',
age: 25,
role: 'Intern',
action: <Button size="xcompact">Edit</Button>,
},
];

const getRowId = (row: Data) => row.id;

return (
<Table size="medium" data={data} columns={columns} getRowId={getRowId} />
);
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance example with all supported features.

The example should demonstrate additional key features:

  • Column resizing
  • Row selection
  • Striped styling
  • Pinned headers with shadows (using shadow tokens: --shadow-fixed-bottom, --shadow-fixed-right, --shadow-fixed-left)


## Component API <a id="ComponentAPI" />

<ArgTypes of={Stories.Default} />
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Stories.Default doesn't fully document available props

The Default story only shows basic usage, while the component supports many features demonstrated in separate stories:

  • Size options (small/medium/large) → shown in Sizes story
  • Column resizing → shown in Resizing story
  • Row selection → shown in Selecting story
  • Pinning → shown in Pinning story
🔗 Analysis chain

Verify complete props documentation in Stories.Default.

Ensure all component props are properly documented in the story, including:

  • Size options (small/medium/large)
  • Column resizing props
  • Row selection props
  • Striped styling props
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if all props are documented in the story file
ast-grep --pattern 'export const Default = {
  args: {
    $$$
  }
}' packages/react-components/src/components/Table/Table.stories.tsx

Length of output: 136


Script:

#!/bin/bash
# Let's first check if the file exists and see its content
fd "Table.stories.tsx" --exec cat {}

# Also check the Table component itself for props
fd "Table.tsx" --type f --exec ast-grep --pattern 'interface TableProps {
  $$$
}'

Length of output: 5683


Script:

#!/bin/bash
# Let's check the Table component interface
fd "Table.tsx" --exec cat {}

Length of output: 11344

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please verify if the "Component API" table covers all props from the interface

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcinsawicki, the "Component API" table currently references Stories.Default, which doesn't include all props from the ITableProps interface. To ensure all props are documented, you can update the ArgTypes in Table.mdx to reference the Table component directly:

<ArgTypes of={Table} />

This will display all the component's props in the API table.

🔗 Analysis chain
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find and extract the ITableProps interface from types.ts
fd 'types.ts' --type f --exec ast-grep --json --pattern 'export interface ITableProps<T> {
  $$$
}' {} \;

# Extract the prop names from the ITableProps interface
fd 'types.ts' --type f --exec ast-grep --json --pattern 'export interface ITableProps<T> {
  $$$
}' {} \; | jq -r '.captures[0].body.members[].key.name'

# Find Table.stories.tsx and extract the args from the Default story
fd 'Table.stories.tsx' --type f --exec ast-grep --json --pattern 'export const Default = {
  args: {
    $$$
  },
};' {} \;

# Extract the arg names from the Default story
fd 'Table.stories.tsx' --type f --exec ast-grep --json --pattern 'export const Default = {
  args: {
    $$$
  },
};' {} \; | jq -r '.captures[0].args.properties[].key.name'


Length of output: 8354


🧠 Learnings used
Learnt from: marcinsawicki
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.mdx:11-11
Timestamp: 2024-12-04T07:31:14.364Z
Learning: When reviewing documentation files for the Table component, avoid suggesting adding specific sizing and styling specifications to the introduction unless explicitly requested.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All props are covered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (4)
packages/react-components/src/components/Table/TableHeader.module.scss (1)

29-31: Avoid using !important

Consider increasing specificity instead of using !important for better maintainability.

-        background-color: var(--background-01) !important;
+        background-color: var(--background-01);

Then increase selector specificity:

&__cell--hoverDisabled#{&}:hover {
  background-color: var(--background-01);
}
packages/react-components/src/components/Table/types.ts (2)

12-12: Document future extensibility of PinOptions

Consider adding a comment indicating that PinOptions may support additional pinning options in the future.

+// Note: Currently only supports 'header'. Future versions may support column pinning.
 export type PinOptions = 'header';

79-82: Consider using undefined for unsorted state

Since we have SortOrder.None, using null for the unsorted state is redundant.

 export interface SortConfig<T> {
-  key: keyof T | null;
+  key: keyof T | undefined;
   direction: SortOrder;
 }
packages/react-components/src/components/Table/Table.stories.tsx (1)

67-67: Standardize button sizes across examples

The Button size prop is inconsistent:

  • xcompact in the data array
  • No size specified in dataForPinnExample

Standardize the button size across all examples.

Also applies to: 74-74, 81-81, 88-88, 143-143, 150-150, 157-157, 164-164, 171-171, 178-178, 185-185, 192-192, 199-199, 206-206, 213-213, 220-220, 227-227, 234-234, 241-241, 248-248, 255-255, 262-262, 269-269, 276-276

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 70726be and e163eab.

📒 Files selected for processing (6)
  • packages/react-components/src/components/Table/Table.module.scss (1 hunks)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.tsx (1 hunks)
  • packages/react-components/src/components/Table/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-components/src/components/Table/Table.tsx
  • packages/react-components/src/components/Table/TableHeader.tsx
🔇 Additional comments (13)
packages/react-components/src/components/Table/TableHeader.module.scss (1)

49-53: Reconsider resizer hover height

Setting height to 100vh on hover could interfere with other page elements.

-      height: 100vh;
+      height: 100%;
+      transform: scaleY(1.5);
packages/react-components/src/components/Table/Table.module.scss (6)

1-6: LGTM! Clean modular SCSS setup


7-11: LGTM! Good performance optimization with contain: paint


12-22: LGTM! Size variants match design requirements


34-51: LGTM! Good use of design system spacing tokens


53-61: LGTM! Proper use of design system color tokens


77-95: LGTM! Well-structured action bar styles

packages/react-components/src/components/Table/types.ts (2)

3-8: Ensure type safety for sortable columns

When a column is marked as sortable, it should have a required sortValue function.


56-65: Improve type safety for selection props

The selectable prop should be boolean, and selection-related props should be conditionally required.

packages/react-components/src/components/Table/Table.stories.tsx (4)

30-59: Add header specifications to column definitions

The column definitions should include header specifications as per design requirements:

  • Header height: 48px
  • Font: paragraph-sm-bold
  • Spacing: 8px between label and sorting icon

61-90: Eliminate data duplication

The dataForPinnExample duplicates the structure of data. Create a helper function to generate test data with configurable size.

Also applies to: 137-278


114-135: Implement standardized cell sizes

Update size variants to match design requirements:

  • Small: 48px
  • Medium: 56px
  • Large: 64px

280-292: Add shadow tokens for pinned elements

Implement required shadow tokens for pinned elements:

  • --shadow-fixed-bottom for pinned header
  • --shadow-fixed-right for left column
  • --shadow-fixed-left for right column

.#{$base-class} {
border-bottom: 1px solid var(--border-basic-tertiary);
background-color: var(--background-01);
height: var(--spacing-12);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Align with design specifications

According to the design requirements:

  • Header height should be 48px instead of var(--spacing-12)
  • Font size should use paragraph-sm-bold token instead of 13px
-  height: var(--spacing-12);
+  height: 48px;
-  font-size: 13px;
+  font: var(--typography-paragraph-sm-bold);

Also applies to: 12-12

Comment on lines +24 to +26
> span {
margin-left: var(--spacing-1);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Adjust icon spacing

The spacing between label and sorting icon should be 8px per design requirements.

-      margin-left: var(--spacing-1);
+      margin-left: var(--spacing-2); /* 8px */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> span {
margin-left: var(--spacing-1);
}
> span {
margin-left: var(--spacing-2); /* 8px */
}

Comment on lines +72 to +77
th > div {
display: flex;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add spacing between label and sorting icon

According to design requirements, there should be 8px spacing between the label and sorting icon.

  th > div {
    display: flex;
+   gap: var(--spacing-1);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
th > div {
display: flex;
}
th > div {
display: flex;
gap: var(--spacing-1);
}

Comment on lines +24 to +27
&--pinned_header {
@include pin.generatePin('header');
}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add shadow token for pinned header

According to design requirements, pinned header should use --shadow-fixed-bottom token.

  &--pinned_header {
    @include pin.generatePin('header');
+   box-shadow: var(--shadow-fixed-bottom);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
&--pinned_header {
@include pin.generatePin('header');
}
&--pinned_header {
@include pin.generatePin('header');
box-shadow: var(--shadow-fixed-bottom);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It uses --shadow-fixed-bottom. It generates from function pin.generatePin

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arturfracala, thank you for the clarification. I see that @include pin.generatePin('header'); already includes the --shadow-fixed-bottom token. I'll keep this in mind for future reviews.


✏️ Learnings added
Learnt from: arturfracala
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.module.scss:25-27
Timestamp: 2024-12-09T08:35:31.503Z
Learning: `@include pin.generatePin('header');` already applies the `--shadow-fixed-bottom` shadow token in the SCSS styling.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +28 to +37
&__cell {
text-align: start;
line-height: 21px;
font-size: 14px;
}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use design system tokens for typography

Replace hardcoded values with design system tokens:

  &__cell {
    text-align: start;
-   line-height: 21px;
-   font-size: 14px;
+   @extend .paragraph-md;
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
&__cell {
text-align: start;
line-height: 21px;
font-size: 14px;
}
&__cell {
text-align: start;
@extend .paragraph-md;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To verify if it is possible to use our typography in the cell instead styling it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcinsawicki, thank you for verifying this. Using your typography in the cell instead of custom styling would enhance consistency with the design system.


✏️ Learnings added
Learnt from: marcinsawicki
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.module.scss:33-37
Timestamp: 2024-12-04T07:39:30.858Z
Learning: When styling table cells in the `Table` component, use design system typography classes instead of custom styles.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (9)
packages/react-components/src/components/Table/TableHeader.spec.tsx (1)

1-13: Add @testing-library/jest-dom for accessibility testing

Consider importing @testing-library/jest-dom to enhance accessibility testing capabilities, especially for checking ARIA attributes and roles in the table header.

+import '@testing-library/jest-dom';
packages/react-components/src/components/Table/TableRow.spec.tsx (1)

6-21: Consider extracting test data to a separate file

The test data (types, columns, row) could be reused across other table component test files.

Create a new file __tests__/tableTestUtils.ts:

export type TestData = {
  id: number;
  name: string;
  age: number;
  role: string;
};

export const testColumns: Column<TestData>[] = [
  { key: 'id', header: 'ID' },
  { key: 'name', header: 'Name' },
  { key: 'age', header: 'Age' },
  { key: 'role', header: 'Role' },
];

export const testRow: TestData = { 
  id: 1, 
  name: 'John Doe', 
  age: 28, 
  role: 'Developer' 
};
packages/react-components/src/components/Table/styles/pin.scss (1)

11-33: Extract clip-path value to design token

Consider moving the clip-path value to a design token for consistency and reusability.

-      clip-path: inset(0 -15px 0 0);
+      clip-path: var(--clip-path-shadow);
packages/react-components/src/components/Table/stories-helpers.tsx (1)

6-19: Move test data to separate JSON file

Consider moving the hardcoded arrays to a JSON file for better maintainability.

Also applies to: 21-38

packages/react-components/src/components/Table/stories-constants.tsx (1)

5-16: Consider adding column configuration options

Enhance column definitions with additional properties like:

  • sortable: boolean
  • resizable: boolean
  • pinned: 'left' | 'right' | null
packages/react-components/src/components/Table/TableBody.spec.tsx (1)

91-114: Enhance column width test assertions

The current test only verifies cell widths. Add assertions for:

  • Resizable columns
  • Minimum/maximum width constraints
  • Column width persistence
packages/react-components/src/components/Table/Table.stories.tsx (2)

58-133: Enhance story organization with composition

Consider using story composition to reduce code duplication:

+const Template: StoryFn = (args) => {
+  const getRowId = (row: Data) => row.id;
+  const data = generateData(4);
+  return <Table getRowId={getRowId} data={data} {...args} />;
+};

-export const Default: StoryFn = () => {
-  const getRowId = (row: Data) => row.id;
-  const data = generateData(4);
-  return (
-    <Table size="medium" data={data} columns={columns} getRowId={getRowId} />
-  );
-};
+export const Default = Template.bind({});
+Default.args = {
+  size: "medium",
+  columns
+};

135-165: Extract action bar component for reusability

The action bar implementation should be moved to a separate component for better reusability across stories.

packages/react-components/src/components/Table/Table.tsx (1)

155-163: Enhance error messages

Add more context to error messages to help developers understand the issue:

-      '`onSelectionChange` and `selectedRows` are required when selectable is true'
+      'Table: `onSelectionChange` and `selectedRows` props are required when selectable={true}'
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e163eab and 64a6396.

📒 Files selected for processing (21)
  • packages/react-components/src/components/Table/Table.mdx (1 hunks)
  • packages/react-components/src/components/Table/Table.module.scss (1 hunks)
  • packages/react-components/src/components/Table/Table.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableBody.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableBody.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableRow.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.tsx (1 hunks)
  • packages/react-components/src/components/Table/hooks.ts (1 hunks)
  • packages/react-components/src/components/Table/index.ts (1 hunks)
  • packages/react-components/src/components/Table/stories-constants.tsx (1 hunks)
  • packages/react-components/src/components/Table/stories-helpers.tsx (1 hunks)
  • packages/react-components/src/components/Table/styles/pin.scss (1 hunks)
  • packages/react-components/src/components/Table/styles/size.scss (1 hunks)
  • packages/react-components/src/components/Table/types.ts (1 hunks)
  • packages/react-components/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/react-components/src/index.ts
  • packages/react-components/src/components/Table/TableRow.module.scss
  • packages/react-components/src/components/Table/styles/size.scss
  • packages/react-components/src/components/Table/index.ts
  • packages/react-components/src/components/Table/TableHeader.tsx
  • packages/react-components/src/components/Table/Table.mdx
  • packages/react-components/src/components/Table/TableHeader.module.scss
  • packages/react-components/src/components/Table/hooks.ts
  • packages/react-components/src/components/Table/Table.spec.tsx
  • packages/react-components/src/components/Table/TableRow.tsx
🔇 Additional comments (13)
packages/react-components/src/components/Table/types.ts (4)

3-8: Add type constraint for sortable columns

When sortable is true, sortValue should be required to ensure proper sorting implementation.


56-65: Fix selectable prop type and add conditional requirements

The selectable prop should be boolean, and selectedRows should be required when selectable is true.


79-103: LGTM!

The data types are well-structured and provide good examples for both simple and complex use cases.


12-12: ⚠️ Potential issue

Add support for right column pinning

According to the shadow requirements, right column pinning should be supported with --shadow-fixed-left.

-export type PinOptions = 'header' | 'leftColumn';
+export type PinOptions = 'header' | 'leftColumn' | 'rightColumn';

Likely invalid or redundant comment.

packages/react-components/src/components/Table/TableRow.spec.tsx (1)

23-115: Add tests for missing table features

Based on the PR objectives, tests are needed for:

  • Sorting functionality
  • Column resizing
  • Style specifications (heights, fonts, shadows)
packages/react-components/src/components/Table/TableBody.tsx (1)

25-38: 🛠️ Refactor suggestion

Optimize row rendering and add error handling

  1. rowId is calculated twice (lines 26 and 38)
  2. Missing error boundary and empty state handling
-            rowId={getRowId(row)}
+            rowId={rowId}

Likely invalid or redundant comment.

packages/react-components/src/components/Table/stories-helpers.tsx (1)

48-48: Verify button size value

The xcompact size seems non-standard. Verify if this is a supported size in the Button component.

✅ Verification successful

xcompact is a valid Button size

The Button component explicitly supports xcompact as one of its size options, along with compact, medium, and large. This is defined in the component's stories and handled in the implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for button size definitions
ast-grep --pattern 'size={$_}' packages/react-components/src/components/Button/

Length of output: 81


Script:

#!/bin/bash
# Search for Button component props interface or type definition
ast-grep --pattern 'interface ButtonProps {
  $$$
}'

# Search for size prop usage in Button stories
rg -A 3 'size=' packages/react-components/src/components/Button/

# Search for size-related constants or types
rg -A 3 'size.*=' packages/react-components/src/components/Button/

Length of output: 6704

packages/react-components/src/components/Table/Table.module.scss (3)

32-36: Use design system typography token

Replace hardcoded values with paragraph-md token for cell text style.


24-26: Add shadow tokens for pinned elements

Add shadow tokens for pinned elements:

  • Header: --shadow-fixed-bottom
  • Left column: --shadow-fixed-right

76-78: Add spacing between label and sorting icon

Add 8px spacing between label and sorting icon.

packages/react-components/src/components/Table/Table.stories.tsx (1)

10-14: Consider consolidating data generation logic

Move all data generation logic including column definitions to stories-helpers.ts for better maintainability.

Also applies to: 27-56

packages/react-components/src/components/Table/Table.tsx (2)

57-91: ⚠️ Potential issue

Update column resizing behavior

Current implementation affects adjacent columns, which doesn't match requirements.


129-145: ⚠️ Potential issue

Add null/undefined handling in sorting

The sorting implementation should handle null/undefined values gracefully.

Comment on lines +15 to +20
const columns: Column<Data>[] = [
{ key: 'id', header: 'ID', sortable: true },
{ key: 'name', header: 'Name', sortable: true },
{ key: 'age', header: 'Age', sortable: false },
{ key: 'role', header: 'Role', sortable: true },
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add tests for header styling requirements

Based on the PR objectives, add tests to verify:

  • Header height (48px)
  • Font size (paragraph-sm-bold)
  • 8px spacing between label and sorting icon

Comment on lines +29 to +128
it('renders all columns', () => {
const { getAllByRole } = render(
<table>
<TableHeader
columns={columns}
sortConfig={sortConfig}
handleSort={mockHandleSort}
resizable={false}
handleMouseDown={mockHandleMouseDown}
columnRefs={columnRefs}
selectable={false}
hoveredColumnIndex={null}
setHoveredColumnIndex={setHoveredColumnIndex}
/>
</table>
);

const headers = getAllByRole('columnheader');
expect(headers.length).toBe(columns.length);
columns.forEach((col) =>
expect(headers.some((header) => header.textContent === col.header)).toBe(
true
)
);
});

it('calls handleSort when a sortable column is clicked', () => {
const { getByText } = render(
<table>
<TableHeader
columns={columns}
sortConfig={sortConfig}
handleSort={mockHandleSort}
resizable={false}
handleMouseDown={mockHandleMouseDown}
columnRefs={columnRefs}
selectable={false}
hoveredColumnIndex={null}
setHoveredColumnIndex={setHoveredColumnIndex}
/>
</table>
);

const sortableColumn = getByText('ID');
fireEvent.click(sortableColumn);

expect(mockHandleSort).toHaveBeenCalledWith('id');
});

it('does not call handleSort when a non-sortable column is clicked', () => {
const handleSort = vi.fn();

const columns: Column<Data>[] = [
{ key: 'id', header: 'ID', sortable: true },
{ key: 'name', header: 'Name', sortable: false },
];

const { getByText } = render(
<TableHeader
columns={columns}
sortConfig={{ key: null, direction: SortOrder.None }}
handleSort={handleSort}
resizable={false}
handleMouseDown={vi.fn()}
columnRefs={React.createRef()}
hoveredColumnIndex={null}
setHoveredColumnIndex={vi.fn()}
/>
);

const nameHeader = getByText('Name');
fireEvent.click(nameHeader);

expect(handleSort).not.toHaveBeenCalled();
});

it('handles resizable columns', () => {
const { getAllByRole } = render(
<table>
<TableHeader
columns={columns}
sortConfig={sortConfig}
handleSort={mockHandleSort}
resizable={true}
handleMouseDown={mockHandleMouseDown}
columnRefs={columnRefs}
selectable={false}
hoveredColumnIndex={null}
setHoveredColumnIndex={setHoveredColumnIndex}
/>
</table>
);

const resizers = getAllByRole('button', { name: /resize/i });
expect(resizers.length).toBe(columns.length);
fireEvent.mouseDown(resizers[0]);

expect(mockHandleMouseDown).toHaveBeenCalledWith(0, expect.any(Object));
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add missing test cases for key requirements

Add test cases for:

  1. Column resizing affecting only the resized column (PR feedback mentions issues with both sides being affected)
  2. Shadow tokens for pinned headers
  3. Selectable state interactions

Example test structure:

it('resizes only the target column', () => {
  // Setup with initial column widths
  // Perform resize action
  // Verify only target column width changed
});

it('applies correct shadow tokens when pinned', () => {
  // Setup with pinned state
  // Verify shadow token classes
});

Comment on lines +22 to +28
describe('<TableHeader> component', () => {
const sortConfig = { key: null, direction: SortOrder.None };
const mockHandleSort = vi.fn();
const mockHandleMouseDown = vi.fn();
const columnRefs = { current: Array(columns.length).fill(null) };
const setHoveredColumnIndex = vi.fn();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Add hover state test coverage

The implementation has hover-related logic (hoveredColumnIndex, hovered, and hover styles) but the test file only sets hoveredColumnIndex to null. Add test cases for:

  • Column hover state changes
  • Sort icon visibility on hover
  • Style class application for hover states
🔗 Analysis chain

Add tests for sorting icon hover behavior

PR feedback mentions issues with sorting icon hover causing visual shifts. Add tests to verify hover behavior doesn't cause layout shifts.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for existing hover-related tests in other spec files
rg -l "hover.*sort.*icon" --type ts

Length of output: 37


Script:

#!/bin/bash
# Let's check for any hover-related tests in the TableHeader spec
rg "hover" "packages/react-components/src/components/Table/TableHeader.spec.tsx"

# Also check the implementation file for hover handling
rg "hover" "packages/react-components/src/components/Table/TableHeader.tsx"

# Look for any sort icon related code
ast-grep --pattern 'const $_ = styled.$_`
  $$$
  &:hover {
    $$$
  }
  $$$
`'

Length of output: 608

Comment on lines +70 to +93
it('should render a checkbox if selectable is true', () => {
const toggleRowSelection = vi.fn();
const { getByRole } = render(
<table>
<tbody>
<TableRow
row={row}
columns={columns}
columnWidths={columnWidths}
selectable={true}
isSelected={(id) => id === row.id}
toggleRowSelection={toggleRowSelection}
rowId={row.id}
/>
</tbody>
</table>
);

const checkbox = getByRole('checkbox');
expect(checkbox).toBeChecked();

fireEvent.click(checkbox);
expect(toggleRowSelection).toHaveBeenCalledWith(row.id);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add negative test case for row selection

The selection test should verify both selected and unselected states.

it('should handle unselected state correctly', () => {
  const toggleRowSelection = vi.fn();
  const { getByRole } = render(
    <table>
      <tbody>
        <TableRow
          row={row}
          columns={columns}
          columnWidths={columnWidths}
          selectable={true}
          isSelected={() => false}
          toggleRowSelection={toggleRowSelection}
          rowId={row.id}
        />
      </tbody>
    </table>
  );

  const checkbox = getByRole('checkbox');
  expect(checkbox).not.toBeChecked();
});

Comment on lines +95 to +114
it('should render aria-selected attribute correctly', () => {
const { getByRole } = render(
<table>
<tbody>
<TableRow
row={row}
columns={columns}
columnWidths={columnWidths}
selectable={true}
isSelected={(id) => id === row.id}
toggleRowSelection={() => {}}
rowId={row.id}
/>
</tbody>
</table>
);

const cell = getByRole('cell', { name: '' });
expect(cell).toHaveAttribute('aria-selected', 'true');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Expand accessibility testing

The accessibility test should verify more ARIA attributes and keyboard interactions.

it('should support keyboard selection', () => {
  const toggleRowSelection = vi.fn();
  const { getByRole } = render(
    <table>
      <tbody>
        <TableRow
          row={row}
          columns={columns}
          columnWidths={columnWidths}
          selectable={true}
          isSelected={() => false}
          toggleRowSelection={toggleRowSelection}
          rowId={row.id}
        />
      </tbody>
    </table>
  );

  const row = getByRole('row');
  expect(row).toHaveAttribute('tabIndex', '0');
  
  fireEvent.keyDown(row, { key: 'Space' });
  expect(toggleRowSelection).toHaveBeenCalledWith(row.id);
});

Comment on lines +24 to +44
it('should render a row with provided data and columns', () => {
const { getByText } = render(
<table>
<tbody>
<TableRow
row={row}
columns={columns}
columnWidths={columnWidths}
selectable={false}
isSelected={() => false}
toggleRowSelection={() => {}}
rowId={row.id}
/>
</tbody>
</table>
);

expect(getByText('John Doe')).toBeInTheDocument();
expect(getByText('28')).toBeInTheDocument();
expect(getByText('Developer')).toBeInTheDocument();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance basic rendering test coverage

The basic rendering test should verify all cell contents and their order.

-    expect(getByText('John Doe')).toBeInTheDocument();
-    expect(getByText('28')).toBeInTheDocument();
-    expect(getByText('Developer')).toBeInTheDocument();
+    const cells = getAllByRole('cell');
+    expect(cells).toHaveLength(4);
+    expect(cells[0]).toHaveTextContent('1');
+    expect(cells[1]).toHaveTextContent('John Doe');
+    expect(cells[2]).toHaveTextContent('28');
+    expect(cells[3]).toHaveTextContent('Developer');

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1 to +9
@mixin generatePin($type) {
@if $type == 'header' {
thead {
position: sticky;
top: 0;
z-index: 5;
box-shadow: var(--shadow-fixed-bottom);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Z-index value needs standardization

Based on the search results, there's no consistent z-index scale in the design system. The codebase shows varying z-index values (1, 5, 120, 13001) without clear organization. The table header's z-index:5 appears arbitrary and should be standardized.

  • Consider defining a z-index scale in design tokens
  • Align table header z-index with other sticky elements
  • Document the z-index hierarchy
🔗 Analysis chain

Verify z-index value in design system

The z-index value of 5 seems arbitrary. Ensure it aligns with the design system's z-index scale.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for z-index values in design system
rg -A 1 "z-index:|--z-index" packages/

Length of output: 5548


Script:

#!/bin/bash
# Search for z-index variables and stacking context definitions
rg "stacking-context-level|z-index:" packages/react-components/src/styles/

Length of output: 154


Script:

#!/bin/bash
# Search for SCSS variables and stacking context definitions
fd -e scss -x cat {} \; | rg "^\$.*stacking|^\$.*z-index"

Length of output: 60

Comment on lines +18 to +67
export const dataForPinningExample: DataForPinningExample[] = [
{
userId: 1,
fullName: 'Alice Brown',
years: 29,
jobTitle: 'Software Engineer',
team: 'Development',
office: 'Seattle',
availability: 'Available',
workExperience: '4 years',
income: '$75,000',
controls: <Button size="xcompact">Edit</Button>,
},
{
userId: 2,
fullName: 'Bob Green',
years: 35,
jobTitle: 'Product Manager',
team: 'Product',
office: 'Austin',
availability: 'Busy',
workExperience: '7 years',
income: '$110,000',
controls: <Button size="xcompact">Edit</Button>,
},
{
userId: 3,
fullName: 'Charlie Davis',
years: 41,
jobTitle: 'UX Designer',
team: 'Design',
office: 'Denver',
availability: 'Available',
workExperience: '10 years',
income: '$95,000',
controls: <Button size="xcompact">Edit</Button>,
},
{
userId: 4,
fullName: 'Dana White',
years: 26,
jobTitle: 'Data Analyst',
team: 'Analytics',
office: 'New York',
availability: 'On Leave',
workExperience: '2 years',
income: '$65,000',
controls: <Button size="xcompact">Edit</Button>,
},
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace sensitive information in mock data

Consider replacing actual salary values with placeholder data to avoid exposing sensitive information in examples.

Comment on lines +26 to +115
describe('<TableBody> component', () => {
const getRowId = (row: Data) => row.id;

it('renders rows for each data entry', () => {
const { getAllByRole } = render(
<table>
<TableBody
data={data}
columns={columns}
getRowId={getRowId}
columnWidths={[]}
selectable={false}
isSelected={() => false}
toggleRowSelection={() => {}}
/>
</table>
);

const rows = getAllByRole('row');
expect(rows.length).toBe(data.length);
});

it('renders selectable rows with checkboxes when selectable is true', () => {
const { getAllByRole } = render(
<table>
<TableBody
data={data}
columns={columns}
getRowId={getRowId}
columnWidths={[]}
selectable={true}
isSelected={(id) => id === 1}
toggleRowSelection={() => {}}
/>
</table>
);

const checkboxes = getAllByRole('checkbox');
expect(checkboxes.length).toBe(data.length);
expect(checkboxes[0]).toBeChecked();
expect(checkboxes[1]).not.toBeChecked();
});

it('calls toggleRowSelection when a checkbox is clicked', () => {
const toggleRowSelection = vi.fn();

const { getAllByRole } = render(
<table>
<TableBody
data={data}
columns={columns}
getRowId={getRowId}
columnWidths={[]}
selectable={true}
isSelected={() => false}
toggleRowSelection={toggleRowSelection}
/>
</table>
);

const checkboxes = getAllByRole('checkbox');
fireEvent.click(checkboxes[1]);
expect(toggleRowSelection).toHaveBeenCalledWith(2);
});

it('passes correct column widths to TableRow', () => {
const columnWidths = [100, 200, 150, 250];
const { getAllByRole } = render(
<table>
<TableBody
data={data}
columns={columns}
getRowId={getRowId}
columnWidths={columnWidths}
selectable={false}
isSelected={() => false}
toggleRowSelection={() => {}}
/>
</table>
);

const cells = getAllByRole('cell');
cells.forEach((cell, index) => {
const expectedWidth = columnWidths[index % columnWidths.length];
if (expectedWidth) {
expect(cell).toHaveStyle(`width: ${expectedWidth}px`);
}
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add test coverage for additional scenarios

Consider adding tests for:

  • Empty data state
  • Error handling
  • Row hover states
  • Striped rows styling

Example test case for empty state:

it('renders empty state when no data is provided', () => {
  const { container } = render(
    <table>
      <TableBody
        data={[]}
        columns={columns}
        getRowId={getRowId}
        columnWidths={[]}
        selectable={false}
        isSelected={() => false}
        toggleRowSelection={() => {}}
      />
    </table>
  );
  
  expect(container.querySelector('tbody')?.children.length).toBe(0);
});

Comment on lines 179 to 188
<table
className={cx(
styles[`${baseClass}`],
styles[`${baseClass}--${size}`],
styles[`${baseClass}--pinned_${pin}`],
{
[styles[`${baseClass}--stripped`]]: stripped,
}
)}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add ARIA attributes for accessibility

Add necessary ARIA attributes to improve accessibility:

 <table
+  role="grid"
+  aria-label="Data table"
   className={cx(
     styles[`${baseClass}`],
     styles[`${baseClass}--${size}`],
     styles[`${baseClass}--pinned_${pin}`],
     {
       [styles[`${baseClass}--stripped`]]: stripped,
     }
   )}
 >

@vladko-uxds
Copy link

Add please properties to turn on "Stripped” Rows or Columns like on example:

image
image

So the user of component can turn "Stripped " props for a specific rows or columns

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (4)
packages/react-components/src/components/Table/Table.stories.tsx (1)

27-56: Consider using enum for column keys

Enhance type safety by defining column keys as an enum instead of string literals.

+enum TableColumns {
+  ID = 'id',
+  NAME = 'name',
+  AGE = 'age',
+  ROLE = 'role',
+  ACTION = 'action'
+}

 const columns: Column<Data>[] = [
   {
-    key: 'id',
+    key: TableColumns.ID,
     header: 'ID',
     // ...
   },
   // ... apply to other columns
 ];
packages/react-components/src/components/Table/Table.tsx (2)

64-64: Document SCALING_FACTOR constant

Add a comment explaining why 1.05 was chosen as the scaling factor for column resizing.

+  // Multiplier to adjust mouse movement for smoother column resizing
   const SCALING_FACTOR = 1.05;

165-209: Consider extracting selection UI to a separate component

The selection UI logic could be moved to a separate component to improve maintainability.

+ const SelectionBar = ({ selectedCount, toggleSelectAll, rowSelectionMessage, rowActions }) => (
+   <div className={styles[`${selectedClass}`]}>
+     <Checkbox checked={selectedCount === data.length} onChange={toggleSelectAll} />
+     {rowSelectionMessage || <Text size="md">{selectedCount} selected items</Text>}
+     {rowActions}
+   </div>
+ );
packages/react-components/src/components/Table/types.ts (1)

88-107: Move example data types to separate files

The Data and DataForPinningExample types are sample data for examples. Consider moving them to example or test files to keep the component types file clean.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 64a6396 and 56710f8.

📒 Files selected for processing (6)
  • packages/react-components/src/components/Table/Table.module.scss (1 hunks)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.tsx (1 hunks)
  • packages/react-components/src/components/Table/styles/pin.scss (1 hunks)
  • packages/react-components/src/components/Table/styles/strips.scss (1 hunks)
  • packages/react-components/src/components/Table/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-components/src/components/Table/styles/pin.scss
🔇 Additional comments (13)
packages/react-components/src/components/Table/Table.stories.tsx (4)

58-65: Implement required cell heights

According to design requirements, demonstrate all cell height variants:

  • Small: 48px
  • Medium: 56px
  • Large: 64px

82-108: LGTM! Stripped rows implementation matches requirements

The implementation correctly demonstrates both row and column stripping variants.


110-132: Align header height with design specs

Set header height to 48px and use paragraph-sm-bold font as per requirements.


134-161: Add required shadow tokens for pinned elements

Implement shadow tokens as specified:

  • --shadow-fixed-bottom for pinned header
  • --shadow-fixed-right for left column
  • --shadow-fixed-left for right column
packages/react-components/src/components/Table/styles/strips.scss (1)

17-19: Consider row-column hover interaction

The hover effect on striped columns might conflict with row hover states. Consider using a different hover color or implementing mutual exclusivity.

packages/react-components/src/components/Table/Table.module.scss (3)

25-27: Add shadow token for pinned header

Add --shadow-fixed-bottom token for pinned header.


33-37: Use design system typography tokens

Replace hardcoded values with design system tokens.


75-77: Add spacing between label and sorting icon

Add 8px spacing between label and sorting icon.

packages/react-components/src/components/Table/Table.tsx (3)

57-91: Modify column resizing behavior

Current implementation affects both columns during resize.


129-145: Handle undefined or null values during sorting

Add null checks in sorting logic.


179-186: Add ARIA attributes for accessibility

Add necessary ARIA attributes to improve table accessibility.

packages/react-components/src/components/Table/types.ts (2)

6-7: Make 'sortValue' required when 'sortable' is true

To ensure proper sorting functionality, make sortValue required when sortable is true.

Apply this diff:

 export type Column<T> = {
   key: keyof T;
   header: string;
   sortable?: boolean;
-  sortValue?: (item: T) => string | number;
+  sortValue?: sortable extends true ? (item: T) => string | number : never;
 };

60-69: Update 'selectable' prop type and conditionally require selection props

Change selectable to a boolean, and make selectedRows and onSelectionChange required when selectable is true.

Apply this diff:

     resizable?: boolean;
-    selectable?: true;
+    selectable?: boolean;
-    selectedRows?: Set<string | number>;
-    onSelectionChange?: (selectedIds: Set<string | number>) => void;
+    selectedRows: selectable extends true ? Set<string | number> : never;
+    onSelectionChange: selectable extends true ? (selectedIds: Set<string | number>) => void : never;

Comment on lines +67 to +80
export const Resizing: StoryFn = () => {
const getRowId = (row: Data) => row.id;
const data = generateData(4);

return (
<Table
size="medium"
data={data}
columns={columns}
getRowId={getRowId}
resizable
/>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix column resizing behavior

Current implementation affects both sides during resizing. Modify to only adjust the resized side.

background-color: var(--surface-secondary-default);

&:hover {
background-color: var(--surface-primary-disabled) !important;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove !important flag

Avoid using !important as it creates specificity issues. Instead, increase selector specificity.

-  background-color: var(--surface-primary-disabled) !important;
+  background-color: var(--surface-primary-disabled);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
background-color: var(--surface-primary-disabled) !important;
background-color: var(--surface-primary-disabled);

@arturfracala
Copy link
Collaborator Author

Add please properties to turn on "Stripped” Rows or Columns like on example:

image image

So the user of component can turn "Stripped " props for a specific rows or columns

@vladko-uxds it's done. Could you check it?

@marcinsawicki marcinsawicki added the feature New feature or request label Dec 4, 2024
#### Example implementation

```jsx

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add imports for components used in this example

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@marcinsawicki
Copy link
Contributor

Great job with this component. 💪
This will be a good starting point for future improvements of Table.
Please verify the comments and implement changes based on the Vlad UI verification.

@vladko-uxds
Copy link

@arturfracala I see that when column is Stripped, the header still without it, the whole column (Cells + header) should have same background

stripped.header.mp4

@vladko-uxds
Copy link

@arturfracala when column in pinned and we have a hover on a row - the whole row should have hover effect pinned cell too

Uploading hover with pinned column.mp4…

@vladko-uxds
Copy link

@arturfracala For resize columns please increase line width -> 2px
resize line

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
packages/react-components/src/components/Table/types.ts (2)

10-10: Document size specifications in type definition

Add JSDoc comment specifying the pixel heights: Small (48px), Medium (56px), Large (64px).


92-111: Move example types to stories file

Data and DataForPinningExample appear to be example types. Consider moving them to the stories file to keep the core types file focused on reusable types.

packages/react-components/src/components/Table/TableBody.tsx (1)

14-43: Optimize row ID calculation.

The rowId is calculated twice - once for the key and once as a prop. Consider using the cached value.

 return (
   <tbody role="rowgroup">
     {data.map((row) => {
       const rowId = getRowId(row);
       return (
         <TableRow
           key={rowId}
           row={row}
           columns={columns}
           columnWidths={columnWidths}
           selectable={selectable}
           isSelected={isSelected}
           toggleRowSelection={toggleRowSelection}
-          rowId={getRowId(row)}
+          rowId={rowId}
         />
       );
     })}
   </tbody>
 );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 56710f8 and 0716e85.

📒 Files selected for processing (22)
  • packages/react-components/src/components/Table/Table.mdx (1 hunks)
  • packages/react-components/src/components/Table/Table.module.scss (1 hunks)
  • packages/react-components/src/components/Table/Table.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableBody.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableBody.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableRow.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.tsx (1 hunks)
  • packages/react-components/src/components/Table/hooks.ts (1 hunks)
  • packages/react-components/src/components/Table/index.ts (1 hunks)
  • packages/react-components/src/components/Table/stories-constants.tsx (1 hunks)
  • packages/react-components/src/components/Table/stories-helpers.tsx (1 hunks)
  • packages/react-components/src/components/Table/styles/pin.scss (1 hunks)
  • packages/react-components/src/components/Table/styles/size.scss (1 hunks)
  • packages/react-components/src/components/Table/styles/strips.scss (1 hunks)
  • packages/react-components/src/components/Table/types.ts (1 hunks)
  • packages/react-components/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (18)
  • packages/react-components/src/index.ts
  • packages/react-components/src/components/Table/TableRow.module.scss
  • packages/react-components/src/components/Table/stories-helpers.tsx
  • packages/react-components/src/components/Table/styles/size.scss
  • packages/react-components/src/components/Table/TableRow.spec.tsx
  • packages/react-components/src/components/Table/index.ts
  • packages/react-components/src/components/Table/styles/pin.scss
  • packages/react-components/src/components/Table/Table.spec.tsx
  • packages/react-components/src/components/Table/styles/strips.scss
  • packages/react-components/src/components/Table/TableBody.spec.tsx
  • packages/react-components/src/components/Table/TableHeader.tsx
  • packages/react-components/src/components/Table/TableHeader.module.scss
  • packages/react-components/src/components/Table/hooks.ts
  • packages/react-components/src/components/Table/Table.mdx
  • packages/react-components/src/components/Table/TableHeader.spec.tsx
  • packages/react-components/src/components/Table/TableRow.tsx
  • packages/react-components/src/components/Table/Table.module.scss
  • packages/react-components/src/components/Table/stories-constants.tsx
🧰 Additional context used
📓 Learnings (1)
packages/react-components/src/components/Table/Table.tsx (1)
Learnt from: marcinsawicki
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.tsx:57-91
Timestamp: 2024-12-04T07:51:00.505Z
Learning: In the `Table` component, when implementing column resizing, ensure that resizing affects only the column being resized and does not adjust adjacent columns.
🔇 Additional comments (9)
packages/react-components/src/components/Table/types.ts (3)

87-90: LGTM!

The SortConfig interface is well-defined and properly typed.


3-8: ⚠️ Potential issue

Enforce sortValue for sortable columns

When a column is marked as sortable, sortValue should be required to prevent runtime errors.


60-69: ⚠️ Potential issue

Fix selectable prop type and dependencies

The current implementation has two issues:

  1. selectable should be boolean instead of literal true
  2. selectedRows and onSelectionChange should be required when selectable is true
packages/react-components/src/components/Table/TableBody.tsx (1)

4-12: LGTM! Well-structured props interface.

The interface provides good type safety and properly handles optional selection-related props.

packages/react-components/src/components/Table/Table.stories.tsx (2)

27-56: LGTM! Well-structured column definitions.

The column definitions properly implement sorting with appropriate sort value functions.


163-193: 🛠️ Refactor suggestion

Add keyboard navigation support.

Enhance accessibility by implementing keyboard navigation for row selection (e.g., Space to select, Arrow keys to navigate).

packages/react-components/src/components/Table/Table.tsx (3)

58-92: ⚠️ Potential issue

Fix column resizing behavior.

According to previous feedback, column resizing should only affect the resized column, not its neighbors.


130-146: ⚠️ Potential issue

Handle null/undefined values in sorting.

Add null checks to prevent runtime errors during sorting.


180-188: 🛠️ Refactor suggestion

Add ARIA attributes for accessibility.

Enhance screen reader support by adding appropriate ARIA attributes.

Copy link

@vladko-uxds vladko-uxds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the huge contribution 👍

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (5)
packages/react-components/src/components/Table/stories-helpers.tsx (2)

6-38: Consider enhancing data diversity.

The mock data could benefit from more international names and modern tech roles (e.g., "DevOps Engineer", "Cloud Architect", "ML Engineer").


44-44: Consider using UUID for id generation.

Using incremental numbers for IDs could cause conflicts when combining datasets.

-id: index + 1,
+id: crypto.randomUUID(),
packages/react-components/src/components/Table/Table.stories.tsx (1)

171-179: Consider using semantic HTML for action bar dividers.

Replace div-based dividers with <hr> elements for better semantics.

-  const Divider = () => <div className={cx(styles[dividerClass])} />;
+  const Divider = () => <hr className={cx(styles[dividerClass])} />;
packages/react-components/src/components/Table/Table.tsx (2)

38-44: Memoize table hook initialization

Wrap useTable hook initialization with useMemo to prevent unnecessary recalculations.

-  const { isSelected, toggleRowSelection, toggleSelectAll, selectedCount } =
-    useTable({
+  const tableHook = React.useMemo(
+    () => useTable({
       data,
       getRowId,
       selectedRows: selectedRows,
       onSelectionChange: onSelectionChange,
-    });
+    }),
+    [data, getRowId, selectedRows, onSelectionChange]
+  );
+  const { isSelected, toggleRowSelection, toggleSelectAll, selectedCount } = tableHook;

149-154: Handle initialization errors gracefully

Add error handling for column width initialization failures.

 React.useEffect(() => {
+  try {
     const initialWidths = columnRefs.current.map(
       (ref) => ref?.offsetWidth || 0
     );
     setColumnWidths(initialWidths);
+  } catch (error) {
+    console.error('Failed to initialize column widths:', error);
+    setColumnWidths(Array(columns.length).fill(100)); // fallback width
+  }
 }, []);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0716e85 and 1739a96.

📒 Files selected for processing (22)
  • packages/react-components/src/components/Table/Table.mdx (1 hunks)
  • packages/react-components/src/components/Table/Table.module.scss (1 hunks)
  • packages/react-components/src/components/Table/Table.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.stories.tsx (1 hunks)
  • packages/react-components/src/components/Table/Table.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableBody.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableBody.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableHeader.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.module.scss (1 hunks)
  • packages/react-components/src/components/Table/TableRow.spec.tsx (1 hunks)
  • packages/react-components/src/components/Table/TableRow.tsx (1 hunks)
  • packages/react-components/src/components/Table/hooks.ts (1 hunks)
  • packages/react-components/src/components/Table/index.ts (1 hunks)
  • packages/react-components/src/components/Table/stories-constants.tsx (1 hunks)
  • packages/react-components/src/components/Table/stories-helpers.tsx (1 hunks)
  • packages/react-components/src/components/Table/styles/pin.scss (1 hunks)
  • packages/react-components/src/components/Table/styles/size.scss (1 hunks)
  • packages/react-components/src/components/Table/styles/strips.scss (1 hunks)
  • packages/react-components/src/components/Table/types.ts (1 hunks)
  • packages/react-components/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (17)
  • packages/react-components/src/index.ts
  • packages/react-components/src/components/Table/TableRow.module.scss
  • packages/react-components/src/components/Table/styles/size.scss
  • packages/react-components/src/components/Table/styles/strips.scss
  • packages/react-components/src/components/Table/styles/pin.scss
  • packages/react-components/src/components/Table/TableRow.spec.tsx
  • packages/react-components/src/components/Table/TableBody.spec.tsx
  • packages/react-components/src/components/Table/index.ts
  • packages/react-components/src/components/Table/Table.spec.tsx
  • packages/react-components/src/components/Table/TableHeader.tsx
  • packages/react-components/src/components/Table/TableRow.tsx
  • packages/react-components/src/components/Table/TableHeader.spec.tsx
  • packages/react-components/src/components/Table/Table.mdx
  • packages/react-components/src/components/Table/hooks.ts
  • packages/react-components/src/components/Table/TableHeader.module.scss
  • packages/react-components/src/components/Table/stories-constants.tsx
  • packages/react-components/src/components/Table/Table.module.scss
🧰 Additional context used
📓 Learnings (1)
packages/react-components/src/components/Table/Table.tsx (1)
Learnt from: marcinsawicki
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.tsx:57-91
Timestamp: 2024-12-04T07:51:00.505Z
Learning: In the `Table` component, when implementing column resizing, ensure that resizing affects only the column being resized and does not adjust adjacent columns.
🔇 Additional comments (8)
packages/react-components/src/components/Table/stories-helpers.tsx (2)

1-3: LGTM! Imports are clean and purposeful.


43-50: Verify Button size prop value.

Ensure that "xcompact" is a valid size option for the Button component.

✅ Verification successful

Button size "xcompact" is valid

The Button component explicitly supports 'xcompact' size as defined in packages/react-components/src/components/Button/types.ts:

export type ButtonSize = 'xcompact' | 'compact' | 'medium' | 'large';
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Button component size prop definition
ast-grep --pattern 'interface ButtonProps {
  $$$
  size?: $_;
  $$$
}'

Length of output: 73


Script:

#!/bin/bash
# Let's try a broader search for Button component props and size-related definitions
rg -A 5 "type ButtonSize|interface ButtonProps|enum ButtonSize" 

# Also search for actual usage of xcompact size in Button components
rg "size=.xcompact.|size=\"xcompact\""

# Search for any size-related constants or types for Button
rg "ButtonSize|BUTTON_SIZES"

Length of output: 2753

packages/react-components/src/components/Table/TableBody.tsx (2)

24-24: LGTM! Proper ARIA role implementation.

The role="rowgroup" attribute is correctly implemented for accessibility.


37-37: ⚠️ Potential issue

Remove redundant rowId calculation.

The rowId is already calculated in line 26 and should be reused instead of calling getRowId again.

-            rowId={getRowId(row)}
+            rowId={rowId}

Likely invalid or redundant comment.

packages/react-components/src/components/Table/Table.tsx (4)

58-92: ⚠️ Potential issue

Fix column resizing behavior per requirements

  1. Remove adjacent column width adjustment
  2. Add visual feedback with 2px resize handle
 const handleMouseMove = (moveEvent: MouseEvent) => {
   const delta = (moveEvent.clientX - startX) * SCALING_FACTOR;
   setColumnWidths((prevWidths) => {
     const newWidths = [...prevWidths];
     const newWidth = Math.max(50, startWidth + delta);
-    const adjustedNextWidth = Math.max(50, nextWidth - delta);
     newWidths[index] = newWidth;
-    if (index + 1 < newWidths.length) {
-      newWidths[index + 1] = adjustedNextWidth;
-    }
     return newWidths;
   });
 };

130-146: ⚠️ Potential issue

Handle edge cases in sorting

Add null/undefined handling to prevent runtime errors.

 return [...data].sort((a, b) => {
   const sortValueA = column.sortValue
     ? column.sortValue(a)
     : a[sortConfig.key as unknown as keyof T];
   const sortValueB = column.sortValue
     ? column.sortValue(b)
     : b[sortConfig.key as unknown as keyof T];
+  
+  if (sortValueA == null && sortValueB == null) return 0;
+  if (sortValueA == null) return 1;
+  if (sortValueB == null) return -1;

   if (sortValueA < sortValueB) {

18-32: 🛠️ Refactor suggestion

Add accessibility attributes and size prop validation

Add ARIA attributes and validate size prop against the required dimensions:

  • Small: 48px
  • Medium: 56px
  • Large: 64px
 export const Table = <T,>({
   data,
   columns,
   stripped,
-  size = 'medium',
+  size = 'medium' as const,
   pin,
   selectable,
   getRowId,
   resizable,
   selectedRows,
   onSelectionChange,
   rowSelectionMessage,
   rowActions,
   testId,
-}: ITableProps<T>) => {
+}: ITableProps<T> & { size?: 'small' | 'medium' | 'large' }) => {
⛔ Skipped due to learnings
Learnt from: marcinsawicki
PR: livechat/design-system#1438
File: packages/react-components/src/components/Table/Table.mdx:11-11
Timestamp: 2024-12-04T07:31:14.364Z
Learning: When reviewing documentation files for the Table component, avoid suggesting adding specific sizing and styling specifications to the introduction unless explicitly requested.

189-199: 🛠️ Refactor suggestion

Implement header styling requirements

Add missing header specifications from PR objectives:

  • Header height: 48px
  • Font size: paragraph-sm-bold
  • Icon spacing: 8px

Comment on lines +40 to +41
const getRandomElement = <T,>(arr: T[]): T =>
arr[Math.floor(Math.random() * arr.length)];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add safety check for empty arrays.

The getRandomElement function should handle empty arrays to prevent undefined behavior.

-const getRandomElement = <T,>(arr: T[]): T =>
-  arr[Math.floor(Math.random() * arr.length)];
+const getRandomElement = <T,>(arr: T[]): T => {
+  if (arr.length === 0) {
+    throw new Error('Cannot get random element from empty array');
+  }
+  return arr[Math.floor(Math.random() * arr.length)];
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getRandomElement = <T,>(arr: T[]): T =>
arr[Math.floor(Math.random() * arr.length)];
const getRandomElement = <T,>(arr: T[]): T => {
if (arr.length === 0) {
throw new Error('Cannot get random element from empty array');
}
return arr[Math.floor(Math.random() * arr.length)];
};

/**
* Enables row selection mode, adding checkboxes to each row and the header for selection.
*/
selectable?: true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix selectable prop type and add conditional requirements.

The selectable prop should be boolean, and selectedRows/onSelectionChange should be required when selectable is true.

-  selectable?: true;
+  selectable?: boolean;
-  selectedRows?: Set<string | number>;
-  onSelectionChange?: (selectedIds: Set<string | number>) => void;
+  selectedRows: selectable extends true ? Set<string | number> : never;
+  onSelectionChange: selectable extends true ? (selectedIds: Set<string | number>) => void : never;

Also applies to: 64-64, 69-69

Comment on lines +163 to +193
export const MultiSelect: StoryFn = () => {
const getRowId = (row: Data) => row.id;
const data = generateData(4);
const [selectedRows, setSelectedRows] = React.useState<Set<string | number>>(
new Set()
);

const Divider = () => <div className={cx(styles[dividerClass])} />;

const rowActions = (
<div className={cx(styles[actionBarClass])}>
<Divider />
<Button kind="link">Action</Button> <Button kind="link">Action</Button>
<Divider />
<Button kind="link">Action</Button> <Button kind="link">Action</Button>
</div>
);

return (
<Table
size="medium"
data={data}
columns={columns}
getRowId={getRowId}
selectable
selectedRows={selectedRows}
onSelectionChange={setSelectedRows}
rowActions={rowActions}
/>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance accessibility with keyboard navigation.

Add keyboard navigation support for row selection:

  • Space to select/deselect
  • Shift+Arrow for multiple selection
  • Tab navigation between rows

@arturfracala arturfracala merged commit e52e072 into main Dec 10, 2024
6 checks passed
@arturfracala arturfracala deleted the feature/DPS-4377 branch December 10, 2024 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
Development

Successfully merging this pull request may close these issues.

4 participants