@@ -108,7 +118,7 @@ const Overflow = ({
{startIndex}}
- overflowPlacement={config.overflowPlacement}
+ overflowPlacement={overflowPlacement}
>
{buttons}
diff --git a/testing/e2e/app/routes/Table/route.tsx b/testing/e2e/app/routes/Table/route.tsx
index 65617b96964..b90f1e07feb 100644
--- a/testing/e2e/app/routes/Table/route.tsx
+++ b/testing/e2e/app/routes/Table/route.tsx
@@ -11,8 +11,9 @@ export default function TableTest() {
const [searchParams] = useSearchParams();
const config = getConfigFromSearchParams(searchParams);
+ const { exampleType } = config;
- return config.exampleType === 'withTablePaginator' ? (
+ return exampleType === 'withTablePaginator' ? (
) : (
@@ -66,7 +67,25 @@ const Default = ({
}: {
config: ReturnType;
}) => {
- const data = config.subRows
+ const {
+ subRows,
+ oneRow,
+ empty,
+ columnResizeMode,
+ density,
+ disableResizing,
+ enableVirtualization,
+ filter,
+ isSelectable,
+ maxWidths,
+ minWidths,
+ scroll,
+ scrollRow,
+ selectSubRows,
+ stateReducer,
+ } = config;
+
+ const data = subRows
? [
{
index: 1,
@@ -144,9 +163,9 @@ const Default = ({
);
const virtualizedData = React.useMemo(() => {
- const size = config.oneRow ? 1 : 100000;
+ const size = oneRow ? 1 : 100000;
const arr = new Array(size);
- if (!config.empty) {
+ if (!empty) {
for (let i = 0; i < size; ++i) {
arr[i] = {
index: i,
@@ -157,7 +176,7 @@ const Default = ({
}
}
return arr;
- }, [config.oneRow, config.empty]);
+ }, [oneRow, empty]);
return (
<>
@@ -167,16 +186,16 @@ const Default = ({
Header: '#',
accessor: 'index',
width: 100,
- maxWidth: parseInt(config.maxWidths[0]) || undefined,
- minWidth: parseInt(config.minWidths[0]) || undefined,
+ maxWidth: parseInt(maxWidths[0]) || undefined,
+ minWidth: parseInt(minWidths[0]) || undefined,
},
{
Header: 'Name',
accessor: 'name',
- maxWidth: parseInt(config.maxWidths[1]) || undefined,
- minWidth: parseInt(config.minWidths[1]) || undefined,
- disableResizing: config.disableResizing,
- Filter: config.filter ? tableFilters.TextFilter() : undefined,
+ maxWidth: parseInt(maxWidths[1]) || undefined,
+ minWidth: parseInt(minWidths[1]) || undefined,
+ disableResizing: disableResizing,
+ Filter: filter ? tableFilters.TextFilter() : undefined,
},
{
Header: 'Description',
@@ -189,27 +208,25 @@ const Default = ({
width: '8rem',
},
]}
- data={config.enableVirtualization ? virtualizedData : data}
+ data={enableVirtualization ? virtualizedData : data}
emptyTableContent='No data.'
isResizable
isRowDisabled={isRowDisabled}
- isSelectable={config.isSelectable}
+ isSelectable={isSelectable}
isSortable
- density={config.density}
- columnResizeMode={
- config.columnResizeMode as 'fit' | 'expand' | undefined
- }
- selectSubRows={config.selectSubRows}
- enableVirtualization={config.enableVirtualization}
- style={config.enableVirtualization ? { maxHeight: '90vh' } : undefined}
+ density={density}
+ columnResizeMode={columnResizeMode as 'fit' | 'expand' | undefined}
+ selectSubRows={selectSubRows}
+ enableVirtualization={enableVirtualization}
+ style={enableVirtualization ? { maxHeight: '90vh' } : undefined}
scrollToRow={
- config.scroll
+ scroll
? (rows, data) =>
- rows.findIndex((row) => row.original === data[config.scrollRow])
+ rows.findIndex((row) => row.original === data[scrollRow])
: undefined
}
stateReducer={
- config.stateReducer
+ stateReducer
? (newState, action, previousState, instance) => {
if (action.type === 'toggleRowSelected') {
console.log(action.value);
@@ -228,6 +245,8 @@ const WithTablePaginator = ({
}: {
config: ReturnType;
}) => {
+ const { density } = config;
+
const columns = React.useMemo(
() => [
{
@@ -274,7 +293,7 @@ const WithTablePaginator = ({
columns={columns}
data={data}
pageSize={50}
- density={config.density}
+ density={density}
paginatorRenderer={paginator}
/>
From c3d2221a91f1d323e1fad6fa4277f4f2f9ec7c19 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Fri, 19 Jul 2024 11:39:34 -0400
Subject: [PATCH 19/55] iui-breadcrumbs-list stretches to iui-breadcrumbs
---
.changeset/light-poems-trade.md | 5 +++++
packages/itwinui-css/src/breadcrumbs/breadcrumbs.scss | 1 +
2 files changed, 6 insertions(+)
create mode 100644 .changeset/light-poems-trade.md
diff --git a/.changeset/light-poems-trade.md b/.changeset/light-poems-trade.md
new file mode 100644
index 00000000000..25cf62fb663
--- /dev/null
+++ b/.changeset/light-poems-trade.md
@@ -0,0 +1,5 @@
+---
+'@itwin/itwinui-css': patch
+---
+
+`iui-breadcrumbs-list` now stretches to the width of `iui-breadcrumbs`.
diff --git a/packages/itwinui-css/src/breadcrumbs/breadcrumbs.scss b/packages/itwinui-css/src/breadcrumbs/breadcrumbs.scss
index 64004a580bc..bce71c12a7c 100644
--- a/packages/itwinui-css/src/breadcrumbs/breadcrumbs.scss
+++ b/packages/itwinui-css/src/breadcrumbs/breadcrumbs.scss
@@ -20,6 +20,7 @@
list-style-type: none;
user-select: none;
block-size: 100%;
+ inline-size: 100%;
}
.iui-breadcrumbs-item {
From 722385f33ca8eed2112b80a62abecd4bd73a3e35 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Fri, 19 Jul 2024 12:06:21 -0400
Subject: [PATCH 20/55] nit
---
packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx | 2 +-
packages/itwinui-react/src/core/Table/TablePaginator.tsx | 2 +-
.../itwinui-react/src/utils/components/OverflowContainer.tsx | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index e43abe3c35d..bfd0e81ce5d 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -132,7 +132,7 @@ const BreadcrumbsComponent = React.forwardRef((props, ref) => {
{...rest}
>
- {(visibleCount: number) => (
+ {(visibleCount) => (
<>
{visibleCount > 1 && (
<>
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index be6180d4f60..b01fac5abe4 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -17,9 +17,9 @@ import {
SvgChevronRight,
Box,
} from '../../utils/index.js';
+import { OverflowContainer } from '../../utils/components/OverflowContainer.js';
import type { CommonProps } from '../../utils/index.js';
import type { TablePaginatorRendererProps } from './Table.js';
-import { OverflowContainer } from '../../utils/components/OverflowContainer.js';
const defaultLocalization = {
pageSizeLabel: (size: number) => `${size} per page`,
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index e84d2f2dee1..d58e00b153b 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -107,7 +107,7 @@ export const OverflowContainer = React.forwardRef((props, ref) => {
* - `visibleCount <= children.length` means that we show visibleCount - 1 children and 1 overflow tag.
*/
const visibleItems = React.useMemo(() => {
- // User wants complete control over what items are rendered.
+ // Consumer wants complete control over what items are rendered.
if (typeof children === 'function' || overflowTag == null) {
return null;
}
From e2b47a0f0719952721528f7b84a51c8ac020b076 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Fri, 19 Jul 2024 12:37:22 -0400
Subject: [PATCH 21/55] Fixed indexes and JSDocs explanation
---
packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx | 2 +-
.../src/utils/components/OverflowContainer.tsx | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index 7d632160ece..29a88c83178 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -207,7 +207,7 @@ const OverflowGroup = React.forwardRef((props, forwardedRef) => {
overflowTag={(visibleCount) => {
const firstOverflowingIndex =
overflowPlacement === 'start'
- ? items.length - visibleCount - 2
+ ? items.length - visibleCount
: visibleCount - 1;
return overflowButton(firstOverflowingIndex);
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index d58e00b153b..5df12414024 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -103,8 +103,8 @@ export const OverflowContainer = React.forwardRef((props, ref) => {
);
/**
- * - `visibleCount === children.length + 1` means that we show all children and no overflow tag.
- * - `visibleCount <= children.length` means that we show visibleCount - 1 children and 1 overflow tag.
+ * - `visibleCount === children.length` means that we show all children and no overflow tag.
+ * - `visibleCount < children.length` means that we show visibleCount - 1 children and 1 overflow tag.
*/
const visibleItems = React.useMemo(() => {
// Consumer wants complete control over what items are rendered.
@@ -119,7 +119,7 @@ export const OverflowContainer = React.forwardRef((props, ref) => {
if (overflowLocation === 'start') {
return (
<>
- {overflowTag(visibleCount - 2)}
+ {overflowTag(visibleCount)}
{children.slice(children.length - (visibleCount - 1))}
>
);
From 0d50c68cedc2435b5175db1696bfd33b9233a3b1 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Fri, 19 Jul 2024 15:19:13 -0400
Subject: [PATCH 22/55] Review comments: - Remove overflowDisabled - Use
React.Children.toArray
---
.../src/utils/components/OverflowContainer.tsx | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index 5df12414024..bc6931fba6a 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -9,11 +9,6 @@ import { Box } from './Box.js';
import { useOverflow } from '../hooks/useOverflow.js';
type OverflowContainerProps = {
- /**
- * If the overflow detection is disabled, all items will be displayed.
- * @default false
- */
- overflowDisabled?: boolean;
/**
* The orientation of the overflow in container.
* @default 'horizontal'
@@ -91,14 +86,13 @@ export const OverflowContainer = React.forwardRef((props, ref) => {
overflowLocation = 'end',
items,
children,
- overflowDisabled = false,
overflowOrientation,
...rest
} = props;
const [containerRef, visibleCount] = useOverflow(
items,
- overflowDisabled,
+ false,
overflowOrientation,
);
@@ -127,7 +121,7 @@ export const OverflowContainer = React.forwardRef((props, ref) => {
return (
<>
- {children.slice(0, visibleCount - 1)}
+ {React.Children.toArray(children).slice(0, visibleCount - 1)}
{overflowTag(visibleCount)}
>
);
From 2758496d61824cb1133b4baeb4155511d2f30033 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Fri, 19 Jul 2024 15:19:38 -0400
Subject: [PATCH 23/55] Try to memoize as much as possible
---
.../utils/components/MiddleTextTruncation.tsx | 54 +++++++++++++------
1 file changed, 37 insertions(+), 17 deletions(-)
diff --git a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
index 528fb2f3747..45e695ea801 100644
--- a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
+++ b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
@@ -44,20 +44,18 @@ export type MiddleTextTruncationProps = {
* />
*/
export const MiddleTextTruncation = (props: MiddleTextTruncationProps) => {
- const { text, endCharsCount = 6, textRenderer, style, ...rest } = props;
+ const { text, style, endCharsCount = 6, textRenderer, ...rest } = props;
- const truncatedText = React.useCallback(
- (visibleCount: number) => {
- if (visibleCount < text.length) {
- return `${text.substring(
- 0,
- visibleCount - endCharsCount - ELLIPSIS_CHAR.length,
- )}${ELLIPSIS_CHAR}${text.substring(text.length - endCharsCount)}`;
- } else {
- return text;
- }
- },
- [endCharsCount, text],
+ const children = React.useCallback(
+ (visibleCount: number) => (
+
+ ),
+ [endCharsCount, text, textRenderer],
);
return (
@@ -73,13 +71,35 @@ export const MiddleTextTruncation = (props: MiddleTextTruncationProps) => {
items={text}
{...rest}
>
- {(visibleCount) =>
- textRenderer?.(truncatedText(visibleCount), text) ??
- truncatedText(visibleCount)
- }
+ {children}
);
};
if (process.env.NODE_ENV === 'development') {
MiddleTextTruncation.displayName = 'MiddleTextTruncation';
}
+
+// ----------------------------------------------------------------------------
+
+const MiddleTextTruncationContent = ({
+ visibleCount,
+ text,
+ endCharsCount,
+ textRenderer,
+}: Required> &
+ Pick & {
+ visibleCount: number;
+ }) => {
+ const truncatedText = React.useMemo(() => {
+ if (visibleCount < text.length) {
+ return `${text.substring(
+ 0,
+ visibleCount - endCharsCount - ELLIPSIS_CHAR.length,
+ )}${ELLIPSIS_CHAR}${text.substring(text.length - endCharsCount)}`;
+ } else {
+ return text;
+ }
+ }, [endCharsCount, text, visibleCount]);
+
+ return textRenderer?.(truncatedText, text) ?? truncatedText;
+};
From d180b6f6ff5354f27d2ecd519b42ed481a78e0a3 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Fri, 19 Jul 2024 16:47:23 -0400
Subject: [PATCH 24/55] Created OverflowContainer.OverflowNode only in
ButtonGroup for now.
---
.../src/core/ButtonGroup/ButtonGroup.tsx | 75 ++++++++++---
.../utils/components/OverflowContainer.tsx | 103 +++++++-----------
2 files changed, 94 insertions(+), 84 deletions(-)
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index 29a88c83178..2d7fc29b713 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -14,7 +14,10 @@ import {
CompositeItem,
FloatingDelayGroup,
} from '@floating-ui/react';
-import { OverflowContainer } from '../../utils/components/OverflowContainer.js';
+import {
+ OverflowContainer,
+ OverflowContainerContext,
+} from '../../utils/components/OverflowContainer.js';
// ----------------------------------------------------------------------------
@@ -173,6 +176,11 @@ const BaseGroup = React.forwardRef((props, forwardedRef) => {
// ----------------------------------------------------------------------------
+type OverflowGroupProps = Pick<
+ ButtonGroupProps,
+ 'children' | 'orientation' | 'overflowButton' | 'overflowPlacement'
+>;
+
/** Note: If `overflowButton == null`, it behaves like a `BaseGroup`. */
const OverflowGroup = React.forwardRef((props, forwardedRef) => {
const {
@@ -193,7 +201,6 @@ const OverflowGroup = React.forwardRef((props, forwardedRef) => {
as={BaseGroup}
items={items}
overflowOrientation={orientation}
- overflowLocation={overflowPlacement}
orientation={orientation}
{...rest}
className={cx(
@@ -204,26 +211,58 @@ const OverflowGroup = React.forwardRef((props, forwardedRef) => {
props.className,
)}
ref={forwardedRef}
- overflowTag={(visibleCount) => {
- const firstOverflowingIndex =
- overflowPlacement === 'start'
- ? items.length - visibleCount
- : visibleCount - 1;
-
- return overflowButton(firstOverflowingIndex);
- }}
>
- {items}
+
) : (
{childrenProp}
);
-}) as PolymorphicForwardRefComponent<
- 'div',
- Pick<
- ButtonGroupProps,
- 'children' | 'orientation' | 'overflowButton' | 'overflowPlacement'
- >
->;
+}) as PolymorphicForwardRefComponent<'div', OverflowGroupProps>;
+
+// ----------------------------------------------------------------------------
+
+const OverflowGroupContent = ({
+ overflowButton,
+ overflowPlacement,
+ items,
+}: Pick & {
+ items: Array>;
+}) => {
+ const overflowGroupContext = React.useContext(OverflowContainerContext);
+
+ const overflowStart = (() => {
+ if (overflowGroupContext == null) {
+ return undefined;
+ }
+
+ return overflowPlacement === 'start'
+ ? items.length - overflowGroupContext.visibleCount
+ : overflowGroupContext.visibleCount - 1;
+ })();
+
+ console.log('overflowStart', overflowStart);
+
+ return overflowStart != null ? (
+ <>
+ {overflowButton &&
+ overflowPlacement === 'start' &&
+ overflowButton(overflowStart)}
+
+ {overflowPlacement === 'start'
+ ? items.slice(overflowStart + 1)
+ : items.slice(0, Math.max(0, overflowStart))}
+
+ {overflowButton &&
+ overflowPlacement === 'end' &&
+ overflowButton(overflowStart)}
+ >
+ ) : (
+ <>>
+ );
+};
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index bc6931fba6a..4825d504b81 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -3,7 +3,7 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import React from 'react';
-import { useMergedRefs } from '../hooks/useMergedRefs.js';
+import { mergeRefs, useMergedRefs } from '../hooks/useMergedRefs.js';
import type { PolymorphicForwardRefComponent } from '../props.js';
import { Box } from './Box.js';
import { useOverflow } from '../hooks/useOverflow.js';
@@ -18,29 +18,7 @@ type OverflowContainerProps = {
* TODO: Will be removed in a later PR in the stacked PRs.
*/
items: React.ReactNode[] | string;
-} & (
- | {
- children: React.ReactNode[];
- /**
- * What is rendered at `overflowLocation` when `OverflowContainer` starts overflowing.
- *
- * Required if `children: React.ReactNode[]`.
- */
- overflowTag: (visibleCount: number) => React.ReactNode;
- /**
- * Where the overflowTag is placed. Values:
- * - start: At the start
- * - end: At the end
- * @default 'end'
- */
- overflowLocation?: 'start' | 'end';
- }
- | {
- children: (visibleCount: number) => React.ReactNode;
- overflowTag?: undefined;
- overflowLocation?: undefined;
- }
-);
+};
/**
* Renders fewer children + an `overflowTag` when it starts overflowing. When not overflowing, it renders all children.
@@ -80,15 +58,8 @@ type OverflowContainerProps = {
* }
*
*/
-export const OverflowContainer = React.forwardRef((props, ref) => {
- const {
- overflowTag,
- overflowLocation = 'end',
- items,
- children,
- overflowOrientation,
- ...rest
- } = props;
+const OverflowContainerComponent = React.forwardRef((props, ref) => {
+ const { items, children, overflowOrientation, ...rest } = props;
const [containerRef, visibleCount] = useOverflow(
items,
@@ -96,40 +67,40 @@ export const OverflowContainer = React.forwardRef((props, ref) => {
overflowOrientation,
);
- /**
- * - `visibleCount === children.length` means that we show all children and no overflow tag.
- * - `visibleCount < children.length` means that we show visibleCount - 1 children and 1 overflow tag.
- */
- const visibleItems = React.useMemo(() => {
- // Consumer wants complete control over what items are rendered.
- if (typeof children === 'function' || overflowTag == null) {
- return null;
- }
+ return (
+
+
+ {children}
+
+
+ );
+}) as PolymorphicForwardRefComponent<'div', OverflowContainerProps>;
- if (visibleCount >= children.length) {
- return children;
- }
+// ----------------------------------------------------------------------------
- if (overflowLocation === 'start') {
- return (
- <>
- {overflowTag(visibleCount)}
- {children.slice(children.length - (visibleCount - 1))}
- >
- );
- }
+const OverflowContainerOverflowNode = React.forwardRef((props, ref) => {
+ const overflowContainerContext = React.useContext(OverflowContainerContext);
+ const isOverflowing =
+ overflowContainerContext != null &&
+ overflowContainerContext.visibleCount < overflowContainerContext.itemCount;
- return (
- <>
- {React.Children.toArray(children).slice(0, visibleCount - 1)}
- {overflowTag(visibleCount)}
- >
- );
- }, [children, overflowTag, overflowLocation, visibleCount]);
+ return isOverflowing && ;
+});
- return (
-
- {typeof children === 'function' ? children(visibleCount) : visibleItems}
-
- );
-}) as PolymorphicForwardRefComponent<'div', OverflowContainerProps>;
+// ----------------------------------------------------------------------------
+
+export const OverflowContainer = Object.assign(OverflowContainerComponent, {
+ OverflowNode: OverflowContainerOverflowNode,
+});
+
+// ----------------------------------------------------------------------------
+
+export const OverflowContainerContext = React.createContext<
+ | {
+ visibleCount: number;
+ itemCount: number;
+ }
+ | undefined
+>(undefined);
From abd6485a7f996d211cb507ed9585761077a2eb02 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Mon, 22 Jul 2024 08:27:44 -0400
Subject: [PATCH 25/55] OverflowContainer.OverflowNode for other components
---
.../src/core/Breadcrumbs/Breadcrumbs.tsx | 113 ++++---
.../src/core/Select/SelectTagContainer.tsx | 28 +-
.../src/core/Table/TablePaginator.tsx | 308 +++++++++++-------
.../utils/components/MiddleTextTruncation.tsx | 35 +-
.../utils/components/OverflowContainer.tsx | 16 +-
5 files changed, 296 insertions(+), 204 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index bfd0e81ce5d..abe0a1e1fd2 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -12,7 +12,10 @@ import {
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { Button } from '../Buttons/Button.js';
import { Anchor } from '../Typography/Anchor.js';
-import { OverflowContainer } from '../../utils/components/OverflowContainer.js';
+import {
+ OverflowContainer,
+ OverflowContainerContext,
+} from '../../utils/components/OverflowContainer.js';
const logWarning = createWarningLogger();
@@ -132,53 +135,13 @@ const BreadcrumbsComponent = React.forwardRef((props, ref) => {
{...rest}
>
- {(visibleCount) => (
- <>
- {visibleCount > 1 && (
- <>
-
-
- >
- )}
- {items.length - visibleCount > 0 && (
- <>
-
- {overflowButton ? (
- overflowButton(visibleCount)
- ) : (
-
- …
-
- )}
-
-
- >
- )}
- {items
- .slice(
- visibleCount > 1
- ? items.length - visibleCount + 1
- : items.length - 1,
- )
- .map((_, _index) => {
- const index =
- visibleCount > 1
- ? 1 + (items.length - visibleCount) + _index
- : items.length - 1;
- return (
-
-
- {index < items.length - 1 && (
-
- )}
-
- );
- })}
- >
- )}
+
+ {items}
+
);
@@ -189,6 +152,60 @@ if (process.env.NODE_ENV === 'development') {
// ----------------------------------------------------------------------------
+const BreadcrumbContent = (props: BreadcrumbsProps) => {
+ const {
+ children: items,
+ currentIndex = items.length - 1,
+ overflowButton,
+ separator,
+ } = props;
+ const visibleCount =
+ React.useContext(OverflowContainerContext)?.visibleCount ?? 0;
+
+ return (
+ <>
+ {visibleCount > 1 && (
+ <>
+
+
+ >
+ )}
+ {items.length - visibleCount > 0 && (
+ <>
+
+ {overflowButton ? (
+ overflowButton(visibleCount)
+ ) : (
+
+ …
+
+ )}
+
+
+ >
+ )}
+ {items
+ .slice(
+ visibleCount > 1 ? items.length - visibleCount + 1 : items.length - 1,
+ )
+ .map((_, _index) => {
+ const index =
+ visibleCount > 1
+ ? 1 + (items.length - visibleCount) + _index
+ : items.length - 1;
+ return (
+
+
+ {index < items.length - 1 && }
+
+ );
+ })}
+ >
+ );
+};
+
+// ----------------------------------------------------------------------------
+
const ListItem = ({
item,
isActive,
diff --git a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
index c5de90bde64..6f059383bcd 100644
--- a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
+++ b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
@@ -6,7 +6,10 @@ import * as React from 'react';
import cx from 'classnames';
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { SelectTag } from './SelectTag.js';
-import { OverflowContainer } from '../../utils/components/OverflowContainer.js';
+import {
+ OverflowContainer,
+ OverflowContainerContext,
+} from '../../utils/components/OverflowContainer.js';
type SelectTagContainerProps = {
/**
@@ -23,14 +26,29 @@ export const SelectTagContainer = React.forwardRef((props, ref) => {
return (
(
- // -1 to account for the overflowTag
- )}
className={cx('iui-select-tag-container', className)}
ref={ref}
{...rest}
>
- {tags}
+
);
}) as PolymorphicForwardRefComponent<'div', SelectTagContainerProps>;
+
+// ----------------------------------------------------------------------------
+
+const SelectTagContainerContent = (props: SelectTagContainerProps) => {
+ const { tags } = props;
+ const visibleCount =
+ React.useContext(OverflowContainerContext)?.visibleCount ?? tags.length;
+
+ return (
+ <>
+ {visibleCount < tags.length ? tags.slice(0, visibleCount - 1) : tags}
+
+
+
+
+ >
+ );
+};
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index b01fac5abe4..0e0a9f47a8b 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -17,7 +17,10 @@ import {
SvgChevronRight,
Box,
} from '../../utils/index.js';
-import { OverflowContainer } from '../../utils/components/OverflowContainer.js';
+import {
+ OverflowContainer,
+ OverflowContainerContext,
+} from '../../utils/components/OverflowContainer.js';
import type { CommonProps } from '../../utils/index.js';
import type { TablePaginatorRendererProps } from './Table.js';
@@ -200,51 +203,6 @@ export const TablePaginator = (props: TablePaginatorProps) => {
const [paginatorResizeRef, paginatorWidth] = useContainerWidth();
- const onKeyDown = (event: React.KeyboardEvent) => {
- // alt + arrow keys are used by browser/assistive technologies
- if (event.altKey) {
- return;
- }
-
- const focusPage = (delta: number) => {
- const newFocusedIndex = getBoundedValue(
- focusedIndex + delta,
- 0,
- totalPagesCount - 1,
- );
-
- needFocus.current = true;
- if (focusActivationMode === 'auto') {
- onPageChange(newFocusedIndex);
- } else {
- setFocusedIndex(newFocusedIndex);
- }
- };
-
- switch (event.key) {
- case 'ArrowRight': {
- focusPage(+1);
- event.preventDefault();
- break;
- }
- case 'ArrowLeft': {
- focusPage(-1);
- event.preventDefault();
- break;
- }
- case 'Enter':
- case ' ':
- case 'Spacebar': {
- if (focusActivationMode === 'manual') {
- onPageChange(focusedIndex);
- }
- break;
- }
- default:
- break;
- }
- };
-
const hasNoRows = totalPagesCount === 0;
const showPagesList = totalPagesCount > 1 || isLoading;
const showPageSizeList =
@@ -290,83 +248,24 @@ export const TablePaginator = (props: TablePaginatorProps) => {
{showPagesList && (
- {(visibleCount) => {
- const halfVisibleCount = Math.floor(visibleCount / 2);
- let startPage = focusedIndex - halfVisibleCount;
- let endPage = focusedIndex + halfVisibleCount + 1;
- if (startPage < 0) {
- endPage = Math.min(
- totalPagesCount,
- endPage + Math.abs(startPage),
- ); // If no room at the beginning, show extra pages at the end
- startPage = 0;
- }
- if (endPage > totalPagesCount) {
- startPage = Math.max(0, startPage - (endPage - totalPagesCount)); // If no room at the end, show extra pages at the beginning
- endPage = totalPagesCount;
- }
-
- return (
- <>
- onPageChange(currentPage - 1)}
- size={buttonSize}
- aria-label={localization.previousPage}
- >
-
-
-
- {(() => {
- if (hasNoRows) {
- return noRowsContent;
- }
- if (visibleCount === 1) {
- return pageButton(focusedIndex);
- }
- return (
- <>
- {startPage !== 0 && (
- <>
- {pageButton(0, 0)}
- {ellipsis}
- >
- )}
- {pageList.slice(startPage, endPage)}
- {endPage !== totalPagesCount && !isLoading && (
- <>
- {ellipsis}
- {pageButton(totalPagesCount - 1, 0)}
- >
- )}
- {isLoading && (
- <>
- {ellipsis}
-
- >
- )}
- >
- );
- })()}
-
- onPageChange(currentPage + 1)}
- size={buttonSize}
- aria-label={localization.nextPage}
- >
-
-
- >
- );
- }}
+
)}
@@ -409,3 +308,166 @@ export const TablePaginator = (props: TablePaginatorProps) => {
);
};
+
+// ----------------------------------------------------------------------------
+
+type TablePaginatorCenterContentProps = Pick<
+ TablePaginatorProps,
+ 'focusActivationMode' | 'onPageChange' | 'isLoading'
+> &
+ Required> & {
+ focusedIndex: number;
+ totalPagesCount: number;
+ needFocus: React.MutableRefObject;
+ setFocusedIndex: React.Dispatch>;
+ currentPage: number;
+ buttonSize: 'small' | undefined;
+ pageListRef: React.MutableRefObject;
+ hasNoRows: boolean;
+ noRowsContent: React.ReactNode;
+ pageButton: (index: number, tabIndex?: number) => React.ReactNode;
+ ellipsis: React.ReactNode;
+ pageList: React.ReactNode[];
+ };
+
+const TablePaginatorCenterContent = (
+ props: TablePaginatorCenterContentProps,
+) => {
+ const {
+ focusedIndex,
+ focusActivationMode,
+ totalPagesCount,
+ needFocus,
+ onPageChange,
+ setFocusedIndex,
+ currentPage,
+ localization,
+ buttonSize,
+ pageListRef,
+ hasNoRows,
+ noRowsContent,
+ pageButton,
+ ellipsis,
+ pageList,
+ isLoading,
+ } = props;
+ const visibleCount =
+ React.useContext(OverflowContainerContext)?.visibleCount ?? 1;
+ const halfVisibleCount = Math.floor(visibleCount / 2);
+ let startPage = focusedIndex - halfVisibleCount;
+ let endPage = focusedIndex + halfVisibleCount + 1;
+ if (startPage < 0) {
+ endPage = Math.min(totalPagesCount, endPage + Math.abs(startPage)); // If no room at the beginning, show extra pages at the end
+ startPage = 0;
+ }
+ if (endPage > totalPagesCount) {
+ startPage = Math.max(0, startPage - (endPage - totalPagesCount)); // If no room at the end, show extra pages at the beginning
+ endPage = totalPagesCount;
+ }
+
+ const onKeyDown = (event: React.KeyboardEvent) => {
+ // alt + arrow keys are used by browser/assistive technologies
+ if (event.altKey) {
+ return;
+ }
+
+ const focusPage = (delta: number) => {
+ const newFocusedIndex = getBoundedValue(
+ focusedIndex + delta,
+ 0,
+ totalPagesCount - 1,
+ );
+
+ needFocus.current = true;
+ if (focusActivationMode === 'auto') {
+ onPageChange(newFocusedIndex);
+ } else {
+ setFocusedIndex(newFocusedIndex);
+ }
+ };
+
+ switch (event.key) {
+ case 'ArrowRight': {
+ focusPage(+1);
+ event.preventDefault();
+ break;
+ }
+ case 'ArrowLeft': {
+ focusPage(-1);
+ event.preventDefault();
+ break;
+ }
+ case 'Enter':
+ case ' ':
+ case 'Spacebar': {
+ if (focusActivationMode === 'manual') {
+ onPageChange(focusedIndex);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ };
+
+ return (
+ <>
+ onPageChange(currentPage - 1)}
+ size={buttonSize}
+ aria-label={localization.previousPage}
+ >
+
+
+
+ {(() => {
+ if (hasNoRows) {
+ return noRowsContent;
+ }
+ if (visibleCount === 1) {
+ return pageButton(focusedIndex);
+ }
+ return (
+ <>
+ {startPage !== 0 && (
+ <>
+ {pageButton(0, 0)}
+ {ellipsis}
+ >
+ )}
+ {pageList.slice(startPage, endPage)}
+ {endPage !== totalPagesCount && !isLoading && (
+ <>
+ {ellipsis}
+ {pageButton(totalPagesCount - 1, 0)}
+ >
+ )}
+ {isLoading && (
+ <>
+ {ellipsis}
+
+ >
+ )}
+ >
+ );
+ })()}
+
+ onPageChange(currentPage + 1)}
+ size={buttonSize}
+ aria-label={localization.nextPage}
+ >
+
+
+ >
+ );
+};
diff --git a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
index 45e695ea801..8752c57b417 100644
--- a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
+++ b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
@@ -4,7 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import * as React from 'react';
import type { CommonProps } from '../props.js';
-import { OverflowContainer } from './OverflowContainer.js';
+import {
+ OverflowContainer,
+ OverflowContainerContext,
+} from './OverflowContainer.js';
const ELLIPSIS_CHAR = '…';
@@ -44,19 +47,7 @@ export type MiddleTextTruncationProps = {
* />
*/
export const MiddleTextTruncation = (props: MiddleTextTruncationProps) => {
- const { text, style, endCharsCount = 6, textRenderer, ...rest } = props;
-
- const children = React.useCallback(
- (visibleCount: number) => (
-
- ),
- [endCharsCount, text, textRenderer],
- );
+ const { text, style, ...rest } = props;
return (
{
items={text}
{...rest}
>
- {children}
+
);
};
@@ -81,15 +72,11 @@ if (process.env.NODE_ENV === 'development') {
// ----------------------------------------------------------------------------
-const MiddleTextTruncationContent = ({
- visibleCount,
- text,
- endCharsCount,
- textRenderer,
-}: Required> &
- Pick & {
- visibleCount: number;
- }) => {
+const MiddleTextTruncationContent = (props: MiddleTextTruncationProps) => {
+ const { text, endCharsCount = 6, textRenderer } = props;
+ const visibleCount =
+ React.useContext(OverflowContainerContext)?.visibleCount ?? text.length;
+
const truncatedText = React.useMemo(() => {
if (visibleCount < text.length) {
return `${text.substring(
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index 4825d504b81..d2f51d434b9 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -3,7 +3,7 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import React from 'react';
-import { mergeRefs, useMergedRefs } from '../hooks/useMergedRefs.js';
+import { useMergedRefs } from '../hooks/useMergedRefs.js';
import type { PolymorphicForwardRefComponent } from '../props.js';
import { Box } from './Box.js';
import { useOverflow } from '../hooks/useOverflow.js';
@@ -80,14 +80,22 @@ const OverflowContainerComponent = React.forwardRef((props, ref) => {
// ----------------------------------------------------------------------------
-const OverflowContainerOverflowNode = React.forwardRef((props, ref) => {
+type OverflowContainerOverflowNodeProps = {
+ children: React.ReactNode;
+};
+
+const OverflowContainerOverflowNode = (
+ props: OverflowContainerOverflowNodeProps,
+) => {
+ const { children } = props;
+
const overflowContainerContext = React.useContext(OverflowContainerContext);
const isOverflowing =
overflowContainerContext != null &&
overflowContainerContext.visibleCount < overflowContainerContext.itemCount;
- return isOverflowing && ;
-});
+ return isOverflowing && children;
+};
// ----------------------------------------------------------------------------
From de8163388fad85fb7870d847fb905e0bce559783 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Mon, 22 Jul 2024 11:57:04 -0400
Subject: [PATCH 26/55] Import from utils root and not from inner paths.
---
packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx | 4 ++--
packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx | 2 +-
packages/itwinui-react/src/core/Select/SelectTagContainer.tsx | 2 +-
packages/itwinui-react/src/core/Table/TablePaginator.tsx | 2 +-
packages/itwinui-react/src/utils/components/index.ts | 1 +
5 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index abe0a1e1fd2..326b0678acf 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -15,7 +15,7 @@ import { Anchor } from '../Typography/Anchor.js';
import {
OverflowContainer,
OverflowContainerContext,
-} from '../../utils/components/OverflowContainer.js';
+} from '../../utils/index.js';
const logWarning = createWarningLogger();
@@ -134,7 +134,7 @@ const BreadcrumbsComponent = React.forwardRef((props, ref) => {
aria-label='Breadcrumb'
{...rest}
>
-
+
Date: Mon, 22 Jul 2024 16:53:36 -0400
Subject: [PATCH 27/55] Memoize context value
---
.../src/utils/components/OverflowContainer.tsx | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index d2f51d434b9..2b3b7498a43 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -61,16 +61,21 @@ type OverflowContainerProps = {
const OverflowContainerComponent = React.forwardRef((props, ref) => {
const { items, children, overflowOrientation, ...rest } = props;
+ const childrenItems = React.Children.toArray(children);
+
const [containerRef, visibleCount] = useOverflow(
items,
false,
overflowOrientation,
);
+ const overflowContainerContextValue = React.useMemo(
+ () => ({ visibleCount, itemCount: childrenItems.length }),
+ [childrenItems.length, visibleCount],
+ );
+
return (
-
+
{children}
From cd1a18a40b3e24cdb0cc6f468682a99d64be45b2 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Mon, 22 Jul 2024 16:54:42 -0400
Subject: [PATCH 28/55] Remove redundant OverflowGroup checks
---
.../src/core/ButtonGroup/ButtonGroup.tsx | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index c8f016d5273..7e694197455 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -178,10 +178,10 @@ const BaseGroup = React.forwardRef((props, forwardedRef) => {
type OverflowGroupProps = Pick<
ButtonGroupProps,
- 'children' | 'orientation' | 'overflowButton' | 'overflowPlacement'
->;
+ 'children' | 'orientation' | 'overflowPlacement'
+> &
+ Required>;
-/** Note: If `overflowButton == null`, it behaves like a `BaseGroup`. */
const OverflowGroup = React.forwardRef((props, forwardedRef) => {
const {
children: childrenProp,
@@ -196,7 +196,7 @@ const OverflowGroup = React.forwardRef((props, forwardedRef) => {
[childrenProp],
);
- return overflowButton != null ? (
+ return (
{
items={items}
/>
- ) : (
-
- {childrenProp}
-
);
}) as PolymorphicForwardRefComponent<'div', OverflowGroupProps>;
From bf4ff5c87221df870744d9886f385921c8e0bb3a Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Tue, 6 Aug 2024 15:21:34 -0400
Subject: [PATCH 29/55] Merged imports
---
.../itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx | 6 ++----
.../itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx | 10 +++++-----
.../itwinui-react/src/core/Table/TablePaginator.tsx | 2 --
3 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index 326b0678acf..95d888f3c0a 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -8,14 +8,12 @@ import {
SvgChevronRight,
Box,
createWarningLogger,
+ OverflowContainer,
+ OverflowContainerContext,
} from '../../utils/index.js';
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { Button } from '../Buttons/Button.js';
import { Anchor } from '../Typography/Anchor.js';
-import {
- OverflowContainer,
- OverflowContainerContext,
-} from '../../utils/index.js';
const logWarning = createWarningLogger();
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index 7e694197455..f335ce71328 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -4,7 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as React from 'react';
import cx from 'classnames';
-import { Box } from '../../utils/index.js';
+import {
+ Box,
+ OverflowContainer,
+ OverflowContainerContext,
+} from '../../utils/index.js';
import type {
AnyString,
PolymorphicForwardRefComponent,
@@ -14,10 +18,6 @@ import {
CompositeItem,
FloatingDelayGroup,
} from '@floating-ui/react';
-import {
- OverflowContainer,
- OverflowContainerContext,
-} from '../../utils/index.js';
// ----------------------------------------------------------------------------
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index ad0b834def1..8375dd3be3c 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -16,8 +16,6 @@ import {
SvgChevronLeft,
SvgChevronRight,
Box,
-} from '../../utils/index.js';
-import {
OverflowContainer,
OverflowContainerContext,
} from '../../utils/index.js';
From bdfaf1bcc2a7369da08501c487499748ce9c283c Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Tue, 6 Aug 2024 16:49:14 -0400
Subject: [PATCH 30/55] Use React.Children.toArray
---
.../src/core/Breadcrumbs/Breadcrumbs.tsx | 12 +++++++++---
.../src/core/ButtonGroup/ButtonGroup.tsx | 2 --
.../src/core/Select/SelectTagContainer.tsx | 7 ++++++-
3 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index 95d888f3c0a..78a665e3029 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -116,14 +116,20 @@ type BreadcrumbsProps = {
*/
const BreadcrumbsComponent = React.forwardRef((props, ref) => {
const {
- children: items,
- currentIndex = items.length - 1,
+ children: childrenProp,
+ currentIndex: currentIndexProp,
separator,
overflowButton,
className,
...rest
} = props;
+ const items = React.useMemo(
+ () => React.Children.toArray(childrenProp),
+ [childrenProp],
+ );
+ const currentIndex = currentIndexProp || items.length - 1;
+
return (
{
aria-label='Breadcrumb'
{...rest}
>
-
+
{overflowButton &&
diff --git a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
index f70ee7f0b93..3c4a81d35b8 100644
--- a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
+++ b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
@@ -21,7 +21,12 @@ type SelectTagContainerProps = {
/**
*/
export const SelectTagContainer = React.forwardRef((props, ref) => {
- const { tags, className, ...rest } = props;
+ const { tags: tagsProp, className, ...rest } = props;
+
+ const tags = React.useMemo(
+ () => React.Children.toArray(tagsProp),
+ [tagsProp],
+ );
return (
Date: Thu, 29 Aug 2024 16:07:20 -0400
Subject: [PATCH 31/55] `useOverflowContainerContext`
---
.../src/core/Breadcrumbs/Breadcrumbs.tsx | 5 ++---
.../src/core/ButtonGroup/ButtonGroup.tsx | 16 ++++++----------
.../src/core/Select/SelectTagContainer.tsx | 5 ++---
.../src/core/Table/TablePaginator.tsx | 6 +++---
.../utils/components/MiddleTextTruncation.tsx | 5 ++---
.../src/utils/components/OverflowContainer.tsx | 11 ++++++++++-
6 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index 78a665e3029..bbb7c1bf88e 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -9,7 +9,7 @@ import {
Box,
createWarningLogger,
OverflowContainer,
- OverflowContainerContext,
+ useOverflowContainerContext,
} from '../../utils/index.js';
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { Button } from '../Buttons/Button.js';
@@ -163,8 +163,7 @@ const BreadcrumbContent = (props: BreadcrumbsProps) => {
overflowButton,
separator,
} = props;
- const visibleCount =
- React.useContext(OverflowContainerContext)?.visibleCount ?? 0;
+ const { visibleCount } = useOverflowContainerContext();
return (
<>
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index 76aa3fe4b43..af81d48c893 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -7,7 +7,7 @@ import cx from 'classnames';
import {
Box,
OverflowContainer,
- OverflowContainerContext,
+ useOverflowContainerContext,
} from '../../utils/index.js';
import type {
AnyString,
@@ -230,17 +230,13 @@ const OverflowGroupContent = ({
}: Pick & {
items: Array>;
}) => {
- const overflowGroupContext = React.useContext(OverflowContainerContext);
-
- const overflowStart = (() => {
- if (overflowGroupContext == null) {
- return undefined;
- }
+ const { visibleCount } = useOverflowContainerContext();
+ const overflowStart = React.useMemo(() => {
return overflowPlacement === 'start'
- ? items.length - overflowGroupContext.visibleCount
- : overflowGroupContext.visibleCount - 1;
- })();
+ ? items.length - visibleCount
+ : visibleCount - 1;
+ }, [items.length, overflowPlacement, visibleCount]);
return overflowStart != null ? (
<>
diff --git a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
index 3c4a81d35b8..567c34fa1d5 100644
--- a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
+++ b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
@@ -8,7 +8,7 @@ import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { SelectTag } from './SelectTag.js';
import {
OverflowContainer,
- OverflowContainerContext,
+ useOverflowContainerContext,
} from '../../utils/index.js';
type SelectTagContainerProps = {
@@ -44,8 +44,7 @@ export const SelectTagContainer = React.forwardRef((props, ref) => {
const SelectTagContainerContent = (props: SelectTagContainerProps) => {
const { tags } = props;
- const visibleCount =
- React.useContext(OverflowContainerContext)?.visibleCount ?? tags.length;
+ const { visibleCount } = useOverflowContainerContext();
return (
<>
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index 8375dd3be3c..f78055eea0c 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -17,7 +17,7 @@ import {
SvgChevronRight,
Box,
OverflowContainer,
- OverflowContainerContext,
+ useOverflowContainerContext,
} from '../../utils/index.js';
import type { CommonProps } from '../../utils/index.js';
import type { TablePaginatorRendererProps } from './Table.js';
@@ -349,8 +349,8 @@ const TablePaginatorCenterContent = (
pageList,
isLoading,
} = props;
- const visibleCount =
- React.useContext(OverflowContainerContext)?.visibleCount ?? 1;
+ const { visibleCount } = useOverflowContainerContext();
+
const halfVisibleCount = Math.floor(visibleCount / 2);
let startPage = focusedIndex - halfVisibleCount;
let endPage = focusedIndex + halfVisibleCount + 1;
diff --git a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
index 8752c57b417..63fa518e85d 100644
--- a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
+++ b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
import type { CommonProps } from '../props.js';
import {
OverflowContainer,
- OverflowContainerContext,
+ useOverflowContainerContext,
} from './OverflowContainer.js';
const ELLIPSIS_CHAR = '…';
@@ -74,8 +74,7 @@ if (process.env.NODE_ENV === 'development') {
const MiddleTextTruncationContent = (props: MiddleTextTruncationProps) => {
const { text, endCharsCount = 6, textRenderer } = props;
- const visibleCount =
- React.useContext(OverflowContainerContext)?.visibleCount ?? text.length;
+ const { visibleCount } = useOverflowContainerContext();
const truncatedText = React.useMemo(() => {
if (visibleCount < text.length) {
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index 2b3b7498a43..a89e5a651bd 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -7,6 +7,7 @@ import { useMergedRefs } from '../hooks/useMergedRefs.js';
import type { PolymorphicForwardRefComponent } from '../props.js';
import { Box } from './Box.js';
import { useOverflow } from '../hooks/useOverflow.js';
+import { useSafeContext } from '../hooks/useSafeContext.js';
type OverflowContainerProps = {
/**
@@ -110,10 +111,18 @@ export const OverflowContainer = Object.assign(OverflowContainerComponent, {
// ----------------------------------------------------------------------------
-export const OverflowContainerContext = React.createContext<
+const OverflowContainerContext = React.createContext<
| {
visibleCount: number;
itemCount: number;
}
| undefined
>(undefined);
+if (process.env.NODE_ENV === 'development') {
+ OverflowContainerContext.displayName = 'OverflowContainerContext';
+}
+
+export const useOverflowContainerContext = () => {
+ const overflowContainerContext = useSafeContext(OverflowContainerContext);
+ return overflowContainerContext;
+};
From 99e35364c1db91a376221199204384319327273f Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Thu, 29 Aug 2024 17:32:54 -0400
Subject: [PATCH 32/55] General improvements
---
.../src/core/Breadcrumbs/Breadcrumbs.tsx | 13 ++--
.../src/core/ButtonGroup/ButtonGroup.tsx | 20 +++---
.../src/core/Select/SelectTagContainer.tsx | 8 ++-
.../src/core/Table/TablePaginator.tsx | 69 +++++++++----------
.../utils/components/OverflowContainer.tsx | 47 +++----------
5 files changed, 64 insertions(+), 93 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index 4825f9adf4f..493d701a604 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -154,13 +154,12 @@ if (process.env.NODE_ENV === 'development') {
// ----------------------------------------------------------------------------
-const BreadcrumbContent = (props: BreadcrumbsProps) => {
- const {
- children: items,
- currentIndex = items.length - 1,
- overflowButton,
- separator,
- } = props;
+type BreadcrumbContentProps = Omit & {
+ currentIndex: NonNullable;
+};
+
+const BreadcrumbContent = (props: BreadcrumbContentProps) => {
+ const { children: items, currentIndex, overflowButton, separator } = props;
const { visibleCount } = useOverflowContainerContext();
return (
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index af81d48c893..812ff794664 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -223,13 +223,15 @@ const OverflowGroup = React.forwardRef((props, forwardedRef) => {
// ----------------------------------------------------------------------------
-const OverflowGroupContent = ({
- overflowButton,
- overflowPlacement,
- items,
-}: Pick & {
- items: Array>;
-}) => {
+type OverflowGroupContentProps = Pick<
+ OverflowGroupProps,
+ 'overflowButton' | 'overflowPlacement'
+> & {
+ items: ReturnType;
+};
+
+const OverflowGroupContent = (props: OverflowGroupContentProps) => {
+ const { overflowButton, overflowPlacement, items } = props;
const { visibleCount } = useOverflowContainerContext();
const overflowStart = React.useMemo(() => {
@@ -238,7 +240,7 @@ const OverflowGroupContent = ({
: visibleCount - 1;
}, [items.length, overflowPlacement, visibleCount]);
- return overflowStart != null ? (
+ return (
<>
{overflowButton &&
overflowPlacement === 'start' &&
@@ -252,7 +254,5 @@ const OverflowGroupContent = ({
overflowPlacement === 'end' &&
overflowButton(overflowStart)}
>
- ) : (
- <>>
);
};
diff --git a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
index 567c34fa1d5..8522e3d49cb 100644
--- a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
+++ b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
@@ -35,14 +35,18 @@ export const SelectTagContainer = React.forwardRef((props, ref) => {
ref={ref}
{...rest}
>
-
+
);
}) as PolymorphicForwardRefComponent<'div', SelectTagContainerProps>;
// ----------------------------------------------------------------------------
-const SelectTagContainerContent = (props: SelectTagContainerProps) => {
+type SelectTagContainerContentProps = {
+ tags: ReturnType;
+};
+
+const SelectTagContainerContent = (props: SelectTagContainerContentProps) => {
const { tags } = props;
const { visibleCount } = useOverflowContainerContext();
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index f78055eea0c..2a016693322 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -201,34 +201,10 @@ export const TablePaginator = (props: TablePaginatorProps) => {
const [paginatorResizeRef, paginatorWidth] = useContainerWidth();
- const hasNoRows = totalPagesCount === 0;
const showPagesList = totalPagesCount > 1 || isLoading;
const showPageSizeList =
pageSizeList && !!onPageSizeChange && !!totalRowsCount;
- const ellipsis = (
-
- …
-
- );
-
- const noRowsContent = (
- <>
- {isLoading ? (
-
- ) : (
-
- )}
- >
- );
-
if (!showPagesList && !showPageSizeList) {
return null;
}
@@ -247,6 +223,7 @@ export const TablePaginator = (props: TablePaginatorProps) => {
{showPagesList && (
{
localization={localization}
buttonSize={buttonSize}
pageListRef={pageListRef}
- hasNoRows={hasNoRows}
- noRowsContent={noRowsContent}
pageButton={pageButton}
- ellipsis={ellipsis}
pageList={pageList}
isLoading={isLoading}
/>
@@ -311,9 +285,14 @@ export const TablePaginator = (props: TablePaginatorProps) => {
type TablePaginatorCenterContentProps = Pick<
TablePaginatorProps,
- 'focusActivationMode' | 'onPageChange' | 'isLoading'
+ 'onPageChange'
> &
- Required> & {
+ Required<
+ Pick<
+ TablePaginatorProps,
+ 'localization' | 'size' | 'focusActivationMode' | 'isLoading'
+ >
+ > & {
focusedIndex: number;
totalPagesCount: number;
needFocus: React.MutableRefObject;
@@ -321,10 +300,7 @@ type TablePaginatorCenterContentProps = Pick<
currentPage: number;
buttonSize: 'small' | undefined;
pageListRef: React.MutableRefObject;
- hasNoRows: boolean;
- noRowsContent: React.ReactNode;
pageButton: (index: number, tabIndex?: number) => React.ReactNode;
- ellipsis: React.ReactNode;
pageList: React.ReactNode[];
};
@@ -342,15 +318,15 @@ const TablePaginatorCenterContent = (
localization,
buttonSize,
pageListRef,
- hasNoRows,
- noRowsContent,
pageButton,
- ellipsis,
pageList,
isLoading,
+ size,
} = props;
const { visibleCount } = useOverflowContainerContext();
+ const hasNoRows = totalPagesCount === 0;
+
const halfVisibleCount = Math.floor(visibleCount / 2);
let startPage = focusedIndex - halfVisibleCount;
let endPage = focusedIndex + halfVisibleCount + 1;
@@ -363,6 +339,29 @@ const TablePaginatorCenterContent = (
endPage = totalPagesCount;
}
+ const ellipsis = (
+
+ …
+
+ );
+
+ const noRowsContent = (
+ <>
+ {isLoading ? (
+
+ ) : (
+
+ )}
+ >
+ );
+
const onKeyDown = (event: React.KeyboardEvent) => {
// alt + arrow keys are used by browser/assistive technologies
if (event.altKey) {
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index a89e5a651bd..0f9644decc7 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -22,42 +22,10 @@ type OverflowContainerProps = {
};
/**
- * Renders fewer children + an `overflowTag` when it starts overflowing. When not overflowing, it renders all children.
- * This component listens to resize events and updates the rendered content accordingly.
+ * Wrapper over `useOverflow`.
*
- * Two forms of usage:
- * 1. `children: React.ReactNode[]`: Pass all the children and an `overflowTag` and this component handles when to show
- * what, depending on whether the component is overflowing.
- * 2. `children: (visibleCount: number) => React.ReactNode`: For more customization, pass a function to get the
- * `visibleCount` and then render custom content based on that.
- *
- * @example
- * (
- * +${tags.length - (visibleCount - 1)} item(s) // -1 to account for the overflowTag
- * )}
- * overflowLocation='start'
- * >
- * {items}
- *
- *
- * @example
- *
- * {(visibleCount) => {
- * // Custom content dependent on visibleCount
- * return (
- * <>
- * itemsLeft(visibleCount)
- * overflowButton(visibleCount)
- * itemsRight(visibleCount)
- * >
- * );
- * }
- *
+ * - Use `useOverflowContainerContext` to get overflow related properties.
+ * - Wrap overflow content in `OverflowContainer.OverflowNode` to conditionally render it when overflowing.
*/
const OverflowContainerComponent = React.forwardRef((props, ref) => {
const { items, children, overflowOrientation, ...rest } = props;
@@ -95,10 +63,8 @@ const OverflowContainerOverflowNode = (
) => {
const { children } = props;
- const overflowContainerContext = React.useContext(OverflowContainerContext);
- const isOverflowing =
- overflowContainerContext != null &&
- overflowContainerContext.visibleCount < overflowContainerContext.itemCount;
+ const { visibleCount, itemCount } = useOverflowContainerContext();
+ const isOverflowing = visibleCount < itemCount;
return isOverflowing && children;
};
@@ -106,6 +72,9 @@ const OverflowContainerOverflowNode = (
// ----------------------------------------------------------------------------
export const OverflowContainer = Object.assign(OverflowContainerComponent, {
+ /**
+ * Wrap overflow content in this component to conditionally render it when overflowing.
+ */
OverflowNode: OverflowContainerOverflowNode,
});
From 48e7fc7b38bde66abae6363b3efa9c33bf5d37b3 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Fri, 30 Aug 2024 08:29:48 -0400
Subject: [PATCH 33/55] Bug fixes
---
.../itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx | 9 +++++----
.../src/utils/components/OverflowContainer.tsx | 8 +++-----
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index 812ff794664..60eb9b7b062 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -234,13 +234,14 @@ const OverflowGroupContent = (props: OverflowGroupContentProps) => {
const { overflowButton, overflowPlacement, items } = props;
const { visibleCount } = useOverflowContainerContext();
- const overflowStart = React.useMemo(() => {
- return overflowPlacement === 'start'
+ const overflowStart =
+ overflowPlacement === 'start'
? items.length - visibleCount
: visibleCount - 1;
- }, [items.length, overflowPlacement, visibleCount]);
- return (
+ return !(visibleCount < items.length) ? (
+ items
+ ) : (
<>
{overflowButton &&
overflowPlacement === 'start' &&
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index 0f9644decc7..32ce4f678a7 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -16,7 +16,7 @@ type OverflowContainerProps = {
*/
overflowOrientation?: 'horizontal' | 'vertical';
/**
- * TODO: Will be removed in a later PR in the stacked PRs.
+ * TODO: Will likely be removed in a later PR in the stacked PRs. If not, remove this TODO.
*/
items: React.ReactNode[] | string;
};
@@ -30,8 +30,6 @@ type OverflowContainerProps = {
const OverflowContainerComponent = React.forwardRef((props, ref) => {
const { items, children, overflowOrientation, ...rest } = props;
- const childrenItems = React.Children.toArray(children);
-
const [containerRef, visibleCount] = useOverflow(
items,
false,
@@ -39,8 +37,8 @@ const OverflowContainerComponent = React.forwardRef((props, ref) => {
);
const overflowContainerContextValue = React.useMemo(
- () => ({ visibleCount, itemCount: childrenItems.length }),
- [childrenItems.length, visibleCount],
+ () => ({ visibleCount, itemCount: items.length }),
+ [items.length, visibleCount],
);
return (
From 42bc28093918b9d894b4d67d3f701eb03b79a1e1 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+rohan-kadkol@users.noreply.github.com>
Date: Fri, 30 Aug 2024 10:06:13 -0400
Subject: [PATCH 34/55] Fix test failures
---
testing/e2e/app/routes/Table/route.tsx | 36 ++++++++++++++------------
testing/e2e/app/routes/Table/spec.ts | 2 +-
2 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/testing/e2e/app/routes/Table/route.tsx b/testing/e2e/app/routes/Table/route.tsx
index 9121f9f7f52..e7d106b61e2 100644
--- a/testing/e2e/app/routes/Table/route.tsx
+++ b/testing/e2e/app/routes/Table/route.tsx
@@ -129,6 +129,24 @@ const Default = ({
stateReducer,
} = config;
+ const virtualizedData = React.useMemo(() => {
+ if (empty) {
+ return [];
+ }
+
+ const size = oneRow ? 1 : 100000;
+ const arr = new Array(size);
+ for (let i = 0; i < size; ++i) {
+ arr[i] = {
+ index: i,
+ name: `Name${i}`,
+ description: `Description${i}`,
+ id: i,
+ };
+ }
+ return arr;
+ }, [oneRow, empty]);
+
const data = subRows ? dataWithSubrows : baseData;
const isRowDisabled = React.useCallback(
@@ -138,22 +156,6 @@ const Default = ({
[],
);
- const virtualizedData = React.useMemo(() => {
- const size = oneRow ? 1 : 100000;
- const arr = new Array(size);
- if (!empty) {
- for (let i = 0; i < size; ++i) {
- arr[i] = {
- index: i,
- name: `Name${i}`,
- description: `Description${i}`,
- id: i,
- };
- }
- }
- return arr;
- }, [oneRow, empty]);
-
return (
<>
{
test.describe('Table filters', () => {
test('DateRangeFilter should show DatePicker', async ({ page }) => {
- await page.goto('/Table?allFilters=true');
+ await page.goto('/Table?exampleType=allFilters');
// open Date filter
const dateHeader = page.locator('[role="columnheader"]', {
From 1651f27b51c6d3ddcc5e55ffbb2a5a450e09ea2a Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Thu, 26 Sep 2024 15:32:46 -0400
Subject: [PATCH 35/55] Fixed e2e test
---
testing/e2e/app/routes/Table/route.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/testing/e2e/app/routes/Table/route.tsx b/testing/e2e/app/routes/Table/route.tsx
index 732972a1d1c..09c967ca4ff 100644
--- a/testing/e2e/app/routes/Table/route.tsx
+++ b/testing/e2e/app/routes/Table/route.tsx
@@ -95,7 +95,7 @@ const getConfigFromSearchParams = (searchParams: URLSearchParams) => {
minWidths,
density,
isSelectable,
- subRows,
+ rows,
filter,
selectSubRows,
enableVirtualization,
@@ -104,6 +104,7 @@ const getConfigFromSearchParams = (searchParams: URLSearchParams) => {
oneRow,
stateReducer,
scrollRow,
+ hasSubComponent,
};
};
@@ -113,7 +114,6 @@ const Default = ({
config: ReturnType;
}) => {
const {
- subRows,
oneRow,
empty,
columnResizeMode,
@@ -128,6 +128,8 @@ const Default = ({
scrollRow,
selectSubRows,
stateReducer,
+ rows,
+ hasSubComponent,
} = config;
const virtualizedData = React.useMemo(() => {
From f55d709bd581ff471cd55649271e35a94d4325a4 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Mon, 30 Sep 2024 16:30:51 -0400
Subject: [PATCH 36/55] Improvements
---
testing/e2e/app/routes/ComboBox/spec.ts | 42 ++++++++++++-------------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/testing/e2e/app/routes/ComboBox/spec.ts b/testing/e2e/app/routes/ComboBox/spec.ts
index 5d872340dc6..26184686492 100644
--- a/testing/e2e/app/routes/ComboBox/spec.ts
+++ b/testing/e2e/app/routes/ComboBox/spec.ts
@@ -21,11 +21,13 @@ test.describe('ComboBox (general)', () => {
await option.click();
}
- const tags = await getSelectTagContainerTags(page);
- expect(tags).toHaveLength(options.length);
+ const tags = getSelectTagContainerTags(page);
+ expect(tags).toHaveCount(options.length);
- for (let i = 0; i < tags.length; i++) {
- await expect(tags[i]).toHaveText((await options[i].textContent()) ?? '');
+ for (let i = 0; i < (await tags.count()); i++) {
+ await expect(tags.nth(i)).toHaveText(
+ (await options[i].textContent()) ?? '',
+ );
}
});
@@ -39,20 +41,20 @@ test.describe('ComboBox (general)', () => {
await page.waitForTimeout(200);
+ let tags = getSelectTagContainerTags(page);
+
// Should change internal state when the value prop changes
if (multiple) {
- let tags = await getSelectTagContainerTags(page);
- expect(tags).toHaveLength(defaultOptions.length);
+ expect(tags).toHaveCount(defaultOptions.length);
- for (let i = 0; i < tags.length; i++) {
- await expect(tags[i]).toHaveText(defaultOptions[i].label);
+ for (let i = 0; i < (await tags.count()); i++) {
+ await expect(tags.nth(i)).toHaveText(defaultOptions[i].label);
}
await page.getByTestId('change-value-to-first-option-button').click();
- tags = await getSelectTagContainerTags(page);
- expect(tags).toHaveLength(1);
- await expect(tags[0]).toHaveText(defaultOptions[0].label);
+ expect(tags).toHaveCount(1);
+ await expect(tags.first()).toHaveText(defaultOptions[0].label);
} else {
await expect(page.locator('input')).toHaveValue('Item 11');
await page.getByTestId('change-value-to-first-option-button').click();
@@ -65,10 +67,8 @@ test.describe('ComboBox (general)', () => {
await page.getByRole('option').nth(3).click();
if (multiple) {
- const tags = await getSelectTagContainerTags(page);
-
- expect(tags).toHaveLength(1);
- await expect(tags[0]).toHaveText(defaultOptions[0].label);
+ expect(tags).toHaveCount(1);
+ await expect(tags.first()).toHaveText(defaultOptions[0].label);
} else {
await expect(page.locator('input')).toHaveValue('Item 0');
}
@@ -342,18 +342,18 @@ const expectOverflowState = async ({
expectedItemLength: number;
expectedLastTagTextContent: string | undefined;
}) => {
- const tags = await getSelectTagContainerTags(page);
- expect(tags).toHaveLength(expectedItemLength);
+ const tags = getSelectTagContainerTags(page);
+ expect(tags).toHaveCount(expectedItemLength);
- const lastTag = tags[tags.length - 1];
+ const lastTag = tags.nth((await tags.count()) - 1);
if (expectedLastTagTextContent != null) {
await expect(lastTag).toHaveText(expectedLastTagTextContent);
} else {
- expect(tags).toHaveLength(0);
+ expect(tags).toHaveCount(0);
}
};
-const getSelectTagContainerTags = async (page: Page) => {
- return await page.locator('span[class$="-select-tag"]').all();
+const getSelectTagContainerTags = (page: Page) => {
+ return page.getByRole('combobox').locator('+ div > span');
};
From b7c05aecd2744766796bf8af42582d4a1a71aae2 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Mon, 30 Sep 2024 16:33:56 -0400
Subject: [PATCH 37/55] Simplicity
---
testing/e2e/app/routes/ComboBox/spec.ts | 96 ++++++++++++-------------
1 file changed, 47 insertions(+), 49 deletions(-)
diff --git a/testing/e2e/app/routes/ComboBox/spec.ts b/testing/e2e/app/routes/ComboBox/spec.ts
index 26184686492..38cb785205f 100644
--- a/testing/e2e/app/routes/ComboBox/spec.ts
+++ b/testing/e2e/app/routes/ComboBox/spec.ts
@@ -10,69 +10,67 @@ const defaultOptions = [
{ label: 'Item 11', value: 11 },
];
-test.describe('ComboBox (general)', () => {
- test('should select multiple options', async ({ page }) => {
- await page.goto('/ComboBox?multiple=true');
+test('should select multiple options', async ({ page }) => {
+ await page.goto('/ComboBox?multiple=true');
- await page.keyboard.press('Tab');
+ await page.keyboard.press('Tab');
- const options = await page.locator('[role="option"]').all();
- for (const option of options) {
- await option.click();
- }
+ const options = await page.locator('[role="option"]').all();
+ for (const option of options) {
+ await option.click();
+ }
- const tags = getSelectTagContainerTags(page);
- expect(tags).toHaveCount(options.length);
+ const tags = getSelectTagContainerTags(page);
+ expect(tags).toHaveCount(options.length);
- for (let i = 0; i < (await tags.count()); i++) {
- await expect(tags.nth(i)).toHaveText(
- (await options[i].textContent()) ?? '',
- );
- }
- });
+ for (let i = 0; i < (await tags.count()); i++) {
+ await expect(tags.nth(i)).toHaveText(
+ (await options[i].textContent()) ?? '',
+ );
+ }
+});
- [true, false].forEach((multiple) => {
- test(`should respect the value prop (${multiple})`, async ({ page }) => {
- await page.goto(
- `/ComboBox?multiple=${multiple}&initialValue=${
- multiple ? 'all' : 11
- }&showChangeValueButton=true`,
- );
+[true, false].forEach((multiple) => {
+ test(`should respect the value prop (${multiple})`, async ({ page }) => {
+ await page.goto(
+ `/ComboBox?multiple=${multiple}&initialValue=${
+ multiple ? 'all' : 11
+ }&showChangeValueButton=true`,
+ );
- await page.waitForTimeout(200);
+ await page.waitForTimeout(200);
- let tags = getSelectTagContainerTags(page);
+ let tags = getSelectTagContainerTags(page);
- // Should change internal state when the value prop changes
- if (multiple) {
- expect(tags).toHaveCount(defaultOptions.length);
+ // Should change internal state when the value prop changes
+ if (multiple) {
+ expect(tags).toHaveCount(defaultOptions.length);
- for (let i = 0; i < (await tags.count()); i++) {
- await expect(tags.nth(i)).toHaveText(defaultOptions[i].label);
- }
+ for (let i = 0; i < (await tags.count()); i++) {
+ await expect(tags.nth(i)).toHaveText(defaultOptions[i].label);
+ }
- await page.getByTestId('change-value-to-first-option-button').click();
+ await page.getByTestId('change-value-to-first-option-button').click();
- expect(tags).toHaveCount(1);
- await expect(tags.first()).toHaveText(defaultOptions[0].label);
- } else {
- await expect(page.locator('input')).toHaveValue('Item 11');
- await page.getByTestId('change-value-to-first-option-button').click();
- await expect(page.locator('input')).toHaveValue('Item 0');
- }
+ expect(tags).toHaveCount(1);
+ await expect(tags.first()).toHaveText(defaultOptions[0].label);
+ } else {
+ await expect(page.locator('input')).toHaveValue('Item 11');
+ await page.getByTestId('change-value-to-first-option-button').click();
+ await expect(page.locator('input')).toHaveValue('Item 0');
+ }
- // Should not allow to select other options
- await page.keyboard.press('Tab');
+ // Should not allow to select other options
+ await page.keyboard.press('Tab');
- await page.getByRole('option').nth(3).click();
+ await page.getByRole('option').nth(3).click();
- if (multiple) {
- expect(tags).toHaveCount(1);
- await expect(tags.first()).toHaveText(defaultOptions[0].label);
- } else {
- await expect(page.locator('input')).toHaveValue('Item 0');
- }
- });
+ if (multiple) {
+ expect(tags).toHaveCount(1);
+ await expect(tags.first()).toHaveText(defaultOptions[0].label);
+ } else {
+ await expect(page.locator('input')).toHaveValue('Item 0');
+ }
});
});
From 3e82ea531c616116486ae82b5fba46d34934fdbb Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Mon, 30 Sep 2024 16:46:01 -0400
Subject: [PATCH 38/55] Improvements
---
testing/e2e/app/routes/ComboBox/route.tsx | 103 +++++++++++++++++-----
testing/e2e/app/routes/ComboBox/spec.ts | 39 ++++++--
2 files changed, 113 insertions(+), 29 deletions(-)
diff --git a/testing/e2e/app/routes/ComboBox/route.tsx b/testing/e2e/app/routes/ComboBox/route.tsx
index 6ca62b82520..234a7ab0b67 100644
--- a/testing/e2e/app/routes/ComboBox/route.tsx
+++ b/testing/e2e/app/routes/ComboBox/route.tsx
@@ -3,8 +3,81 @@ import { useSearchParams } from '@remix-run/react';
import * as React from 'react';
export default function ComboBoxTest() {
+ const config = getConfigFromSearchParams();
+
+ if (config.exampleType === 'overflow') {
+ return ;
+ }
+ return ;
+}
+
+const Default = ({
+ config,
+}: {
+ config: ReturnType;
+}) => {
+ const {
+ initialValue,
+ multiple,
+ options,
+ showChangeValueButton,
+ virtualization,
+ } = config;
+ const [value, setValue] = React.useState(initialValue);
+
+ return (
+
+ {showChangeValueButton && (
+
+ )}
+
+
+
+ );
+};
+
+const Overflow = () => {
+ const data = new Array(15).fill(0).map((_, i) => ({
+ label: `option ${i}`,
+ value: i,
+ }));
+ const widths = new Array(10).fill(0).map((_, i) => 790 + i * 3);
+
+ return (
+ <>
+ {widths.slice(0, 10).map((width) => (
+ x.value)}
+ />
+ ))}
+ >
+ );
+};
+
+// ----------------------------------------------------------------------------
+
+function getConfigFromSearchParams() {
const [searchParams] = useSearchParams();
+ const exampleType = searchParams.get('exampleType') as
+ | 'default'
+ | 'overflow'
+ | undefined;
const virtualization = searchParams.get('virtualization') === 'true';
const multiple = searchParams.get('multiple') === 'true';
const showChangeValueButton =
@@ -30,26 +103,12 @@ export default function ComboBoxTest() {
: (JSON.parse(initialValueSearchParam) as number | number[])
: undefined;
- const [value, setValue] = React.useState(initialValue);
-
- return (
-
- {showChangeValueButton && (
-
- )}
-
-
-
- );
+ return {
+ exampleType,
+ virtualization,
+ multiple,
+ showChangeValueButton,
+ options,
+ initialValue,
+ };
}
diff --git a/testing/e2e/app/routes/ComboBox/spec.ts b/testing/e2e/app/routes/ComboBox/spec.ts
index 38cb785205f..bfacf120e78 100644
--- a/testing/e2e/app/routes/ComboBox/spec.ts
+++ b/testing/e2e/app/routes/ComboBox/spec.ts
@@ -21,7 +21,7 @@ test('should select multiple options', async ({ page }) => {
}
const tags = getSelectTagContainerTags(page);
- expect(tags).toHaveCount(options.length);
+ await expect(tags).toHaveCount(options.length);
for (let i = 0; i < (await tags.count()); i++) {
await expect(tags.nth(i)).toHaveText(
@@ -44,7 +44,7 @@ test('should select multiple options', async ({ page }) => {
// Should change internal state when the value prop changes
if (multiple) {
- expect(tags).toHaveCount(defaultOptions.length);
+ await expect(tags).toHaveCount(defaultOptions.length);
for (let i = 0; i < (await tags.count()); i++) {
await expect(tags.nth(i)).toHaveText(defaultOptions[i].label);
@@ -52,7 +52,7 @@ test('should select multiple options', async ({ page }) => {
await page.getByTestId('change-value-to-first-option-button').click();
- expect(tags).toHaveCount(1);
+ await expect(tags).toHaveCount(1);
await expect(tags.first()).toHaveText(defaultOptions[0].label);
} else {
await expect(page.locator('input')).toHaveValue('Item 11');
@@ -66,7 +66,7 @@ test('should select multiple options', async ({ page }) => {
await page.getByRole('option').nth(3).click();
if (multiple) {
- expect(tags).toHaveCount(1);
+ await expect(tags).toHaveCount(1);
await expect(tags.first()).toHaveText(defaultOptions[0].label);
} else {
await expect(page.locator('input')).toHaveValue('Item 0');
@@ -74,6 +74,31 @@ test('should select multiple options', async ({ page }) => {
});
});
+test('should not have flickering tags (fixes #2112)', async ({ page }) => {
+ await page.goto('/ComboBox?exampleType=overflow');
+
+ const selectTagContainers = page.locator(
+ "[role='combobox'] + div:first-of-type",
+ );
+
+ // Wait for page to stabilize
+ await expect(selectTagContainers).toHaveCount(10);
+ for (let i = 0; i < 10; i++) {
+ await expect(selectTagContainers.nth(i).locator('> span')).toHaveCount(
+ i < 5 ? 6 : 7,
+ );
+ }
+
+ // The number of items should not change with time (i.e. no flickering)
+ for (let iteration = 0; iteration < 10; iteration++) {
+ for (let i = 0; i < 10; i++) {
+ expect(selectTagContainers.nth(i).locator('> span')).toHaveCount(
+ i < 5 ? 6 : 7,
+ );
+ }
+ }
+});
+
test.describe('ComboBox (virtualization)', () => {
test('should support keyboard navigation when virtualization is enabled', async ({
page,
@@ -341,14 +366,14 @@ const expectOverflowState = async ({
expectedLastTagTextContent: string | undefined;
}) => {
const tags = getSelectTagContainerTags(page);
- expect(tags).toHaveCount(expectedItemLength);
+ await expect(tags).toHaveCount(expectedItemLength);
- const lastTag = tags.nth((await tags.count()) - 1);
+ const lastTag = tags.last();
if (expectedLastTagTextContent != null) {
await expect(lastTag).toHaveText(expectedLastTagTextContent);
} else {
- expect(tags).toHaveCount(0);
+ await expect(tags).toHaveCount(0);
}
};
From aa8b849b88c1d8c6e044316697bc5f1538c8f01f Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Tue, 1 Oct 2024 10:40:25 -0400
Subject: [PATCH 39/55] Remove unrelated test
---
testing/e2e/app/routes/ComboBox/route.tsx | 30 +----------------------
testing/e2e/app/routes/ComboBox/spec.ts | 25 -------------------
2 files changed, 1 insertion(+), 54 deletions(-)
diff --git a/testing/e2e/app/routes/ComboBox/route.tsx b/testing/e2e/app/routes/ComboBox/route.tsx
index 234a7ab0b67..89d348c86e6 100644
--- a/testing/e2e/app/routes/ComboBox/route.tsx
+++ b/testing/e2e/app/routes/ComboBox/route.tsx
@@ -5,9 +5,6 @@ import * as React from 'react';
export default function ComboBoxTest() {
const config = getConfigFromSearchParams();
- if (config.exampleType === 'overflow') {
- return ;
- }
return ;
}
@@ -47,37 +44,12 @@ const Default = ({
);
};
-const Overflow = () => {
- const data = new Array(15).fill(0).map((_, i) => ({
- label: `option ${i}`,
- value: i,
- }));
- const widths = new Array(10).fill(0).map((_, i) => 790 + i * 3);
-
- return (
- <>
- {widths.slice(0, 10).map((width) => (
- x.value)}
- />
- ))}
- >
- );
-};
-
// ----------------------------------------------------------------------------
function getConfigFromSearchParams() {
const [searchParams] = useSearchParams();
- const exampleType = searchParams.get('exampleType') as
- | 'default'
- | 'overflow'
- | undefined;
+ const exampleType = searchParams.get('exampleType') as 'default' | undefined;
const virtualization = searchParams.get('virtualization') === 'true';
const multiple = searchParams.get('multiple') === 'true';
const showChangeValueButton =
diff --git a/testing/e2e/app/routes/ComboBox/spec.ts b/testing/e2e/app/routes/ComboBox/spec.ts
index bfacf120e78..81d408ae929 100644
--- a/testing/e2e/app/routes/ComboBox/spec.ts
+++ b/testing/e2e/app/routes/ComboBox/spec.ts
@@ -74,31 +74,6 @@ test('should select multiple options', async ({ page }) => {
});
});
-test('should not have flickering tags (fixes #2112)', async ({ page }) => {
- await page.goto('/ComboBox?exampleType=overflow');
-
- const selectTagContainers = page.locator(
- "[role='combobox'] + div:first-of-type",
- );
-
- // Wait for page to stabilize
- await expect(selectTagContainers).toHaveCount(10);
- for (let i = 0; i < 10; i++) {
- await expect(selectTagContainers.nth(i).locator('> span')).toHaveCount(
- i < 5 ? 6 : 7,
- );
- }
-
- // The number of items should not change with time (i.e. no flickering)
- for (let iteration = 0; iteration < 10; iteration++) {
- for (let i = 0; i < 10; i++) {
- expect(selectTagContainers.nth(i).locator('> span')).toHaveCount(
- i < 5 ? 6 : 7,
- );
- }
- }
-});
-
test.describe('ComboBox (virtualization)', () => {
test('should support keyboard navigation when virtualization is enabled', async ({
page,
From 314aac7eb46da90782bbc12c78cee6f71fdfb64c Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Tue, 1 Oct 2024 12:41:16 -0400
Subject: [PATCH 40/55] Reduce diff
---
testing/e2e/app/routes/DropdownMenu/spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/testing/e2e/app/routes/DropdownMenu/spec.ts b/testing/e2e/app/routes/DropdownMenu/spec.ts
index 9ee0974679c..35437928266 100644
--- a/testing/e2e/app/routes/DropdownMenu/spec.ts
+++ b/testing/e2e/app/routes/DropdownMenu/spec.ts
@@ -309,7 +309,7 @@ test.describe('DropdownMenu', () => {
await page.locator('button').first().click();
await page.locator('button').nth(10).scrollIntoViewIfNeeded();
- await expect(page.getByRole('menu')).toBeVisible();
+ await expect(page.getByRole('menu')).toHaveCount(1);
});
});
From c23cffb143103ac276a1bc0a2adac0666915f9c4 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Tue, 1 Oct 2024 19:38:48 -0400
Subject: [PATCH 41/55] Changes
---
.../src/core/Breadcrumbs/Breadcrumbs.tsx | 2 +-
.../src/core/ButtonGroup/ButtonGroup.tsx | 2 +-
.../src/core/Select/SelectTagContainer.tsx | 2 +-
.../src/core/Table/TablePaginator.tsx | 2 +-
.../utils/components/MiddleTextTruncation.tsx | 2 +-
.../src/utils/hooks/useOverflow.tsx | 18 +++++++++---------
6 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index 82b09fe528a..74f49e4501b 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -122,7 +122,7 @@ const BreadcrumbsComponent = React.forwardRef((props, ref) => {
...rest
} = props;
- const [overflowRef, visibleCount] = useOverflow(items);
+ const [overflowRef, visibleCount] = useOverflow(items.length);
const refs = useMergedRefs(overflowRef, ref);
return (
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index 00739344889..1594117dad8 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -187,7 +187,7 @@ const OverflowGroup = React.forwardRef((props, forwardedRef) => {
);
const [overflowRef, visibleCount] = useOverflow(
- items,
+ items.length,
!overflowButton,
orientation,
);
diff --git a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
index 28ec34d33e5..00336400ff4 100644
--- a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
+++ b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
@@ -20,7 +20,7 @@ type SelectTagContainerProps = {
export const SelectTagContainer = React.forwardRef((props, ref) => {
const { tags, className, ...rest } = props;
- const [containerRef, visibleCount] = useOverflow(tags);
+ const [containerRef, visibleCount] = useOverflow(tags.length);
const refs = useMergedRefs(ref, containerRef);
return (
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index 8ef30a080a4..7196c350b75 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -197,7 +197,7 @@ export const TablePaginator = (props: TablePaginatorProps) => {
.map((_, index) => pageButton(index)),
[pageButton, totalPagesCount],
);
- const [overflowRef, visibleCount] = useOverflow(pageList);
+ const [overflowRef, visibleCount] = useOverflow(pageList.length);
const [paginatorResizeRef, paginatorWidth] = useContainerWidth();
diff --git a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
index e7d81268fb0..ece5ac57d56 100644
--- a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
+++ b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
@@ -46,7 +46,7 @@ export type MiddleTextTruncationProps = {
export const MiddleTextTruncation = (props: MiddleTextTruncationProps) => {
const { text, endCharsCount = 6, textRenderer, style, ...rest } = props;
- const [ref, visibleCount] = useOverflow(text);
+ const [ref, visibleCount] = useOverflow(text.length);
const truncatedText = React.useMemo(() => {
if (visibleCount < text.length) {
diff --git a/packages/itwinui-react/src/utils/hooks/useOverflow.tsx b/packages/itwinui-react/src/utils/hooks/useOverflow.tsx
index 157571f75c2..14aa58641ea 100644
--- a/packages/itwinui-react/src/utils/hooks/useOverflow.tsx
+++ b/packages/itwinui-react/src/utils/hooks/useOverflow.tsx
@@ -16,7 +16,7 @@ const STARTING_MAX_ITEMS_COUNT = 20;
* The returned number should be used to render the element with fewer items.
*
* @private
- * @param items Items that this element contains.
+ * @param itemsCount Number of items that this element contains.
* @param disabled Set to true to disconnect the observer.
* @param dimension 'horizontal' (default) or 'vertical'
* @returns [callback ref to set on container, stateful count of visible items]
@@ -32,14 +32,14 @@ const STARTING_MAX_ITEMS_COUNT = 20;
* );
*/
export const useOverflow = (
- items: React.ReactNode[] | string,
+ itemsCount: number,
disabled = false,
orientation: 'horizontal' | 'vertical' = 'horizontal',
) => {
const containerRef = React.useRef(null);
const [visibleCount, setVisibleCount] = React.useState(() =>
- disabled ? items.length : Math.min(items.length, STARTING_MAX_ITEMS_COUNT),
+ disabled ? itemsCount : Math.min(itemsCount, STARTING_MAX_ITEMS_COUNT),
);
const needsFullRerender = React.useRef(true);
@@ -56,12 +56,12 @@ export const useOverflow = (
useLayoutEffect(() => {
if (disabled) {
- setVisibleCount(items.length);
+ setVisibleCount(itemsCount);
} else {
- setVisibleCount(Math.min(items.length, STARTING_MAX_ITEMS_COUNT));
+ setVisibleCount(Math.min(itemsCount, STARTING_MAX_ITEMS_COUNT));
needsFullRerender.current = true;
}
- }, [containerSize, disabled, items]);
+ }, [containerSize, disabled, itemsCount]);
const mergedRefs = useMergedRefs(containerRef, resizeRef);
@@ -89,17 +89,17 @@ export const useOverflow = (
// Previous `useEffect` might have updated visible count, but we still have old one
// If it is 0, lets try to update it with items length.
const currentVisibleCount =
- visibleCount || Math.min(items.length, STARTING_MAX_ITEMS_COUNT);
+ visibleCount || Math.min(itemsCount, STARTING_MAX_ITEMS_COUNT);
const avgItemSize = childrenSize / currentVisibleCount;
const visibleItems = Math.floor(availableSize / avgItemSize);
if (!isNaN(visibleItems)) {
// Doubling the visible items to overflow the container. Just to be safe.
- setVisibleCount(Math.min(items.length, visibleItems * 2));
+ setVisibleCount(Math.min(itemsCount, visibleItems * 2));
}
}
needsFullRerender.current = false;
- }, [containerSize, visibleCount, disabled, items.length, orientation]);
+ }, [containerSize, visibleCount, disabled, itemsCount, orientation]);
useLayoutEffect(() => {
previousContainerSize.current = containerSize;
From 2902ba080eb8da638c2c31d1f2bb03ab7b9d8ab0 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Wed, 2 Oct 2024 16:07:42 -0400
Subject: [PATCH 42/55] Fix unit tests
---
.../itwinui-react/src/core/Breadcrumbs/Breadcrumbs.test.tsx | 4 ++--
packages/itwinui-react/src/core/Table/Table.test.tsx | 4 ++--
packages/itwinui-react/src/core/Table/TablePaginator.test.tsx | 4 ++--
packages/itwinui-react/src/utils/hooks/useOverflow.test.tsx | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.test.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.test.tsx
index 15eea9a0711..eacaed76d28 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.test.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.test.tsx
@@ -58,9 +58,9 @@ const assertBaseElement = (
const useOverflowMock = vi
.spyOn(UseOverflow, 'useOverflow')
- .mockImplementation((items) => [vi.fn(), items.length]);
+ .mockImplementation((itemsCount) => [vi.fn(), itemsCount]);
beforeEach(() => {
- useOverflowMock.mockImplementation((items) => [vi.fn(), items.length]);
+ useOverflowMock.mockImplementation((itemsCount) => [vi.fn(), itemsCount]);
});
it('should render all elements in default state', () => {
diff --git a/packages/itwinui-react/src/core/Table/Table.test.tsx b/packages/itwinui-react/src/core/Table/Table.test.tsx
index 6b9e49882e0..cd478def0e9 100644
--- a/packages/itwinui-react/src/core/Table/Table.test.tsx
+++ b/packages/itwinui-react/src/core/Table/Table.test.tsx
@@ -2500,9 +2500,9 @@ it('should handle unwanted actions on editable cell', async () => {
});
it('should render data in pages', async () => {
- vi.spyOn(UseOverflow, 'useOverflow').mockImplementation((items) => [
+ vi.spyOn(UseOverflow, 'useOverflow').mockImplementation((itemsCount) => [
vi.fn(),
- items.length,
+ itemsCount,
]);
const { container } = renderComponent({
data: mockedData(100),
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.test.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.test.tsx
index ccb8544f9a9..6291fdb1ecf 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.test.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.test.tsx
@@ -22,9 +22,9 @@ const renderComponent = (props?: Partial) => {
};
beforeEach(() => {
- vi.spyOn(UseOverflow, 'useOverflow').mockImplementation((items) => [
+ vi.spyOn(UseOverflow, 'useOverflow').mockImplementation((itemsCount) => [
vi.fn(),
- items.length,
+ itemsCount,
]);
});
diff --git a/packages/itwinui-react/src/utils/hooks/useOverflow.test.tsx b/packages/itwinui-react/src/utils/hooks/useOverflow.test.tsx
index 8187717394c..9b67c5dae48 100644
--- a/packages/itwinui-react/src/utils/hooks/useOverflow.test.tsx
+++ b/packages/itwinui-react/src/utils/hooks/useOverflow.test.tsx
@@ -17,7 +17,7 @@ const MockComponent = ({
orientation?: 'horizontal' | 'vertical';
}) => {
const [overflowRef, visibleCount] = useOverflow(
- children,
+ children.length,
disableOverflow,
orientation,
);
From c67f2ff48acf000a0cf3c9627a9118b4912952d2 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Wed, 2 Oct 2024 16:15:56 -0400
Subject: [PATCH 43/55] Update outdated test
---
testing/e2e/app/routes/Table/spec.ts | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/testing/e2e/app/routes/Table/spec.ts b/testing/e2e/app/routes/Table/spec.ts
index 9cc5355ac26..2115b181f5e 100644
--- a/testing/e2e/app/routes/Table/spec.ts
+++ b/testing/e2e/app/routes/Table/spec.ts
@@ -473,7 +473,15 @@ test.describe('Table Paginator', () => {
const paginatorButtons = page.locator('#paginator button', {
hasText: /[0-9]+/,
});
- await expect(paginatorButtons).toHaveText(['1', '5', '6', '7', '11']);
+ await expect(paginatorButtons).toHaveText([
+ '1',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '11',
+ ]);
await expect(paginatorButtons.nth(2)).toHaveAttribute(
'data-iui-active',
'true',
From 557e096e61affb8648a54cd82c9fefe94c038b78 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Wed, 2 Oct 2024 16:30:57 -0400
Subject: [PATCH 44/55] Fix test
---
testing/e2e/app/routes/Table/spec.ts | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/testing/e2e/app/routes/Table/spec.ts b/testing/e2e/app/routes/Table/spec.ts
index 2115b181f5e..9cc5355ac26 100644
--- a/testing/e2e/app/routes/Table/spec.ts
+++ b/testing/e2e/app/routes/Table/spec.ts
@@ -473,15 +473,7 @@ test.describe('Table Paginator', () => {
const paginatorButtons = page.locator('#paginator button', {
hasText: /[0-9]+/,
});
- await expect(paginatorButtons).toHaveText([
- '1',
- '4',
- '5',
- '6',
- '7',
- '8',
- '11',
- ]);
+ await expect(paginatorButtons).toHaveText(['1', '5', '6', '7', '11']);
await expect(paginatorButtons.nth(2)).toHaveAttribute(
'data-iui-active',
'true',
From 11af8764addaff6f560a77a585e753b8b78bc40d Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Wed, 2 Oct 2024 16:39:23 -0400
Subject: [PATCH 45/55] Fix test
---
testing/e2e/app/routes/Table/spec.ts | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/testing/e2e/app/routes/Table/spec.ts b/testing/e2e/app/routes/Table/spec.ts
index 9cc5355ac26..aefbda64782 100644
--- a/testing/e2e/app/routes/Table/spec.ts
+++ b/testing/e2e/app/routes/Table/spec.ts
@@ -473,8 +473,16 @@ test.describe('Table Paginator', () => {
const paginatorButtons = page.locator('#paginator button', {
hasText: /[0-9]+/,
});
- await expect(paginatorButtons).toHaveText(['1', '5', '6', '7', '11']);
- await expect(paginatorButtons.nth(2)).toHaveAttribute(
+ await expect(paginatorButtons).toHaveText([
+ '1',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '11',
+ ]);
+ await expect(paginatorButtons.nth(3)).toHaveAttribute(
'data-iui-active',
'true',
);
From 192964d14b90db7709b697bb4ad55213507e27eb Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Wed, 2 Oct 2024 17:06:12 -0400
Subject: [PATCH 46/55] =?UTF-8?q?`items`=20=E2=86=92=20`itemsCount`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/core/Breadcrumbs/Breadcrumbs.tsx | 6 +++++-
.../src/core/ButtonGroup/ButtonGroup.tsx | 2 +-
.../src/core/Select/SelectTagContainer.tsx | 2 +-
.../src/core/Table/TablePaginator.tsx | 2 +-
.../utils/components/MiddleTextTruncation.tsx | 2 +-
.../utils/components/OverflowContainer.tsx | 19 ++++++++-----------
6 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index 493d701a604..eb7e7a05450 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -136,7 +136,11 @@ const BreadcrumbsComponent = React.forwardRef((props, ref) => {
aria-label='Breadcrumb'
{...rest}
>
-
+
{
return (
{
return (
{
)}
{showPagesList && (
-
+
{
whiteSpace: 'nowrap',
...style,
}}
- items={text}
+ itemsCount={text.length}
{...rest}
>
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index 32ce4f678a7..8b0047e924a 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -15,10 +15,7 @@ type OverflowContainerProps = {
* @default 'horizontal'
*/
overflowOrientation?: 'horizontal' | 'vertical';
- /**
- * TODO: Will likely be removed in a later PR in the stacked PRs. If not, remove this TODO.
- */
- items: React.ReactNode[] | string;
+ itemsCount: number;
};
/**
@@ -28,17 +25,17 @@ type OverflowContainerProps = {
* - Wrap overflow content in `OverflowContainer.OverflowNode` to conditionally render it when overflowing.
*/
const OverflowContainerComponent = React.forwardRef((props, ref) => {
- const { items, children, overflowOrientation, ...rest } = props;
+ const { itemsCount, children, overflowOrientation, ...rest } = props;
const [containerRef, visibleCount] = useOverflow(
- items,
+ itemsCount,
false,
overflowOrientation,
);
const overflowContainerContextValue = React.useMemo(
- () => ({ visibleCount, itemCount: items.length }),
- [items.length, visibleCount],
+ () => ({ visibleCount, itemsCount }),
+ [itemsCount, visibleCount],
);
return (
@@ -61,8 +58,8 @@ const OverflowContainerOverflowNode = (
) => {
const { children } = props;
- const { visibleCount, itemCount } = useOverflowContainerContext();
- const isOverflowing = visibleCount < itemCount;
+ const { visibleCount, itemsCount } = useOverflowContainerContext();
+ const isOverflowing = visibleCount < itemsCount;
return isOverflowing && children;
};
@@ -81,7 +78,7 @@ export const OverflowContainer = Object.assign(OverflowContainerComponent, {
const OverflowContainerContext = React.createContext<
| {
visibleCount: number;
- itemCount: number;
+ itemsCount: number;
}
| undefined
>(undefined);
From eb1cd2e1cb31291f6ee46afdbb90dfafbd23b2e7 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Tue, 8 Oct 2024 16:39:26 -0400
Subject: [PATCH 47/55] Comments on components
---
.../src/core/Breadcrumbs/Breadcrumbs.tsx | 3 +-
.../src/core/ButtonGroup/ButtonGroup.tsx | 8 +-
.../src/core/Table/TablePaginator.tsx | 74 +++++++++----------
3 files changed, 43 insertions(+), 42 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index eb7e7a05450..15a9112b3a7 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -115,7 +115,7 @@ type BreadcrumbsProps = {
const BreadcrumbsComponent = React.forwardRef((props, ref) => {
const {
children: childrenProp,
- currentIndex: currentIndexProp,
+ currentIndex = React.Children.count(childrenProp) - 1,
separator,
overflowButton,
className,
@@ -126,7 +126,6 @@ const BreadcrumbsComponent = React.forwardRef((props, ref) => {
() => React.Children.toArray(childrenProp),
[childrenProp],
);
- const currentIndex = currentIndexProp || items.length - 1;
return (
{
? items.length - visibleCount
: visibleCount - 1;
- return !(visibleCount < items.length) ? (
- items
- ) : (
+ if (!(visibleCount < items.length)) {
+ return items;
+ }
+
+ return (
<>
{overflowButton &&
overflowPlacement === 'start' &&
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index 8782c57d579..11b6bbbc0ca 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -325,43 +325,6 @@ const TablePaginatorCenterContent = (
} = props;
const { visibleCount } = useOverflowContainerContext();
- const hasNoRows = totalPagesCount === 0;
-
- const halfVisibleCount = Math.floor(visibleCount / 2);
- let startPage = focusedIndex - halfVisibleCount;
- let endPage = focusedIndex + halfVisibleCount + 1;
- if (startPage < 0) {
- endPage = Math.min(totalPagesCount, endPage + Math.abs(startPage)); // If no room at the beginning, show extra pages at the end
- startPage = 0;
- }
- if (endPage > totalPagesCount) {
- startPage = Math.max(0, startPage - (endPage - totalPagesCount)); // If no room at the end, show extra pages at the beginning
- endPage = totalPagesCount;
- }
-
- const ellipsis = (
-
- …
-
- );
-
- const noRowsContent = (
- <>
- {isLoading ? (
-
- ) : (
-
- )}
- >
- );
-
const onKeyDown = (event: React.KeyboardEvent) => {
// alt + arrow keys are used by browser/assistive technologies
if (event.altKey) {
@@ -407,6 +370,43 @@ const TablePaginatorCenterContent = (
}
};
+ const halfVisibleCount = Math.floor(visibleCount / 2);
+ let startPage = focusedIndex - halfVisibleCount;
+ let endPage = focusedIndex + halfVisibleCount + 1;
+ if (startPage < 0) {
+ endPage = Math.min(totalPagesCount, endPage + Math.abs(startPage)); // If no room at the beginning, show extra pages at the end
+ startPage = 0;
+ }
+ if (endPage > totalPagesCount) {
+ startPage = Math.max(0, startPage - (endPage - totalPagesCount)); // If no room at the end, show extra pages at the beginning
+ endPage = totalPagesCount;
+ }
+
+ const hasNoRows = totalPagesCount === 0;
+
+ const ellipsis = (
+
+ …
+
+ );
+
+ const noRowsContent = (
+ <>
+ {isLoading ? (
+
+ ) : (
+
+ )}
+ >
+ );
+
return (
<>
Date: Tue, 8 Oct 2024 16:50:32 -0400
Subject: [PATCH 48/55] OverflowContainer comments
---
.../src/core/Breadcrumbs/Breadcrumbs.tsx | 3 +--
.../src/core/ButtonGroup/ButtonGroup.tsx | 8 ++----
.../src/core/Select/SelectTagContainer.tsx | 7 ++----
.../src/core/Table/TablePaginator.tsx | 3 +--
.../utils/components/MiddleTextTruncation.tsx | 7 ++----
.../utils/components/OverflowContainer.tsx | 25 ++++++++++++-------
6 files changed, 24 insertions(+), 29 deletions(-)
diff --git a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
index 15a9112b3a7..1c09a82d759 100644
--- a/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/itwinui-react/src/core/Breadcrumbs/Breadcrumbs.tsx
@@ -8,7 +8,6 @@ import {
SvgChevronRight,
Box,
OverflowContainer,
- useOverflowContainerContext,
useWarningLogger,
} from '../../utils/index.js';
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
@@ -163,7 +162,7 @@ type BreadcrumbContentProps = Omit & {
const BreadcrumbContent = (props: BreadcrumbContentProps) => {
const { children: items, currentIndex, overflowButton, separator } = props;
- const { visibleCount } = useOverflowContainerContext();
+ const { visibleCount } = OverflowContainer.useContext();
return (
<>
diff --git a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
index 8f86c2cf693..188653dcde2 100644
--- a/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
+++ b/packages/itwinui-react/src/core/ButtonGroup/ButtonGroup.tsx
@@ -4,11 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as React from 'react';
import cx from 'classnames';
-import {
- Box,
- OverflowContainer,
- useOverflowContainerContext,
-} from '../../utils/index.js';
+import { Box, OverflowContainer } from '../../utils/index.js';
import type {
AnyString,
PolymorphicForwardRefComponent,
@@ -232,7 +228,7 @@ type OverflowGroupContentProps = Pick<
const OverflowGroupContent = (props: OverflowGroupContentProps) => {
const { overflowButton, overflowPlacement, items } = props;
- const { visibleCount } = useOverflowContainerContext();
+ const { visibleCount } = OverflowContainer.useContext();
const overflowStart =
overflowPlacement === 'start'
diff --git a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
index 3d53afcbe49..8311ab26465 100644
--- a/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
+++ b/packages/itwinui-react/src/core/Select/SelectTagContainer.tsx
@@ -6,10 +6,7 @@ import * as React from 'react';
import cx from 'classnames';
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { SelectTag } from './SelectTag.js';
-import {
- OverflowContainer,
- useOverflowContainerContext,
-} from '../../utils/index.js';
+import { OverflowContainer } from '../../utils/index.js';
type SelectTagContainerProps = {
/**
@@ -48,7 +45,7 @@ type SelectTagContainerContentProps = {
const SelectTagContainerContent = (props: SelectTagContainerContentProps) => {
const { tags } = props;
- const { visibleCount } = useOverflowContainerContext();
+ const { visibleCount } = OverflowContainer.useContext();
return (
<>
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index 11b6bbbc0ca..6bffdcb3d36 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -17,7 +17,6 @@ import {
SvgChevronRight,
Box,
OverflowContainer,
- useOverflowContainerContext,
} from '../../utils/index.js';
import type { CommonProps } from '../../utils/index.js';
import type { TablePaginatorRendererProps } from './Table.js';
@@ -323,7 +322,7 @@ const TablePaginatorCenterContent = (
isLoading,
size,
} = props;
- const { visibleCount } = useOverflowContainerContext();
+ const { visibleCount } = OverflowContainer.useContext();
const onKeyDown = (event: React.KeyboardEvent) => {
// alt + arrow keys are used by browser/assistive technologies
diff --git a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
index 578e13cfe58..ec22e27733e 100644
--- a/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
+++ b/packages/itwinui-react/src/utils/components/MiddleTextTruncation.tsx
@@ -4,10 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as React from 'react';
import type { CommonProps } from '../props.js';
-import {
- OverflowContainer,
- useOverflowContainerContext,
-} from './OverflowContainer.js';
+import { OverflowContainer } from './OverflowContainer.js';
const ELLIPSIS_CHAR = '…';
@@ -74,7 +71,7 @@ if (process.env.NODE_ENV === 'development') {
const MiddleTextTruncationContent = (props: MiddleTextTruncationProps) => {
const { text, endCharsCount = 6, textRenderer } = props;
- const { visibleCount } = useOverflowContainerContext();
+ const { visibleCount } = OverflowContainer.useContext();
const truncatedText = React.useMemo(() => {
if (visibleCount < text.length) {
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index 8b0047e924a..317ef056a99 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -15,15 +15,12 @@ type OverflowContainerProps = {
* @default 'horizontal'
*/
overflowOrientation?: 'horizontal' | 'vertical';
+ /**
+ * Count of the *original* items (i.e. when sufficient space is available).
+ */
itemsCount: number;
};
-/**
- * Wrapper over `useOverflow`.
- *
- * - Use `useOverflowContainerContext` to get overflow related properties.
- * - Wrap overflow content in `OverflowContainer.OverflowNode` to conditionally render it when overflowing.
- */
const OverflowContainerComponent = React.forwardRef((props, ref) => {
const { itemsCount, children, overflowOrientation, ...rest } = props;
@@ -61,16 +58,26 @@ const OverflowContainerOverflowNode = (
const { visibleCount, itemsCount } = useOverflowContainerContext();
const isOverflowing = visibleCount < itemsCount;
- return isOverflowing && children;
+ return isOverflowing ? children : null;
};
// ----------------------------------------------------------------------------
+/**
+ * Wrapper over `useOverflow`.
+ *
+ * - Use `OverflowContainer.useContext()` to get overflow related properties.
+ * - Wrap overflow content in `OverflowContainer.OverflowNode` to conditionally render it when overflowing.
+ */
export const OverflowContainer = Object.assign(OverflowContainerComponent, {
/**
* Wrap overflow content in this component to conditionally render it when overflowing.
*/
OverflowNode: OverflowContainerOverflowNode,
+ /**
+ * Get overflow related properties of the nearest `OverflowContainer` ancestor.
+ */
+ useContext: useOverflowContainerContext,
});
// ----------------------------------------------------------------------------
@@ -86,7 +93,7 @@ if (process.env.NODE_ENV === 'development') {
OverflowContainerContext.displayName = 'OverflowContainerContext';
}
-export const useOverflowContainerContext = () => {
+function useOverflowContainerContext() {
const overflowContainerContext = useSafeContext(OverflowContainerContext);
return overflowContainerContext;
-};
+}
From 33a02d64a38c6191e5b57bfad5c1decc0453fde0 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Fri, 11 Oct 2024 11:56:40 -0400
Subject: [PATCH 49/55] Comment suggestion patch
Co-authored-by: Mayank <9084735+mayank99@users.noreply.github.com>
---
.../src/core/Table/TablePaginator.tsx | 325 ++++++++----------
1 file changed, 148 insertions(+), 177 deletions(-)
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index 6bffdcb3d36..a7729d31d98 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -169,34 +169,52 @@ export const TablePaginator = (props: TablePaginatorProps) => {
}, [focusedIndex]);
const buttonSize = size != 'default' ? 'small' : undefined;
+ const totalPagesCount = Math.ceil(totalRowsCount / pageSize);
- const pageButton = React.useCallback(
- (index: number, tabIndex = index === focusedIndex ? 0 : -1) => (
-
- ),
- [focusedIndex, currentPage, localization, buttonSize, onPageChange],
- );
+ const onKeyDown = (event: React.KeyboardEvent) => {
+ // alt + arrow keys are used by browser/assistive technologies
+ if (event.altKey) {
+ return;
+ }
- const totalPagesCount = Math.ceil(totalRowsCount / pageSize);
- const pageList = React.useMemo(
- () =>
- new Array(totalPagesCount)
- .fill(null)
- .map((_, index) => pageButton(index)),
- [pageButton, totalPagesCount],
- );
+ const focusPage = (delta: number) => {
+ const newFocusedIndex = getBoundedValue(
+ focusedIndex + delta,
+ 0,
+ totalPagesCount - 1,
+ );
+
+ needFocus.current = true;
+ if (focusActivationMode === 'auto') {
+ onPageChange(newFocusedIndex);
+ } else {
+ setFocusedIndex(newFocusedIndex);
+ }
+ };
+
+ switch (event.key) {
+ case 'ArrowRight': {
+ focusPage(+1);
+ event.preventDefault();
+ break;
+ }
+ case 'ArrowLeft': {
+ focusPage(-1);
+ event.preventDefault();
+ break;
+ }
+ case 'Enter':
+ case ' ':
+ case 'Spacebar': {
+ if (focusActivationMode === 'manual') {
+ onPageChange(focusedIndex);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ };
const [paginatorResizeRef, paginatorWidth] = useContainerWidth();
@@ -204,6 +222,19 @@ export const TablePaginator = (props: TablePaginatorProps) => {
const showPageSizeList =
pageSizeList && !!onPageSizeChange && !!totalRowsCount;
+ const hasNoRows = totalPagesCount === 0;
+ const noRowsContent = (
+ <>
+ {isLoading ? (
+
+ ) : (
+
+ )}
+ >
+ );
+
if (!showPagesList && !showPageSizeList) {
return null;
}
@@ -220,23 +251,45 @@ export const TablePaginator = (props: TablePaginatorProps) => {
)}
{showPagesList && (
-
-
+
+ onPageChange(currentPage - 1)}
+ size={buttonSize}
+ aria-label={localization.previousPage}
+ >
+
+
+
+ {hasNoRows ? (
+ noRowsContent
+ ) : (
+
+ )}
+
+ onPageChange(currentPage + 1)}
+ size={buttonSize}
+ aria-label={localization.nextPage}
+ >
+
+
)}
@@ -282,92 +335,57 @@ export const TablePaginator = (props: TablePaginatorProps) => {
// ----------------------------------------------------------------------------
-type TablePaginatorCenterContentProps = Pick<
+type TablePaginatorPageButtonsProps = Pick<
TablePaginatorProps,
'onPageChange'
> &
- Required<
- Pick<
- TablePaginatorProps,
- 'localization' | 'size' | 'focusActivationMode' | 'isLoading'
- >
- > & {
+ Required> & {
focusedIndex: number;
totalPagesCount: number;
- needFocus: React.MutableRefObject;
- setFocusedIndex: React.Dispatch>;
currentPage: number;
- buttonSize: 'small' | undefined;
- pageListRef: React.MutableRefObject;
- pageButton: (index: number, tabIndex?: number) => React.ReactNode;
- pageList: React.ReactNode[];
};
-const TablePaginatorCenterContent = (
- props: TablePaginatorCenterContentProps,
-) => {
+const TablePaginatorPageButtons = (props: TablePaginatorPageButtonsProps) => {
const {
focusedIndex,
- focusActivationMode,
totalPagesCount,
- needFocus,
onPageChange,
- setFocusedIndex,
currentPage,
localization,
- buttonSize,
- pageListRef,
- pageButton,
- pageList,
isLoading,
size,
} = props;
- const { visibleCount } = OverflowContainer.useContext();
- const onKeyDown = (event: React.KeyboardEvent) => {
- // alt + arrow keys are used by browser/assistive technologies
- if (event.altKey) {
- return;
- }
+ const { visibleCount } = OverflowContainer.useContext();
- const focusPage = (delta: number) => {
- const newFocusedIndex = getBoundedValue(
- focusedIndex + delta,
- 0,
- totalPagesCount - 1,
- );
+ const buttonSize = size != 'default' ? 'small' : undefined;
- needFocus.current = true;
- if (focusActivationMode === 'auto') {
- onPageChange(newFocusedIndex);
- } else {
- setFocusedIndex(newFocusedIndex);
- }
- };
+ const pageButton = React.useCallback(
+ (index: number, tabIndex = index === focusedIndex ? 0 : -1) => (
+
+ ),
+ [focusedIndex, currentPage, localization, buttonSize, onPageChange],
+ );
- switch (event.key) {
- case 'ArrowRight': {
- focusPage(+1);
- event.preventDefault();
- break;
- }
- case 'ArrowLeft': {
- focusPage(-1);
- event.preventDefault();
- break;
- }
- case 'Enter':
- case ' ':
- case 'Spacebar': {
- if (focusActivationMode === 'manual') {
- onPageChange(focusedIndex);
- }
- break;
- }
- default:
- break;
- }
- };
+ const pageList = React.useMemo(
+ () =>
+ new Array(totalPagesCount)
+ .fill(null)
+ .map((_, index) => pageButton(index)),
+ [pageButton, totalPagesCount],
+ );
const halfVisibleCount = Math.floor(visibleCount / 2);
let startPage = focusedIndex - halfVisibleCount;
@@ -381,8 +399,6 @@ const TablePaginatorCenterContent = (
endPage = totalPagesCount;
}
- const hasNoRows = totalPagesCount === 0;
-
const ellipsis = (
);
- const noRowsContent = (
- <>
- {isLoading ? (
-
- ) : (
-
- )}
- >
- );
+ if (visibleCount === 1) {
+ return pageButton(focusedIndex);
+ }
return (
<>
- onPageChange(currentPage - 1)}
- size={buttonSize}
- aria-label={localization.previousPage}
- >
-
-
-
- {(() => {
- if (hasNoRows) {
- return noRowsContent;
- }
- if (visibleCount === 1) {
- return pageButton(focusedIndex);
- }
- return (
- <>
- {startPage !== 0 && (
- <>
- {pageButton(0, 0)}
- {ellipsis}
- >
- )}
- {pageList.slice(startPage, endPage)}
- {endPage !== totalPagesCount && !isLoading && (
- <>
- {ellipsis}
- {pageButton(totalPagesCount - 1, 0)}
- >
- )}
- {isLoading && (
- <>
- {ellipsis}
-
- >
- )}
- >
- );
- })()}
-
- onPageChange(currentPage + 1)}
- size={buttonSize}
- aria-label={localization.nextPage}
- >
-
-
+ {startPage !== 0 && (
+ <>
+ {pageButton(0, 0)}
+ {ellipsis}
+ >
+ )}
+ {pageList.slice(startPage, endPage)}
+ {endPage !== totalPagesCount && !isLoading && (
+ <>
+ {ellipsis}
+ {pageButton(totalPagesCount - 1, 0)}
+ >
+ )}
+ {isLoading && (
+ <>
+ {ellipsis}
+
+ >
+ )}
>
);
};
From fb18559ab16fd60605320605a61895c0692d6daa Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Mon, 14 Oct 2024 14:02:25 -0400
Subject: [PATCH 50/55] Fix focusedIndex out of sync with currentPage
---
packages/itwinui-react/src/core/Table/TablePaginator.tsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index a7729d31d98..1a05ad084d2 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -147,7 +147,7 @@ export const TablePaginator = (props: TablePaginatorProps) => {
const pageListRef = React.useRef(null);
const [focusedIndex, setFocusedIndex] = React.useState(currentPage);
- React.useEffect(() => {
+ React.useLayoutEffect(() => {
setFocusedIndex(currentPage);
}, [currentPage]);
@@ -356,6 +356,8 @@ const TablePaginatorPageButtons = (props: TablePaginatorPageButtonsProps) => {
size,
} = props;
+ console.log('focusedIndex', focusedIndex);
+
const { visibleCount } = OverflowContainer.useContext();
const buttonSize = size != 'default' ? 'small' : undefined;
@@ -399,6 +401,8 @@ const TablePaginatorPageButtons = (props: TablePaginatorPageButtonsProps) => {
endPage = totalPagesCount;
}
+ console.log(startPage, endPage, focusedIndex);
+
const ellipsis = (
Date: Mon, 14 Oct 2024 14:55:49 -0400
Subject: [PATCH 51/55] =?UTF-8?q?=F0=9F=94=A7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.changeset/tidy-poems-buy.md | 5 +++++
packages/itwinui-react/src/core/Table/TablePaginator.tsx | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)
create mode 100644 .changeset/tidy-poems-buy.md
diff --git a/.changeset/tidy-poems-buy.md b/.changeset/tidy-poems-buy.md
new file mode 100644
index 00000000000..17bfb538035
--- /dev/null
+++ b/.changeset/tidy-poems-buy.md
@@ -0,0 +1,5 @@
+---
+'@itwin/itwinui-react': patch
+---
+
+Fixed arrow keys page navigation in `TablePaginator`.
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index 7196c350b75..0e78d2fdd22 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -20,6 +20,7 @@ import {
} from '../../utils/index.js';
import type { CommonProps } from '../../utils/index.js';
import type { TablePaginatorRendererProps } from './Table.js';
+import { styles } from '../../styles.js';
const defaultLocalization = {
pageSizeLabel: (size: number) => `${size} per page`,
@@ -159,7 +160,7 @@ export const TablePaginator = (props: TablePaginatorProps) => {
if (isMounted.current && needFocus.current) {
const buttonToFocus = Array.from(
pageListRef.current?.querySelectorAll(
- '.iui-table-paginator-page-button',
+ `.${styles['iui-table-paginator-page-button']}`,
) ?? [],
).find((el) => el.textContent?.trim() === (focusedIndex + 1).toString());
(buttonToFocus as HTMLButtonElement | undefined)?.focus();
From 863f69e5679f26328b9c4f77e23e92d8121aa34b Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Mon, 14 Oct 2024 16:34:26 -0400
Subject: [PATCH 52/55] Fix e2e tests
---
packages/itwinui-react/src/core/Table/TablePaginator.tsx | 4 ----
testing/e2e/app/routes/Table/spec.ts | 6 +++---
2 files changed, 3 insertions(+), 7 deletions(-)
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index 12dfc98b4a2..934cf4a2122 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -357,8 +357,6 @@ const TablePaginatorPageButtons = (props: TablePaginatorPageButtonsProps) => {
size,
} = props;
- console.log('focusedIndex', focusedIndex);
-
const { visibleCount } = OverflowContainer.useContext();
const buttonSize = size != 'default' ? 'small' : undefined;
@@ -402,8 +400,6 @@ const TablePaginatorPageButtons = (props: TablePaginatorPageButtonsProps) => {
endPage = totalPagesCount;
}
- console.log(startPage, endPage, focusedIndex);
-
const ellipsis = (
{
await setContainerSize(page, '800px');
- // Go to the 6th page
- await page.locator('button').last().click({ clickCount: 5 });
+ // Go to the 5th page
+ await page.locator('#paginator button').nth(5).click();
const paginatorButtons = page.locator('#paginator button', {
hasText: /[0-9]+/,
});
await expect(paginatorButtons).toHaveText([
'1',
+ '3',
'4',
'5',
'6',
'7',
- '8',
'11',
]);
await expect(paginatorButtons.nth(3)).toHaveAttribute(
From c1bb0205424e689e2610909eb8117093a6bc4f7c Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Tue, 15 Oct 2024 16:57:35 -0400
Subject: [PATCH 53/55] nits
---
.../itwinui-react/src/utils/components/OverflowContainer.tsx | 3 +++
testing/e2e/app/routes/Table/spec.ts | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
index 317ef056a99..548b586195b 100644
--- a/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
+++ b/packages/itwinui-react/src/utils/components/OverflowContainer.tsx
@@ -50,6 +50,9 @@ type OverflowContainerOverflowNodeProps = {
children: React.ReactNode;
};
+/**
+ * Shows the content only when the container is overflowing.
+ */
const OverflowContainerOverflowNode = (
props: OverflowContainerOverflowNodeProps,
) => {
diff --git a/testing/e2e/app/routes/Table/spec.ts b/testing/e2e/app/routes/Table/spec.ts
index 1d34edc17bf..ce1c65522b9 100644
--- a/testing/e2e/app/routes/Table/spec.ts
+++ b/testing/e2e/app/routes/Table/spec.ts
@@ -454,7 +454,7 @@ test.describe('Table Paginator', () => {
);
// Go to the 6th page
- await page.locator('button').last().click({ clickCount: 5 });
+ await page.locator('button').last().click();
await expect(page.locator(`[role="cell"]`).first()).toHaveText('Name 250');
await expect(page.locator(`[role="cell"]`).last()).toHaveText(
From a9ca6511dd7f8614bb1e414f3c0a9f6c904c5d6b Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Wed, 16 Oct 2024 09:53:15 -0400
Subject: [PATCH 54/55] Fix e2e tests
---
testing/e2e/app/routes/Table/spec.ts | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/testing/e2e/app/routes/Table/spec.ts b/testing/e2e/app/routes/Table/spec.ts
index ce1c65522b9..177281d2fcc 100644
--- a/testing/e2e/app/routes/Table/spec.ts
+++ b/testing/e2e/app/routes/Table/spec.ts
@@ -448,13 +448,17 @@ test.describe('Table Paginator', () => {
test(`should render data in pages`, async ({ page }) => {
await page.goto(`/Table?exampleType=withTablePaginator`);
+ const paginatorButtons = page.locator('#paginator button', {
+ hasText: /[0-9]+/,
+ });
+
await expect(page.locator(`[role="cell"]`).first()).toHaveText('Name 0');
await expect(page.locator(`[role="cell"]`).last()).toHaveText(
'Description 49',
);
// Go to the 6th page
- await page.locator('button').last().click();
+ await paginatorButtons.nth(5).click();
await expect(page.locator(`[role="cell"]`).first()).toHaveText('Name 250');
await expect(page.locator(`[role="cell"]`).last()).toHaveText(
@@ -465,14 +469,15 @@ test.describe('Table Paginator', () => {
test('should render truncated pages list', async ({ page }) => {
await page.goto(`/Table?exampleType=withTablePaginator`);
+ const paginatorButtons = page.locator('#paginator button', {
+ hasText: /[0-9]+/,
+ });
+
await setContainerSize(page, '800px');
// Go to the 5th page
- await page.locator('#paginator button').nth(5).click();
+ await paginatorButtons.nth(4).click();
- const paginatorButtons = page.locator('#paginator button', {
- hasText: /[0-9]+/,
- });
await expect(paginatorButtons).toHaveText([
'1',
'3',
From 6f85103c1b85d94d8ab86477a715c3ee459d9a44 Mon Sep 17 00:00:00 2001
From: Rohan <45748283+r100-stack@users.noreply.github.com>
Date: Thu, 17 Oct 2024 12:21:06 -0400
Subject: [PATCH 55/55] Fix tests
---
packages/itwinui-react/src/core/Table/TablePaginator.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/itwinui-react/src/core/Table/TablePaginator.tsx b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
index 9e830f3ba9c..6f5a26e66ff 100644
--- a/packages/itwinui-react/src/core/Table/TablePaginator.tsx
+++ b/packages/itwinui-react/src/core/Table/TablePaginator.tsx
@@ -430,13 +430,13 @@ const TablePaginatorPageButtons = (props: TablePaginatorPageButtonsProps) => {
{pageList.slice(startPage, endPage)}
{endPage !== totalPagesCount && !isLoading && (
<>
- {ellipsis}
+ {showEndEllipsis ? ellipsis : null}
{pageButton(totalPagesCount - 1, 0)}
>
)}
{isLoading && (
<>
- {showEndEllipsis ? ellipsis : null}
+ {ellipsis}
>
)}