Skip to content

Commit cd072c8

Browse files
committed
Prefix indices in URL when labels are nodes
1 parent 815156e commit cd072c8

File tree

2 files changed

+31
-15
lines changed

2 files changed

+31
-15
lines changed

packages/components/src/components/tabs/index.client.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export const Tabs = ({
6767
className,
6868
tabClassName,
6969
}: TabsProps) => {
70+
const id = useId();
71+
7072
let [selectedIndex, setSelectedIndex] = useState<number>(defaultIndex);
7173
if (_selectedIndex !== undefined) {
7274
selectedIndex = _selectedIndex;
@@ -79,20 +81,22 @@ export const Tabs = ({
7981
items,
8082
searchParamKey,
8183
setSelectedIndex,
84+
id,
8285
);
83-
const id = useId();
86+
8487
useActiveTabFromStorage(
8588
storageKey ?? id,
8689
items,
8790
setSelectedIndex,
8891
tabIndexFromSearchParams !== -1,
92+
id,
8993
);
9094

9195
const handleChange = (index: number) => {
9296
onChange?.(index);
9397

9498
if (storageKey) {
95-
const newValue = getTabKey(items, index);
99+
const newValue = getTabKey(items, index, id);
96100
localStorage.setItem(storageKey, newValue);
97101

98102
// the storage event only get picked up (by the listener) if the localStorage was changed in
@@ -104,7 +108,7 @@ export const Tabs = ({
104108

105109
if (searchParamKey) {
106110
const searchParams = new URLSearchParams(window.location.search);
107-
searchParams.set(searchParamKey, getTabKey(items, index));
111+
searchParams.set(searchParamKey, getTabKey(items, index, id));
108112
window.history.replaceState(
109113
null,
110114
'',
@@ -195,15 +199,18 @@ function useActiveTabFromURL(
195199
items: (TabItem | TabObjectItem)[],
196200
searchParamKey: string,
197201
setSelectedIndex: (index: number) => void,
202+
id: string,
198203
) {
199204
const hash = useHash();
200205
const searchParams = useSearchParams();
201206
const tabsInSearchParams = searchParams.getAll(searchParamKey).sort();
202207

203208
const tabIndexFromSearchParams = items.findIndex((_, index) =>
204-
tabsInSearchParams.includes(getTabKey(items, index)),
209+
tabsInSearchParams.includes(getTabKey(items, index, id)),
205210
);
206211

212+
console.log({ tabIndexFromSearchParams, tabsInSearchParams });
213+
207214
useIsomorphicLayoutEffect(() => {
208215
const tabPanel = hash
209216
? tabPanelsRef.current?.querySelector(`[role=tabpanel]:has([id="${hash}"])`)
@@ -251,6 +258,7 @@ function useActiveTabFromStorage(
251258
items: (TabItem | TabObjectItem)[],
252259
setSelectedIndex: (index: number) => void,
253260
ignoreLocalStorage: boolean,
261+
id: string,
254262
) {
255263
useIsomorphicLayoutEffect(() => {
256264
if (!storageKey || ignoreLocalStorage) {
@@ -259,7 +267,7 @@ function useActiveTabFromStorage(
259267
}
260268

261269
const setSelectedTab = (key: string) => {
262-
const index = items.findIndex((_, i) => getTabKey(items, i) === key);
270+
const index = items.findIndex((_, i) => getTabKey(items, i, id) === key);
263271
if (index !== -1) {
264272
setSelectedIndex(index);
265273
}
@@ -288,7 +296,7 @@ function useActiveTabFromStorage(
288296

289297
type TabKey = string & { __brand: 'TabKey' };
290298

291-
function getTabKey(items: (TabItem | TabObjectItem)[], index: number): TabKey {
299+
function getTabKey(items: (TabItem | TabObjectItem)[], index: number, prefix: string): TabKey {
292300
const item = items[index];
293301
const isObject = isTabObjectItem(item);
294302
// if the key is defined by user, we use it
@@ -298,7 +306,7 @@ function getTabKey(items: (TabItem | TabObjectItem)[], index: number): TabKey {
298306
const label = isObject ? item.label : item;
299307
// otherwise we use the slugified label prefixed by the tab group id, if the label is a string
300308
// or the index of the item in the items array prefixed by the tab group id if the label is a ReactElement
301-
const key = typeof label === 'string' ? slugify(label) : index.toString();
309+
const key = typeof label === 'string' ? slugify(label) : `${prefix}-${index.toString()}`;
302310
return key as TabKey;
303311
}
304312

packages/components/src/components/tabs/tabs.stories.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,6 @@ export const URLSearchParamsSync: Story = {
184184
},
185185
},
186186
},
187-
play: async ({ canvasElement }) => {
188-
// const canvas = within(canvasElement);
189-
// await expect(window.location.search).toContain('framework=vue');
190-
// const angularTab = canvas.getByRole('tab', { name: 'angular' });
191-
// await userEvent.click(angularTab);
192-
// await expect(window.location.search).toContain('framework=angular');
193-
},
194187
};
195188

196189
/**
@@ -354,6 +347,7 @@ export const ControlledMode: Story = {
354347
*/
355348
export const WithReactElementLabels: Story = {
356349
args: {
350+
searchParamKey: 'benefit',
357351
items: [
358352
<span key="1" className="flex items-center gap-2">
359353
🚀 Fast
@@ -373,6 +367,16 @@ export const WithReactElementLabels: Story = {
373367
</>
374368
),
375369
},
370+
parameters: {
371+
nextjs: {
372+
appDirectory: true,
373+
navigation: {
374+
query: {
375+
benefit: ':r0:-2',
376+
},
377+
},
378+
},
379+
},
376380
};
377381

378382
/**
@@ -460,7 +464,11 @@ export const ContentInHiddenPanelOpensByHash: Story = {
460464
items: ['Docker', 'Binary'],
461465
children: (
462466
<>
463-
<Tabs.Tab />
467+
<Tabs.Tab>
468+
<CallToAction href={`${window.location.href}#binary`} variant="tertiary">
469+
Navigate to #binary
470+
</CallToAction>
471+
</Tabs.Tab>
464472
<Tabs.Tab>
465473
<div id="binary">Binary</div>
466474
</Tabs.Tab>

0 commit comments

Comments
 (0)