Skip to content

Commit

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

Add forceMount option to retain inactive tab content in the DOM

#2283
  • Loading branch information
acd02 committed Jul 3, 2024
1 parent 8256e4d commit decf930
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} />

### forceMount

Use `forceMount` on `Tabs` 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.ForceMount} />

## 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 ForceMount: StoryFn = _args => (
<div>
<StoryLabel>forceMount</StoryLabel>
{createTabs({
rootProps: { defaultValue: 'tab1', forceMount: 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 forceMount prop is true', async () => {
render(
createTabs({
tabs,
rootProps: { defaultValue: 'tab1', forceMount: 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
*/
forceMount?: 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,
forceMount = false,
orientation = 'horizontal',
children,
className,
Expand All @@ -43,6 +49,7 @@ export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
intent,
size,
orientation,
forceMount,
}}
>
<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: {
forceMount: {
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 { forceMount } = useTabsContext()

return (
<RadixTabs.Content
ref={ref}
className={contentStyles({ className })}
forceMount={forceMount || rest.forceMount}
className={contentStyles({ className, forceMount })}
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'> & { forceMount?: boolean }

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

Expand Down

0 comments on commit decf930

Please sign in to comment.