Skip to content

Commit

Permalink
feat(tabs): add keepMountedOnInactive prop to retain inactive tab con…
Browse files Browse the repository at this point in the history
…tent in the DOM

Add keepMountedOnInactive option to retain inactive tab content in the DOM

#2283
  • Loading branch information
acd02 committed Jul 2, 2024
1 parent ba78da3 commit 198c4fd
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 6 deletions.
8 changes: 8 additions & 0 deletions packages/components/tabs/src/Tabs.doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ Use `size` prop to set the size of items.

<Canvas of={stories.Size} />

### keepMountedOnInactive

Use `keepMountedOnInactive` when you want the content of your inactive tabs to remain in the DOM instead of being removed.

This can be useful if your tabs load heavy resources on mount, such as heavy images, videos, iframes, etc...

<Canvas of={stories.KeepMountedOnInactive} />

## Accessibility

Adheres to the [Tabs WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs).
Expand Down
10 changes: 10 additions & 0 deletions packages/components/tabs/src/Tabs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,16 @@ export const Size: StoryFn = _args => (
</div>
)

export const KeepMountedOnInactive: StoryFn = _args => (
<div>
<StoryLabel>keepMountedOnInactive</StoryLabel>
{createTabs({
rootProps: { defaultValue: 'tab1', keepMountedOnInactive: true },
tabs: defaultTabs,
})}
</div>
)

export const State: StoryFn = _args => (
<div className="flex flex-row gap-lg">
<div className="shrink basis-auto overflow-auto">
Expand Down
13 changes: 13 additions & 0 deletions packages/components/tabs/src/Tabs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,19 @@ describe('Tabs', () => {
behavior: 'smooth',
})
})

it('should keep inactive tabs in the DOM (but hidden) when keepMountedOnInactive prop is true', async () => {
render(
createTabs({
tabs,
rootProps: { defaultValue: 'tab1', keepMountedOnInactive: true },
})
)

expect(screen.getByText(/Make things happen!/)).toBeInTheDocument()

expect(screen.getAllByRole('tabpanel').at(-1)).toHaveClass('data-[state=inactive]:hidden')
})
})
})
})
7 changes: 7 additions & 0 deletions packages/components/tabs/src/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export interface TabsProps
* @default false
*/
asChild?: boolean
/**
* Whether to keep inactive tabs content in the DOM.
* @default false
*/
keepMountedOnInactive?: boolean
}

/**
Expand All @@ -30,6 +35,7 @@ export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
* see https://www.radix-ui.com/docs/primitives/components/tabs#root
*/
asChild = false,
keepMountedOnInactive = false,
orientation = 'horizontal',
children,
className,
Expand All @@ -43,6 +49,7 @@ export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
intent,
size,
orientation,
keepMountedOnInactive,
}}
>
<RadixTabs.Root
Expand Down
15 changes: 11 additions & 4 deletions packages/components/tabs/src/TabsContent.styles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { cva } from 'class-variance-authority'

export const contentStyles = cva([
'w-full p-lg',
'focus-visible:outline-none focus-visible:u-ring-inset',
])
export const contentStyles = cva(
['w-full p-lg', 'focus-visible:outline-none focus-visible:u-ring-inset'],
{
variants: {
keepMountedOnInactive: {
true: 'data-[state=inactive]:hidden',
false: '',
},
},
}
)
6 changes: 5 additions & 1 deletion packages/components/tabs/src/TabsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as RadixTabs from '@radix-ui/react-tabs'
import { forwardRef, type PropsWithChildren } from 'react'

import { contentStyles } from './TabsContent.styles'
import { useTabsContext } from './TabsContext'

export interface TabsContentProps
extends PropsWithChildren<Omit<RadixTabs.TabsContentProps, 'forceMount'>> {
Expand Down Expand Up @@ -34,10 +35,13 @@ export const TabsContent = forwardRef<HTMLDivElement, TabsContentProps>(
},
ref
) => {
const { keepMountedOnInactive } = useTabsContext()

return (
<RadixTabs.Content
ref={ref}
className={contentStyles({ className })}
forceMount={keepMountedOnInactive || undefined}
className={contentStyles({ className, keepMountedOnInactive })}
asChild={asChild}
{...rest}
>
Expand Down
3 changes: 2 additions & 1 deletion packages/components/tabs/src/TabsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { createContext, useContext } from 'react'

import type { TabsTriggerVariantsProps } from './TabsTrigger.styles'

export type TabsContextInterface = TabsTriggerVariantsProps & Pick<TabsProps, 'orientation'>
export type TabsContextInterface = TabsTriggerVariantsProps &
Pick<TabsProps, 'orientation'> & { keepMountedOnInactive?: boolean }

export const TabsContext = createContext<TabsContextInterface>({} as TabsContextInterface)

Expand Down

0 comments on commit 198c4fd

Please sign in to comment.