diff --git a/.changeset/fair-lamps-relax.md b/.changeset/fair-lamps-relax.md new file mode 100644 index 00000000000..5eae2e4eb6c --- /dev/null +++ b/.changeset/fair-lamps-relax.md @@ -0,0 +1,5 @@ +--- +"@itwin/itwinui-css": minor +--- + +The filter button inside a Table will now always be visible, instead of only being shown on hover/focus. diff --git a/.changeset/six-pans-guess.md b/.changeset/six-pans-guess.md new file mode 100644 index 00000000000..d84d2d1488e --- /dev/null +++ b/.changeset/six-pans-guess.md @@ -0,0 +1,5 @@ +--- +"@itwin/itwinui-react": minor +--- + +The filter button inside a Table will now always be visible, instead of only being shown on hover/focus. diff --git a/.changeset/tender-seas-shake.md b/.changeset/tender-seas-shake.md new file mode 100644 index 00000000000..6041efed6c4 --- /dev/null +++ b/.changeset/tender-seas-shake.md @@ -0,0 +1,8 @@ +--- +"@itwin/itwinui-react": minor +--- + +The responsive behavior of Table columns has been improved in a few different ways: +- All columns now have a non-zero default min-width. While we still recommend passing a custom min-width based on your data, this default will help prevent resizable columns from becoming too small. +- The filter and sort icons in a column header will now wrap to the next line, before the text starts wrapping. +- For cells that have a string value, the value will be automatically truncated after three lines. diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_dark_0_demo-edit_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_dark_0_demo-edit_0_desktop.png index caf0ed3888b..ba72e13e6bd 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_dark_0_demo-edit_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_dark_0_demo-edit_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_hc_dark_0_demo-edit_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_hc_dark_0_demo-edit_0_desktop.png index 1356dcb2e31..ea48c90a4f4 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_hc_dark_0_demo-edit_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_hc_dark_0_demo-edit_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_hc_light_0_demo-edit_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_hc_light_0_demo-edit_0_desktop.png index 6441d3e78b8..8138e415a5d 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_hc_light_0_demo-edit_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_hc_light_0_demo-edit_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_light_0_demo-edit_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_light_0_demo-edit_0_desktop.png index e2fedd594cb..43c9c48dfe4 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_light_0_demo-edit_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_information-panel_Type_default_light_0_demo-edit_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_dark_0_demo-defaultiui-tablenth-child1_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_dark_0_demo-defaultiui-tablenth-child1_0_desktop.png index a3d4db52e96..5c51b899b6d 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_dark_0_demo-defaultiui-tablenth-child1_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_dark_0_demo-defaultiui-tablenth-child1_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_hc_dark_0_demo-defaultiui-tablenth-child1_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_hc_dark_0_demo-defaultiui-tablenth-child1_0_desktop.png index ec693dfb476..6935abd92e7 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_hc_dark_0_demo-defaultiui-tablenth-child1_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_hc_dark_0_demo-defaultiui-tablenth-child1_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_hc_light_0_demo-defaultiui-tablenth-child1_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_hc_light_0_demo-defaultiui-tablenth-child1_0_desktop.png index 8c704dec71a..3a982fd1f83 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_hc_light_0_demo-defaultiui-tablenth-child1_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_hc_light_0_demo-defaultiui-tablenth-child1_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_light_0_demo-defaultiui-tablenth-child1_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_light_0_demo-defaultiui-tablenth-child1_0_desktop.png index 44af1000a38..d96c4d42d7f 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_light_0_demo-defaultiui-tablenth-child1_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Editable_cell_light_0_demo-defaultiui-tablenth-child1_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_dark_0_demo-default_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_dark_0_demo-default_0_desktop.png index 8ff0c244d2e..88f929e61c7 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_dark_0_demo-default_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_dark_0_demo-default_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_hc_dark_0_demo-default_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_hc_dark_0_demo-default_0_desktop.png index c33196b616d..8971187aa07 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_hc_dark_0_demo-default_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_hc_dark_0_demo-default_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_hc_light_0_demo-default_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_hc_light_0_demo-default_0_desktop.png index 06c788bed52..8cc777b35a3 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_hc_light_0_demo-default_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_hc_light_0_demo-default_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_light_0_demo-default_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_light_0_demo-default_0_desktop.png index 73ec5e2b41e..162be214152 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_light_0_demo-default_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Default_light_0_demo-default_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_dark_0_demo-empty-state_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_dark_0_demo-empty-state_0_desktop.png index cc3275ebb9e..849c2a416ae 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_dark_0_demo-empty-state_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_dark_0_demo-empty-state_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_hc_dark_0_demo-empty-state_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_hc_dark_0_demo-empty-state_0_desktop.png index 24d90faf7de..2cb70918475 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_hc_dark_0_demo-empty-state_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_hc_dark_0_demo-empty-state_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_hc_light_0_demo-empty-state_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_hc_light_0_demo-empty-state_0_desktop.png index b478c1ab93a..98c0c55d54d 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_hc_light_0_demo-empty-state_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_hc_light_0_demo-empty-state_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_light_0_demo-empty-state_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_light_0_demo-empty-state_0_desktop.png index 8e946903188..87e19286f1c 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_light_0_demo-empty-state_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Empty_light_0_demo-empty-state_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_dark_0_demo-expandable-rows_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_dark_0_demo-expandable-rows_0_desktop.png index 6ce8f9198d6..03f0c2665f1 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_dark_0_demo-expandable-rows_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_dark_0_demo-expandable-rows_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_hc_dark_0_demo-expandable-rows_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_hc_dark_0_demo-expandable-rows_0_desktop.png index 114af418797..3cc44f26b48 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_hc_dark_0_demo-expandable-rows_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_hc_dark_0_demo-expandable-rows_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_hc_light_0_demo-expandable-rows_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_hc_light_0_demo-expandable-rows_0_desktop.png index 09d366c1396..29805126818 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_hc_light_0_demo-expandable-rows_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_hc_light_0_demo-expandable-rows_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_light_0_demo-expandable-rows_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_light_0_demo-expandable-rows_0_desktop.png index 35a216204d1..652a725be5a 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_light_0_demo-expandable-rows_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Expandable_Rows_light_0_demo-expandable-rows_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_dark_0_demo-extras_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_dark_0_demo-extras_0_desktop.png index e15e08a7574..7e59b57eff2 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_dark_0_demo-extras_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_dark_0_demo-extras_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_hc_dark_0_demo-extras_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_hc_dark_0_demo-extras_0_desktop.png index 86ba1f356e4..1942982f23d 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_hc_dark_0_demo-extras_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_hc_dark_0_demo-extras_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_hc_light_0_demo-extras_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_hc_light_0_demo-extras_0_desktop.png index 113ff32c775..03cbae0ad0f 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_hc_light_0_demo-extras_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_hc_light_0_demo-extras_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_light_0_demo-extras_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_light_0_demo-extras_0_desktop.png index 3e19e47663f..eee2c158b2a 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_light_0_demo-extras_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Extras_light_0_demo-extras_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_dark_0_demo-loading_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_dark_0_demo-loading_0_desktop.png index 4d850c34380..c64a0caa684 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_dark_0_demo-loading_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_dark_0_demo-loading_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_hc_dark_0_demo-loading_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_hc_dark_0_demo-loading_0_desktop.png index c3ba20dbb6a..b380adf81bb 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_hc_dark_0_demo-loading_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_hc_dark_0_demo-loading_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_hc_light_0_demo-loading_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_hc_light_0_demo-loading_0_desktop.png index 51876ea0e4c..075806c3034 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_hc_light_0_demo-loading_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_hc_light_0_demo-loading_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_light_0_demo-loading_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_light_0_demo-loading_0_desktop.png index f957d3152c0..a999a54f8ba 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_light_0_demo-loading_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Loading_light_0_demo-loading_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_dark_0_demo-status_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_dark_0_demo-status_0_desktop.png index 0421a539c15..9fead748030 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_dark_0_demo-status_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_dark_0_demo-status_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_hc_dark_0_demo-status_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_hc_dark_0_demo-status_0_desktop.png index f8a90b0ca83..7d47c450382 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_hc_dark_0_demo-status_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_hc_dark_0_demo-status_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_hc_light_0_demo-status_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_hc_light_0_demo-status_0_desktop.png index ef183a64e3c..8cb6c961cca 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_hc_light_0_demo-status_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_hc_light_0_demo-status_0_desktop.png differ diff --git a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_light_0_demo-status_0_desktop.png b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_light_0_demo-status_0_desktop.png index a99be89cd52..360a518b485 100644 Binary files a/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_light_0_demo-status_0_desktop.png and b/apps/css-workshop/backstop/results/bitmaps_reference/iTwinUI_table_Type_Status_light_0_demo-status_0_desktop.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Column Manager.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Column Manager.png index f87e80073b7..1518e50ad15 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Column Manager.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Column Manager.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Condensed.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Condensed.png index c88a7f90d44..80ca0c0a57c 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Condensed.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Condensed.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Custom Filter.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Custom Filter.png index 9b17fe38065..7a1ade47a32 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Custom Filter.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Custom Filter.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Editable.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Editable.png index 71bf236cef1..be27e59c588 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Editable.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Editable.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Expandable Subrows.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Expandable Subrows.png index e9e49130c16..2c5dd0e8be8 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Expandable Subrows.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Expandable Subrows.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Filters.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Filters.png index 8b7dd34b8a1..bc0c1778379 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Filters.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Filters.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Full.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Full.png index 4d850a7a5ff..7c1db0d38b8 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Full.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Full.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Full2.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Full2.png index 080aef3697e..f6462411b25 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Full2.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Full2.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Global Filter.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Global Filter.png index 895454020c2..889a0d6fb82 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Global Filter.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Global Filter.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Initial State.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Initial State.png index f941e782abb..be81f004bae 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Initial State.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Initial State.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Localized.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Localized.png index 96419ab45ab..a99c757fffe 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Localized.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Localized.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Resizable Columns.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Resizable Columns.png index a79b2fe6067..d9e3c8bd5ea 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Resizable Columns.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Resizable Columns.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Status And Cell Icons.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Status And Cell Icons.png index 13c629d3ca2..5415bf85eba 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Status And Cell Icons.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Status And Cell Icons.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-With Manual Paginator And Filter.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-With Manual Paginator And Filter.png index 00023fb680a..1e4d9cd894f 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-With Manual Paginator And Filter.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-With Manual Paginator And Filter.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-With Paginator.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-With Paginator.png index dd28d3606c7..8d05192c633 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-With Paginator.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-With Paginator.png differ diff --git a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Zebra Striped Rows.png b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Zebra Striped Rows.png index 00f38f2f3e5..ca341ce0b4f 100755 Binary files a/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Zebra Striped Rows.png and b/apps/react-workshop/cypress-visual-screenshots/baseline/Table.test.ts-Zebra Striped Rows.png differ diff --git a/packages/itwinui-css/src/table/base.scss b/packages/itwinui-css/src/table/base.scss index 0499c304add..13acd8ef01a 100644 --- a/packages/itwinui-css/src/table/base.scss +++ b/packages/itwinui-css/src/table/base.scss @@ -57,10 +57,6 @@ cursor: grabbing; } - .iui-table-filter-button:not([data-iui-active='true']) { - opacity: 0; - } - > .iui-table-resizer { block-size: 100%; inline-size: var(--iui-size-m); @@ -93,23 +89,14 @@ opacity: 1; } - &:is(:hover, :focus-visible, :has(:focus-visible)) { + &:is(:hover, :focus-visible), + &:has(:focus-visible) { background-color: var(--iui-color-background-hover); - :is(.iui-table-sort, .iui-table-filter-button) { + .iui-table-sort { opacity: 1; } } - - @supports not selector(:has(+ *)) { - &:focus-within { - background-color: var(--iui-color-background-hover); - - :is(.iui-table-sort, .iui-table-filter-button) { - opacity: 1; - } - } - } } .iui-table-reorder-bar { @@ -336,14 +323,12 @@ flex-grow: 1; align-items: center; flex-wrap: wrap; - justify-content: flex-end; margin-inline-end: var(--iui-size-s); .iui-table-cell-end-icon { - // Hardcoded size of the borderless button so that the icons are centered - inline-size: 28px; - block-size: 28px; - margin-inline-end: initial; + inline-size: unset; + block-size: unset; + margin: 0; margin-inline-start: auto; } } diff --git a/packages/itwinui-react/setupTests.ts b/packages/itwinui-react/setupTests.ts index 6b843a10f5a..7b292b8b01d 100644 --- a/packages/itwinui-react/setupTests.ts +++ b/packages/itwinui-react/setupTests.ts @@ -22,6 +22,26 @@ vi.mock('./src/core/utils/hooks/useGlobals.js', () => { }; }); +vi.mock('@testing-library/react', async () => { + const originalRtl = await vi.importActual< + typeof import('@testing-library/react') + >('@testing-library/react'); + + return { + ...originalRtl, + /** + * Wrapper over `@testing-library/react`'s `render()` that also waits for all + * microtasks to be flushed. This is necessary for ShadowRoot to be tested properly. + */ + render: (...args: Parameters) => { + vi.useFakeTimers({ toFake: ['queueMicrotask'] }); + const result = originalRtl.render(...args); + originalRtl.act(() => vi.runAllTicks()); + return result; + }, + }; +}); + afterEach(() => { vi.useRealTimers(); }); diff --git a/packages/itwinui-react/src/core/Table/SubRowExpander.tsx b/packages/itwinui-react/src/core/Table/SubRowExpander.tsx index 80ea651f8ec..bc041a0e2d3 100644 --- a/packages/itwinui-react/src/core/Table/SubRowExpander.tsx +++ b/packages/itwinui-react/src/core/Table/SubRowExpander.tsx @@ -13,12 +13,13 @@ export type SubRowExpanderProps> = { isDisabled: boolean; cellProps: CellProps; density?: 'default' | 'condensed' | 'extra-condensed'; + [k: string]: unknown; }; export const SubRowExpander = >( props: SubRowExpanderProps, ) => { - const { cell, isDisabled, cellProps, expanderCell, density } = props; + const { cell, isDisabled, cellProps, expanderCell, density, ...rest } = props; return ( <> @@ -40,6 +41,7 @@ export const SubRowExpander = >( cell.row.toggleRowExpanded(); }} disabled={isDisabled} + {...rest} > { { ); }); -it('should handle resize by increasing width of current column and decreasing the next ones', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(resizer).toBeTruthy(); - - fireEvent.mouseDown(resizer, { clientX: 100 }); - fireEvent.mouseMove(resizer, { clientX: 150 }); - fireEvent.mouseUp(resizer); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('50px'); - expect(headerCells[2].style.width).toBe('100px'); -}); - -it('should handle resize with touch', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(resizer).toBeTruthy(); - - fireEvent.touchStart(resizer, { touches: [{ clientX: 100 }] }); - fireEvent.touchMove(resizer, { touches: [{ clientX: 150 }] }); - fireEvent.touchEnd(resizer); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('50px'); - expect(headerCells[2].style.width).toBe('100px'); -}); - -it.each(['px', '%', 'rem'])( - 'should handle resize when widths are string units. (E.g. %s)', - (unit) => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - width: `20${unit}`, - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - width: `20${unit}`, - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - width: `60${unit}`, - }, - ]; - - const { container } = renderComponent({ - columns, - isResizable: true, - }); - - const resizers = container.querySelectorAll( - '.iui-table-resizer', - ) as NodeListOf; - - expect(resizers).toBeTruthy(); - resizers.forEach((resizer) => expect(resizer).toBeTruthy()); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - // Initial - expect(headerCells[0].style.width).toBe(`20${unit}`); - expect(headerCells[1].style.width).toBe(`20${unit}`); - expect(headerCells[2].style.width).toBe(`60${unit}`); - - // Drag first resizer - fireEvent.mouseDown(resizers[0], { clientX: 100 }); - fireEvent.mouseMove(resizers[0], { clientX: 150 }); - fireEvent.mouseUp(resizers[0]); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('50px'); - expect(headerCells[2].style.width).toBe(`60${unit}`); - - // Drag second resizer - fireEvent.mouseDown(resizers[1], { clientX: 200 }); - fireEvent.mouseMove(resizers[1], { clientX: 250 }); - fireEvent.mouseUp(resizers[1]); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('100px'); - expect(headerCells[2].style.width).toBe('50px'); - }, -); - -it('should prevent from resizing past 1px width', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(resizer).toBeTruthy(); - - fireEvent.mouseDown(resizer, { clientX: 100 }); - fireEvent.mouseMove(resizer, { clientX: 198 }); - fireEvent.mouseMove(resizer, { clientX: 300 }); - fireEvent.mouseUp(resizer); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - expect(headerCells[0].style.width).toBe('198px'); - expect(headerCells[1].style.width).toBe('2px'); - expect(headerCells[2].style.width).toBe('100px'); -}); - -it('should prevent from resizing past max-width', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - maxWidth: 150, - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - maxWidth: 150, - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(resizer).toBeTruthy(); - - // Current column - fireEvent.mouseDown(resizer, { clientX: 100 }); - fireEvent.mouseMove(resizer, { clientX: 150 }); - fireEvent.mouseMove(resizer, { clientX: 200 }); - fireEvent.mouseUp(resizer); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('50px'); - expect(headerCells[2].style.width).toBe('100px'); - - // Next column - fireEvent.mouseDown(resizer, { clientX: 150 }); - fireEvent.mouseMove(resizer, { clientX: 50 }); - fireEvent.mouseMove(resizer, { clientX: 10 }); - fireEvent.mouseUp(resizer); - - expect(headerCells[0].style.width).toBe('50px'); - expect(headerCells[1].style.width).toBe('150px'); - expect(headerCells[2].style.width).toBe('100px'); -}); - -it('should prevent from resizing past min-width', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - minWidth: 50, - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - minWidth: 50, - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(resizer).toBeTruthy(); - - // Current column - fireEvent.mouseDown(resizer, { clientX: 100 }); - fireEvent.mouseMove(resizer, { clientX: 50 }); - fireEvent.mouseMove(resizer, { clientX: 10 }); - fireEvent.mouseUp(resizer); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - expect(headerCells[0].style.width).toBe('50px'); - expect(headerCells[1].style.width).toBe('150px'); - expect(headerCells[2].style.width).toBe('100px'); - - // Next column - fireEvent.mouseDown(resizer, { clientX: 50 }); - fireEvent.mouseMove(resizer, { clientX: 150 }); - fireEvent.mouseMove(resizer, { clientX: 190 }); - fireEvent.mouseUp(resizer); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('50px'); - expect(headerCells[2].style.width).toBe('100px'); -}); - -it('should not resize column with disabled resize but resize closest ones', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - disableResizing: true, - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - disableResizing: true, - }, - { - id: 'edit', - Header: 'edit', - Cell: () => <>Edit, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - // Current column - const nameResizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(nameResizer).toBeTruthy(); - - fireEvent.mouseDown(nameResizer, { clientX: 100 }); - fireEvent.mouseMove(nameResizer, { clientX: 150 }); - fireEvent.mouseUp(nameResizer); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(4); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('100px'); - expect(headerCells[2].style.width).toBe('100px'); - expect(headerCells[3].style.width).toBe('50px'); - - // Description column shouldn't have resizer because resizing is disabled for it - // and next column also isn't resizable - const descriptionResizer = container.querySelector( - '.iui-table-cell:nth-of-type(2) .iui-table-resizer', - ) as HTMLDivElement; - expect(descriptionResizer).toBeFalsy(); - - // Last column - const viewResizer = container.querySelector( - '.iui-table-cell:nth-of-type(3) .iui-table-resizer', - ) as HTMLDivElement; - expect(viewResizer).toBeTruthy(); - - fireEvent.mouseDown(viewResizer, { clientX: 350 }); - fireEvent.mouseMove(viewResizer, { clientX: 250 }); - fireEvent.mouseUp(viewResizer); - - expect(headerCells[0].style.width).toBe('50px'); - expect(headerCells[1].style.width).toBe('100px'); - expect(headerCells[2].style.width).toBe('100px'); - expect(headerCells[3].style.width).toBe('150px'); -}); - it('should not show resizer when there are no next resizable columns', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); const columns: Column[] = [ { id: 'name', @@ -3052,9 +2629,6 @@ it('should not show resizer when there are no next resizable columns', () => { }); it('should not trigger sort when resizing', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); const onSort = vi.fn(); const columns: Column[] = [ { @@ -3080,78 +2654,14 @@ it('should not trigger sort when resizing', () => { onSort, }); - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - const resizer = container.querySelector( '.iui-table-resizer', ) as HTMLDivElement; expect(resizer).toBeTruthy(); - - fireEvent.mouseDown(resizer, { clientX: 100 }); - fireEvent.mouseMove(resizer, { clientX: 150 }); - fireEvent.mouseUp(resizer); fireEvent.click(resizer); - expect(onSort).not.toHaveBeenCalled(); }); -it('should handle table resize only when some columns were resized', () => { - const htmlWidthMock = vi - .spyOn(HTMLElement.prototype, 'getBoundingClientRect') - .mockReturnValue({ width: 100 } as DOMRect); - - let triggerResize: (size: DOMRectReadOnly) => void = vi.fn(); - vi.spyOn(UseResizeObserver, 'useResizeObserver').mockImplementation( - (onResize) => { - triggerResize = onResize; - return [vi.fn(), { disconnect: vi.fn() } as unknown as ResizeObserver]; - }, - ); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ columns, isResizable: true }); - - // Initial render - triggerResize({ width: 300 } as DOMRectReadOnly); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - headerCells.forEach((cell) => expect(cell.style.width).toBe('0px')); - - const resizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(resizer).toBeTruthy(); - - fireEvent.mouseDown(resizer, { clientX: 100 }); - fireEvent.mouseMove(resizer, { clientX: 150 }); - fireEvent.mouseUp(resizer); - - act(() => { - htmlWidthMock.mockReturnValue({ width: 50 } as DOMRect); - triggerResize({ width: 150 } as DOMRectReadOnly); - }); - headerCells.forEach((cell) => expect(cell.style.width).toBe('50px')); -}); - it('should not render resizer when resizer is disabled', () => { const { container } = renderComponent(undefined); @@ -3166,273 +2676,6 @@ it('should not render resizer when resizer is disabled', () => { expect(resizer).toBeFalsy(); }); -it('should resize only the current column when resize mode is expand', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - columnResizeMode: 'expand', - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizers = container.querySelectorAll('.iui-table-resizer'); - // Every column should have a resizer - expect(resizers.length).toBe(3); - - fireEvent.mouseDown(resizers[0], { clientX: 100 }); - fireEvent.mouseMove(resizers[0], { clientX: 150 }); - fireEvent.mouseUp(resizers[0]); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('100px'); - expect(headerCells[2].style.width).toBe('100px'); -}); - -it('should resize current and closest column when table width would decrease when resize mode is expand', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - let triggerResize: (size: DOMRectReadOnly) => void = vi.fn(); - vi.spyOn(UseResizeObserver, 'useResizeObserver').mockImplementation( - (onResize) => { - triggerResize = onResize; - return [vi.fn(), { disconnect: vi.fn() } as unknown as ResizeObserver]; - }, - ); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - columnResizeMode: 'expand', - }); - - // Initial render - triggerResize({ width: 300 } as DOMRectReadOnly); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(resizer).toBeTruthy(); - - // Resize past table width - fireEvent.mouseDown(resizer, { clientX: 150 }); - fireEvent.mouseMove(resizer, { clientX: 100 }); - fireEvent.mouseMove(resizer, { clientX: 50 }); - fireEvent.mouseUp(resizer); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - // Headers widths sum are not lower than table width - expect(headerCells[0].style.width).toBe('50px'); - expect(headerCells[1].style.width).toBe('150px'); - expect(headerCells[2].style.width).toBe('100px'); -}); - -it('should resize last and closest column on the left when table width would decrease when resize mode is expand', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - let triggerResize: (size: DOMRectReadOnly) => void = vi.fn(); - vi.spyOn(UseResizeObserver, 'useResizeObserver').mockImplementation( - (onResize) => { - triggerResize = onResize; - return [vi.fn(), { disconnect: vi.fn() } as unknown as ResizeObserver]; - }, - ); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - columnResizeMode: 'expand', - }); - - // Initial render - triggerResize({ width: 300 } as DOMRectReadOnly); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizers = container.querySelectorAll('.iui-table-resizer'); - expect(resizers.length).toBe(3); - - // Resize past table width - fireEvent.mouseDown(resizers[2], { clientX: 300 }); - fireEvent.mouseMove(resizers[2], { clientX: 250 }); - // fireEvent.mouseMove(resizers[2], { clientX: 50 }); - fireEvent.mouseUp(resizers[2]); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - // Headers widths sum are not lower than table width - expect(headerCells[0].style.width).toBe('100px'); - expect(headerCells[1].style.width).toBe('150px'); - expect(headerCells[2].style.width).toBe('50px'); -}); - -it('should not show resizer when column has disabled resizing when resize mode is expand', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - disableResizing: true, - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - const { container } = renderComponent({ - columns, - isResizable: true, - columnResizeMode: 'expand', - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const descriptionResizer = container.querySelector( - '.iui-cell:nth-of-type(2) .iui-resizer', - ) as HTMLDivElement; - expect(descriptionResizer).toBeFalsy(); -}); - -it('should stop resizing when mouse leaves the screen', () => { - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ - width: 100, - } as DOMRect); - const columns: Column[] = [ - { - id: 'name', - Header: 'Name', - accessor: 'name', - }, - { - id: 'description', - Header: 'description', - accessor: 'description', - }, - { - id: 'view', - Header: 'view', - Cell: () => <>View, - }, - ]; - let resizeEndCount = 0; - const { container } = renderComponent({ - columns, - isResizable: true, - stateReducer: (newState, action) => { - if (action.type === actions.columnDoneResizing) { - resizeEndCount++; - } - return newState; - }, - }); - - const rows = container.querySelectorAll('.iui-table-body .iui-table-row'); - expect(rows.length).toBe(3); - - const resizer = container.querySelector( - '.iui-table-resizer', - ) as HTMLDivElement; - expect(resizer).toBeTruthy(); - - fireEvent.mouseDown(resizer, { clientX: 100 }); - fireEvent.mouseMove(resizer, { clientX: 150 }); - fireEvent.mouseLeave(resizer.ownerDocument); - fireEvent.mouseLeave(resizer.ownerDocument); - fireEvent.mouseMove(resizer, { clientX: 50 }); - fireEvent.mouseLeave(resizer.ownerDocument); - - const headerCells = container.querySelectorAll( - '.iui-table-header .iui-table-cell', - ); - expect(headerCells).toHaveLength(3); - - expect(headerCells[0].style.width).toBe('150px'); - expect(headerCells[1].style.width).toBe('50px'); - expect(headerCells[2].style.width).toBe('100px'); - - expect(resizeEndCount).toBe(1); -}); - it('should render zebra striped table', () => { const { container } = renderComponent({ styleType: 'zebra-rows' }); @@ -4629,18 +3872,6 @@ it('should render row with loading status', () => { expect(rows[1]).not.toHaveClass(`iui-loading`); }); -it('should navigate through table sorting with the keyboard', async () => { - const onSort = vi.fn(); - renderComponent({ - isSortable: true, - onSort, - }); - - await userEvent.tab(); // tab to sort icon button - await userEvent.keyboard('{Enter}'); - expect(onSort).toHaveBeenCalledTimes(1); -}); - it('should navigate through table filtering with the keyboard', async () => { const onFilter = vi.fn(); const mockedColumns = [ diff --git a/packages/itwinui-react/src/core/Table/Table.tsx b/packages/itwinui-react/src/core/Table/Table.tsx index 4a85d8d9a89..d14b99b15fe 100644 --- a/packages/itwinui-react/src/core/Table/Table.tsx +++ b/packages/itwinui-react/src/core/Table/Table.tsx @@ -35,6 +35,8 @@ import { useLayoutEffect, Box, createWarningLogger, + ShadowRoot, + LineClamp, useMergedRefs, } from '../utils/index.js'; import type { CommonProps } from '../utils/index.js'; @@ -70,6 +72,11 @@ const shiftRowSelectedAction = 'shiftRowSelected'; export const tableResizeStartAction = 'tableResizeStart'; const tableResizeEndAction = 'tableResizeEnd'; +const COLUMN_MIN_WIDTHS = { + default: 72, + withExpander: 108, // expander column should be wider to accommodate the expander icon +}; + const logWarningInDev = createWarningLogger(); export type TablePaginatorRendererProps = { @@ -943,6 +950,13 @@ export const Table = < (c) => c.id !== SELECTION_CELL_ID, // first non-selection column is the expander column ); + // override "undefined" or zero min-width with default value + if ([undefined, 0].includes(column.minWidth)) { + column.minWidth = columnHasExpanders + ? COLUMN_MIN_WIDTHS.withExpander + : COLUMN_MIN_WIDTHS.default; + } + const columnProps = column.getHeaderProps({ ...restSortProps, className: cx( @@ -958,9 +972,11 @@ export const Table = < ...getCellStyle(column, !!state.isTableResizing), ...(columnHasExpanders && getSubRowStyle({ density })), ...getStickyStyle(column, visibleColumns), - flexWrap: 'unset', + flexWrap: 'wrap', + columnGap: 'var(--iui-size-xs)', }, }); + return ( + + {typeof column.Header === 'string' ? ( + + + + ) : ( + + )} + + + + + {column.render('Header')} {(showFilterButton(column) || showSortButton(column)) && ( e.stopPropagation()} // prevents from triggering sort + slot='actions' > {showFilterButton(column) && ( @@ -1026,21 +1056,31 @@ export const Table = < )} {enableColumnReordering && !column.disableReordering && ( - + )} {column.sticky === 'left' && state.sticky.isScrolledToRight && ( - + )} {column.sticky === 'right' && state.sticky.isScrolledToLeft && ( - + )} ); diff --git a/packages/itwinui-react/src/core/Table/TableCell.tsx b/packages/itwinui-react/src/core/Table/TableCell.tsx index db5e7b8d121..8e4314fc50b 100644 --- a/packages/itwinui-react/src/core/Table/TableCell.tsx +++ b/packages/itwinui-react/src/core/Table/TableCell.tsx @@ -74,6 +74,7 @@ export const TableCell = >( cellProps={cellProps} expanderCell={expanderCell} density={density} + slot='start' /> )} {cell.render('Cell')} @@ -88,11 +89,11 @@ export const TableCell = >( {cellContent} {cell.column.sticky === 'left' && tableInstance.state.sticky.isScrolledToRight && ( - + )} {cell.column.sticky === 'right' && tableInstance.state.sticky.isScrolledToLeft && ( - + )} ), diff --git a/packages/itwinui-react/src/core/Table/cells/DefaultCell.tsx b/packages/itwinui-react/src/core/Table/cells/DefaultCell.tsx index 014d768cbf4..52aeda68440 100644 --- a/packages/itwinui-react/src/core/Table/cells/DefaultCell.tsx +++ b/packages/itwinui-react/src/core/Table/cells/DefaultCell.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import type { CellRendererProps } from '../../../react-table/react-table.js'; import cx from 'classnames'; -import { Box } from '../../utils/index.js'; +import { Box, LineClamp, ShadowRoot } from '../../utils/index.js'; export type DefaultCellProps> = { /** @@ -20,6 +20,12 @@ export type DefaultCellProps> = { * Status of the cell. */ status?: 'positive' | 'negative' | 'warning'; + /** + * Should the contents of the cell be clamped after a certain number of lines? + * + * Will be enabled by default if the cell content is a string. + */ + clamp?: boolean; } & CellRendererProps & React.ComponentPropsWithoutRef<'div'>; @@ -37,8 +43,6 @@ export type DefaultCellProps> = { export const DefaultCell = >( props: DefaultCellProps, ) => { - // Omitting `cellProps` - // eslint-disable-next-line @typescript-eslint/no-unused-vars const { cellElementProps: { className: cellElementClassName, @@ -53,6 +57,7 @@ export const DefaultCell = >( className, style, status, + clamp = typeof cellProps.value === 'string', ...rest } = props; @@ -65,11 +70,30 @@ export const DefaultCell = >( data-iui-status={status} style={{ ...cellElementStyle, ...style }} > + + + {clamp ? ( + + + + ) : ( + + )} + + + + {startIcon && ( - {startIcon} + + {startIcon} + )} {children} - {endIcon && {endIcon}} + {endIcon && ( + + {endIcon} + + )} ); }; diff --git a/packages/itwinui-react/src/core/Table/filters/FilterToggle.tsx b/packages/itwinui-react/src/core/Table/filters/FilterToggle.tsx index ed5ac2e3ca8..d975efb158f 100644 --- a/packages/itwinui-react/src/core/Table/filters/FilterToggle.tsx +++ b/packages/itwinui-react/src/core/Table/filters/FilterToggle.tsx @@ -63,6 +63,7 @@ export const FilterToggle = >( // Prevents from triggering sort e.stopPropagation(); }} + data-iui-shift='left' {...rest} > {isColumnFiltered ? : } diff --git a/testing/e2e/app/routes/Table/route.tsx b/testing/e2e/app/routes/Table/route.tsx new file mode 100644 index 00000000000..87cfaaa322a --- /dev/null +++ b/testing/e2e/app/routes/Table/route.tsx @@ -0,0 +1,70 @@ +import { Table } from '@itwin/itwinui-react'; +import { useSearchParams } from '@remix-run/react'; + +export default function Resizing() { + const [searchParams] = useSearchParams(); + + const disableResizing = searchParams.get('disableResizing') === 'true'; + const columnResizeMode = searchParams.get('columnResizeMode') || 'fit'; + const maxWidths = searchParams.getAll('maxWidth'); + const minWidths = searchParams.getAll('minWidth'); + + return ( + <> + + + ); +} + +const data = [ + { + index: 1, + name: 'Name1', + description: 'Description1', + id: '111', + }, + { + index: 2, + name: 'Name2', + description: 'Description2', + id: '222', + }, + { + index: 3, + name: 'Name3', + description: 'Description3', + id: '333', + }, +]; diff --git a/testing/e2e/app/routes/Table/spec.ts b/testing/e2e/app/routes/Table/spec.ts new file mode 100644 index 00000000000..9ec5048d65f --- /dev/null +++ b/testing/e2e/app/routes/Table/spec.ts @@ -0,0 +1,191 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.describe('Table sorting', () => { + test('should work with keyboard', async ({ page }) => { + await page.goto('/Table'); + + const firstColumnCells = page.locator('[role="cell"]:first-child'); + expect(firstColumnCells).toHaveText(['1', '2', '3']); + + await page.keyboard.press('Tab'); + + // ascending + await page.keyboard.press('Enter'); + expect(firstColumnCells).toHaveText(['1', '2', '3']); + + // descending + await page.keyboard.press('Enter'); + expect(firstColumnCells).toHaveText(['3', '2', '1']); + + // ascending again + await page.keyboard.press('Enter'); + expect(firstColumnCells).toHaveText(['1', '2', '3']); + }); +}); + +test.describe('Table resizing', () => { + test('should adjust column widths', async ({ page }) => { + await page.goto('/Table'); + + // resize first column + { + const initialWidths = await getColumnWidths(page); + + const delta = +100; + await resizeColumn({ index: 0, delta, page }); + + const newWidths = await getColumnWidths(page); + expect(newWidths[0]).toBe(initialWidths[0] + delta); + expect(newWidths[1]).toBe(initialWidths[1] - delta); + } + + // resize second column + { + const initialWidths = await getColumnWidths(page); + + const delta = +100; + await resizeColumn({ index: 1, delta, page }); + + const newWidths = await getColumnWidths(page); + expect(newWidths[1]).toBe(initialWidths[1] + delta); + expect(newWidths[2]).toBe(initialWidths[2] - delta); + } + }); + + test('should respect columnResizeMode=expand', async ({ page }) => { + await page.goto('/Table?columnResizeMode=expand'); + + // resize first column + { + const initialWidths = await getColumnWidths(page); + + const delta = +100; + await resizeColumn({ index: 0, delta, page }); + + const newWidths = await getColumnWidths(page); + expect(newWidths[0]).toBe(initialWidths[0] + delta); + + // other columns should not change + expect(newWidths[1]).toBe(initialWidths[1]); + expect(newWidths[2]).toBe(initialWidths[2]); + expect(newWidths[3]).toBe(initialWidths[3]); + } + + // resize second column + { + const initialWidths = await getColumnWidths(page); + + const delta = +100; + await resizeColumn({ index: 1, delta, page }); + + const newWidths = await getColumnWidths(page); + expect(newWidths[1]).toBe(initialWidths[1] + delta); + + // other columns should not change + expect(newWidths[0]).toBe(initialWidths[0]); + expect(newWidths[2]).toBe(initialWidths[2]); + expect(newWidths[3]).toBe(initialWidths[3]); + } + }); + + test('should respect min width', async ({ page }) => { + const minWidth0 = 50; + const minWidth1 = 300; + + await page.goto(`/Table?minWidth=${minWidth0}&minWidth=${minWidth1}`); + + // resize first column + { + await resizeColumn({ index: 0, delta: -100, page, step: true }); + const newWidths = await getColumnWidths(page); + expect(newWidths[0]).toBe(minWidth0); + } + + // resize second column + { + await resizeColumn({ index: 1, delta: -600, page, step: true }); + const newWidths = await getColumnWidths(page); + expect(newWidths[1]).toBe(minWidth1); + } + + // resize third column (has implicit min width) + { + await resizeColumn({ index: 2, delta: -800, page, step: true }); + const newWidths = await getColumnWidths(page); + expect(newWidths[2]).toBe(72); // 72 is the implicit min width + } + }); + + test('should respect max width', async ({ page }) => { + const maxWidth0 = 150; + const maxWidth1 = 300; + + await page.goto(`/Table?maxWidth=${maxWidth0}&maxWidth=${maxWidth1}`); + + // resize first column + { + await resizeColumn({ index: 0, delta: +100, page, step: true }); + const newWidths = await getColumnWidths(page); + expect(newWidths[0]).toBe(maxWidth0); + } + + // resize second column + { + await resizeColumn({ index: 1, delta: +100, page, step: true }); + const newWidths = await getColumnWidths(page); + expect(newWidths[1]).toBe(maxWidth1); + } + }); + + test('should respect disableResizing', async ({ page }) => { + await page.goto('/Table?disableResizing=true'); + + // resize first column + { + const initialWidths = await getColumnWidths(page); + + const delta = +50; + await resizeColumn({ index: 0, delta, page }); + + const newWidths = await getColumnWidths(page); + expect(newWidths[0]).toBe(initialWidths[0] + delta); + expect(newWidths[1]).toBe(initialWidths[1]); // should not change + } + }); + + // #region Helpers for column resizing tests + const resizeColumn = async (options: { + index: number; + delta: number; + page: Page; + step?: boolean; + }) => { + const { index, delta, page, step = false } = options; + + const resizer = page.getByRole('separator').nth(index); + const resizerBox = (await resizer.boundingBox())!; + + await resizer.hover({ + position: { x: resizerBox.width / 2, y: resizerBox.height / 2 }, + }); + await page.mouse.down(); + await page.mouse.move( + Math.max(1, resizerBox.x + delta + resizerBox.width / 2), + resizerBox.y + resizerBox.height / 2, + { steps: step ? Math.abs(delta) : 5 }, + ); + await page.mouse.up(); + }; + + const getColumnWidths = async (page: Page) => { + const columnHeaders = page.locator('[role="columnheader"]'); + const columnCount = await columnHeaders.count(); + return await Promise.all( + Array.from({ length: columnCount }).map(async (_, i) => { + const header = columnHeaders.nth(i); + return (await header.boundingBox())!.width; + }), + ); + }; + // #endregion +});