Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Forward the `ref` to all components ([#1116](https://github.com/tailwindlabs/headlessui/pull/1116))
- Ensure links are triggered inside `Popover Panel` components ([#1153](https://github.com/tailwindlabs/headlessui/pull/1153))
- Improve SSR for `Tab` component ([#1155](https://github.com/tailwindlabs/headlessui/pull/1155))

## [Unreleased - @headlessui/vue]

Expand Down
42 changes: 25 additions & 17 deletions packages/@headlessui-react/src/components/tabs/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { focusIn, Focus } from '../../utils/focus-management'
import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'
import { useSyncRefs } from '../../hooks/use-sync-refs'
import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
import { useLatestValue } from '../../hooks/use-latest-value'

interface StateDefinition {
selectedIndex: number | null
Expand Down Expand Up @@ -103,6 +104,9 @@ let TabsContext = createContext<
>(null)
TabsContext.displayName = 'TabsContext'

let TabsSSRContext = createContext<MutableRefObject<number> | null>(null)
TabsSSRContext.displayName = 'TabsSSRContext'

function useTabsContext(component: string) {
let context = useContext(TabsContext)
if (context === null) {
Expand Down Expand Up @@ -147,14 +151,14 @@ let Tabs = forwardRefWithAs(function Tabs<TTag extends ElementType = typeof DEFA

let tabsRef = useSyncRefs(ref)
let [state, dispatch] = useReducer(stateReducer, {
selectedIndex: null,
selectedIndex: typeof window === 'undefined' ? selectedIndex ?? defaultIndex : null,
tabs: [],
panels: [],
orientation,
activation,
} as StateDefinition)
let slot = useMemo(() => ({ selectedIndex: state.selectedIndex }), [state.selectedIndex])
let onChangeRef = useRef<(index: number) => void>(() => {})
let onChangeRef = useLatestValue(onChange || (() => {}))

useEffect(() => {
dispatch({ type: ActionTypes.SetOrientation, orientation })
Expand All @@ -164,12 +168,6 @@ let Tabs = forwardRefWithAs(function Tabs<TTag extends ElementType = typeof DEFA
dispatch({ type: ActionTypes.SetActivation, activation })
}, [activation])

useEffect(() => {
if (typeof onChange === 'function') {
onChangeRef.current = onChange
}
}, [onChange])

useEffect(() => {
if (state.tabs.length <= 0) return
if (selectedIndex === null && state.selectedIndex !== null) return
Expand Down Expand Up @@ -225,15 +223,19 @@ let Tabs = forwardRefWithAs(function Tabs<TTag extends ElementType = typeof DEFA
[state, dispatch]
)

let SSRCounter = useRef(0)

return (
<TabsContext.Provider value={providerBag}>
{render({
props: { ref: tabsRef, ...passThroughProps },
slot,
defaultTag: DEFAULT_TABS_TAG,
name: 'Tabs',
})}
</TabsContext.Provider>
<TabsSSRContext.Provider value={typeof window === 'undefined' ? SSRCounter : null}>
<TabsContext.Provider value={providerBag}>
{render({
props: { ref: tabsRef, ...passThroughProps },
slot,
defaultTag: DEFAULT_TABS_TAG,
name: 'Tabs',
})}
</TabsContext.Provider>
</TabsSSRContext.Provider>
)
})

Expand Down Expand Up @@ -414,6 +416,11 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
ref: Ref<HTMLElement>
) {
let [{ selectedIndex, tabs, panels }, { dispatch }] = useTabsContext('Tab.Panel')
let SSRContext = useContext(TabsSSRContext)

if (SSRContext !== null && selectedIndex === null) {
selectedIndex = 0 // Should normally not happen, but in case the selectedIndex is null, we can default to 0.
}

let id = `headlessui-tabs-panel-${useId()}`
let internalPanelRef = useRef<HTMLElement>(null)
Expand All @@ -428,7 +435,8 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
}, [dispatch, internalPanelRef])

let myIndex = panels.indexOf(internalPanelRef)
let selected = myIndex === selectedIndex
let selected =
SSRContext === null ? myIndex === selectedIndex : SSRContext.current++ === selectedIndex

let slot = useMemo(() => ({ selected }), [selected])
let propsWeControl = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ export default function Home() {
</Switch>
</Switch.Group>

<Tab.Group className="flex w-full max-w-3xl flex-col" as="div" manual={manual}>
<Tab.Group
className="flex w-full max-w-3xl flex-col"
as="div"
manual={manual}
defaultIndex={2}
>
<Tab.List className="relative z-0 flex divide-x divide-gray-200 rounded-lg shadow">
{tabs.map((tab, tabIdx) => (
<Tab
Expand Down