Skip to content

Commit 7e9f45a

Browse files
committed
Tweak
1 parent c237714 commit 7e9f45a

File tree

6 files changed

+93
-47
lines changed

6 files changed

+93
-47
lines changed

packages/lab/src/__tests__/__e2e__/tabs-next/TabsNext.cy.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,20 @@ describe("Given a Tabstrip", () => {
179179
cy.findAllByRole("tab").should("have.length", 5);
180180

181181
cy.findByRole("tab", { name: "13 tabs hidden" }).realClick();
182-
cy.findByRole("tab", { name: "Liquidity" }).realClick();
182+
cy.findByRole("tab", { name: "Liquidity" }).should("be.focused");
183183

184+
cy.findByRole("tab", { name: "Liquidity" }).realClick();
184185
cy.findByRole("tab", { name: "Liquidity" })
185186
.should("have.attr", "aria-selected", "true")
186187
.should("be.focused");
187188

188189
cy.findAllByRole("tab").should("have.length", 5);
189190

191+
cy.wait(100);
192+
190193
cy.findByRole("tab", { name: "13 tabs hidden" }).realClick();
194+
cy.findByRole("tab", { name: "Checks" }).should("be.focused");
195+
191196
cy.realPress("Enter");
192197
cy.findByRole("tab", { name: "Checks" })
193198
.should("have.attr", "aria-selected", "true")
@@ -419,16 +424,21 @@ describe("Given a Tabstrip", () => {
419424
"true",
420425
);
421426

422-
cy.get("[data-overflowbutton]").realClick();
427+
cy.findByRole("tab", { name: "15 tabs hidden" }).realClick();
428+
cy.findByRole("tab", { name: "Loans" }).should("be.focused");
429+
423430
cy.findByRole("tab", { name: "Lots" }).realClick();
431+
cy.findByRole("tab", { name: "Lots" }).should("be.focused");
424432
cy.findByRole("tab", { name: "Lots" }).should(
425433
"have.attr",
426434
"aria-selected",
427435
"true",
428436
);
429437

438+
cy.wait(100);
439+
430440
cy.findByRole("button", { name: "Lots Close tab" }).realClick();
431-
cy.findByRole("tab", { name: "More" })
441+
cy.findByRole("tab", { name: "Transactions" })
432442
.should("have.attr", "aria-selected", "true")
433443
.and("be.focused");
434444
});
@@ -442,12 +452,11 @@ describe("Given a Tabstrip", () => {
442452
cy.mount(<Overflow />);
443453
cy.findAllByRole("tab").should("have.length", 5);
444454

445-
cy.get("[data-overflowbutton]").realClick();
455+
cy.findByRole("tab", { name: "13 tabs hidden" }).realClick();
446456
cy.wait(500);
447457

448458
// no horizontal overflow, menu should flip in horizontally
449459
cy.get("html").then((body) => {
450-
console.log(body[0]);
451460
const { clientWidth, scrollWidth } = body[0];
452461
expect(clientWidth).to.equal(scrollWidth);
453462
});

packages/lab/src/tabs-next/TabListNext.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const TabListNext = forwardRef<HTMLDivElement, TabListNextProps>(
6565
activeTab,
6666
menuOpen,
6767
setMenuOpen,
68+
sortItems,
6869
} = useTabsNext();
6970

7071
const tabstripRef = useRef<HTMLDivElement>(null);
@@ -129,22 +130,32 @@ export const TabListNext = forwardRef<HTMLDivElement, TabListNextProps>(
129130
if (!currentTab?.stale) return;
130131
const nextIndex = currentTab.staleIndex ?? -1;
131132

132-
queueMicrotask(() => {
133-
const nextTab = itemAt(nextIndex) ?? getLast();
133+
setTimeout(() => {
134+
let nextTab = itemAt(nextIndex) ?? getLast();
135+
136+
if (nextTab?.element === overflowButtonRef.current) {
137+
nextTab = itemAt(nextIndex - 1);
138+
}
134139

135140
if (nextTab) {
136-
if (currentTabIsSelected) {
141+
if (
142+
currentTabIsSelected &&
143+
!tabstripRef.current?.querySelector(
144+
'[role="tab"][aria-selected="true"]',
145+
)
146+
) {
137147
nextTab.element?.click();
138148
nextTab.element?.focus();
139149
} else {
140150
nextTab.element?.focus();
141151
}
142152
}
143-
});
153+
}, 166);
144154
});
145155

146156
useEffect(() => {
147157
const handleFocus = () => {
158+
if (!tabstripRef.current) return;
148159
handleTabRemoval();
149160
};
150161

@@ -159,6 +170,20 @@ export const TabListNext = forwardRef<HTMLDivElement, TabListNextProps>(
159170
};
160171
}, [targetWindow, handleTabRemoval]);
161172

173+
useEffect(() => {
174+
const observer = new MutationObserver(() => {
175+
sortItems();
176+
});
177+
178+
if (!tabstripRef.current) return;
179+
180+
observer.observe(tabstripRef.current, { childList: true });
181+
182+
return () => {
183+
observer.disconnect();
184+
};
185+
}, [sortItems]);
186+
162187
const warningId = useId();
163188

164189
return (

packages/lab/src/tabs-next/TabsNext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export const TabsNext = forwardRef<HTMLDivElement, TabsNextProps>(
5353
getLast,
5454
itemAt,
5555
getIndex,
56+
sortItems,
5657
} = useCollection({ wrap: true });
5758

5859
const activeTab = useRef<Pick<Item, "id" | "value">>();
@@ -136,6 +137,7 @@ export const TabsNext = forwardRef<HTMLDivElement, TabsNextProps>(
136137
setMenuOpen,
137138
itemAt,
138139
getIndex,
140+
sortItems,
139141
}),
140142
[
141143
registerPanel,
@@ -152,6 +154,7 @@ export const TabsNext = forwardRef<HTMLDivElement, TabsNextProps>(
152154
menuOpen,
153155
itemAt,
154156
getIndex,
157+
sortItems,
155158
],
156159
);
157160

packages/lab/src/tabs-next/TabsNextContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const TabsNextContext = createContext<TabsNextContextValue>(
3737
item: () => null,
3838
getIndex: () => -1,
3939
itemAt: () => null,
40+
sortItems: () => undefined,
4041
selected: undefined,
4142
registerTab: () => () => undefined,
4243
registerPanel: () => () => undefined,

packages/lab/src/tabs-next/hooks/useCollection.ts

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,7 @@ export function useCollection({ wrap }: UseCollectionProps) {
6262
itemMap.current.delete(id);
6363
}
6464
}
65-
66-
const newItems = Array.from(itemMap.current.values());
67-
itemsRef.current = sortBasedOnDOMPosition(newItems);
68-
batchTimeout.current = null;
69-
}, 166);
65+
}, 44);
7066

7167
return () => {
7268
itemMap.current.set(item.id, {
@@ -80,7 +76,7 @@ export function useCollection({ wrap }: UseCollectionProps) {
8076

8177
return {
8278
registerItem,
83-
item: (id?: string | null): Item | null => {
79+
item: useCallback((id?: string | null): Item | null => {
8480
if (!id) return null;
8581
let item = itemMap.current.get(id);
8682
if (!item) {
@@ -90,36 +86,48 @@ export function useCollection({ wrap }: UseCollectionProps) {
9086
}
9187
}
9288
return item ?? null;
93-
},
94-
getNext: (current: string): Item | null => {
95-
const index = itemsRef.current.findIndex(({ id }) => id === current);
96-
97-
const newIndex = wrap
98-
? (index + 1) % itemsRef.current.length
99-
: Math.min(index + 1, itemsRef.current.length - 1);
100-
101-
return itemsRef.current[newIndex] ?? null;
102-
},
103-
getPrevious: (current: string): Item | null => {
104-
const index = itemsRef.current.findIndex(({ id }) => id === current);
105-
106-
const newIndex = wrap
107-
? (index - 1 + itemsRef.current.length) % itemsRef.current.length
108-
: Math.max(index - 1, 0);
109-
110-
return itemsRef.current[newIndex] ?? null;
111-
},
112-
getFirst: (): Item | null => {
89+
}, []),
90+
getNext: useCallback(
91+
(current: string): Item | null => {
92+
const index = itemsRef.current.findIndex(({ id }) => id === current);
93+
94+
const newIndex = wrap
95+
? (index + 1) % itemsRef.current.length
96+
: Math.min(index + 1, itemsRef.current.length - 1);
97+
98+
return itemsRef.current[newIndex] ?? null;
99+
},
100+
[wrap],
101+
),
102+
getPrevious: useCallback(
103+
(current: string): Item | null => {
104+
const index = itemsRef.current.findIndex(({ id }) => id === current);
105+
106+
const newIndex = wrap
107+
? (index - 1 + itemsRef.current.length) % itemsRef.current.length
108+
: Math.max(index - 1, 0);
109+
110+
return itemsRef.current[newIndex] ?? null;
111+
},
112+
[wrap],
113+
),
114+
getFirst: useCallback((): Item | null => {
113115
return itemsRef.current[0] ?? null;
114-
},
115-
getLast: (): Item | null => {
116+
}, []),
117+
getLast: useCallback((): Item | null => {
116118
return itemsRef.current[itemsRef.current.length - 1] ?? null;
117-
},
118-
getIndex: (current: string): number => {
119+
}, []),
120+
getIndex: useCallback((current: string): number => {
119121
return itemsRef.current.findIndex(({ id }) => id === current);
120-
},
121-
itemAt: (index: number): Item | null => {
122+
}, []),
123+
itemAt: useCallback((index: number): Item | null => {
122124
return itemsRef.current[index] ?? null;
123-
},
125+
}, []),
126+
sortItems: useCallback(() => {
127+
const newItems = Array.from(
128+
itemMap.current.values().filter((item) => !item.stale),
129+
);
130+
itemsRef.current = sortBasedOnDOMPosition(newItems);
131+
}, []),
124132
};
125133
}

packages/lab/src/tabs-next/hooks/useOverflow.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type RefObject,
1313
useEffect,
1414
useMemo,
15+
useState,
1516
} from "react";
1617
import type { TabNextProps } from "../TabNext";
1718

@@ -27,8 +28,6 @@ function getTabWidth(element: HTMLElement) {
2728
return Math.ceil(width);
2829
}
2930

30-
let pinned: string | undefined;
31-
3231
export function useOverflow({
3332
container,
3433
overflowButton,
@@ -42,6 +41,7 @@ export function useOverflow({
4241
visibleCount: Number.POSITIVE_INFINITY,
4342
isMeasuring: false,
4443
});
44+
const [pinned, setPinned] = useState(selected);
4545

4646
const pinnedValue = pinned ?? selected;
4747

@@ -214,13 +214,13 @@ export function useOverflow({
214214
[childArray, visibleCount],
215215
);
216216

217-
const hiddenSelectedIndex = hidden.findIndex(
217+
const hiddenSelected = hidden.find(
218218
(child) =>
219219
isValidElement<TabNextProps>(child) && child?.props?.value === selected,
220220
);
221221

222-
if (hiddenSelectedIndex !== -1) {
223-
pinned = selected;
222+
if (hiddenSelected) {
223+
setPinned(selected);
224224
}
225225

226226
const hiddenPinnedIndex = hidden.findIndex(

0 commit comments

Comments
 (0)