Improve SSR for Tabs in Vue (#2068)
* improve SSR for Tabs in Vue * update changelog
This commit is contained in:
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fix crash when using `multiple` mode without `value` prop (uncontrolled) for `Listbox` and `Combobox` components ([#2058](https://github.com/tailwindlabs/headlessui/pull/2058))
|
||||
- Allow passing in your own `id` prop ([#2060](https://github.com/tailwindlabs/headlessui/pull/2060))
|
||||
- Add `null` as a valid type for Listbox and Combobox in Vue ([#2064](https://github.com/tailwindlabs/headlessui/pull/2064), [#2067](https://github.com/tailwindlabs/headlessui/pull/2067))
|
||||
- Improve SSR for Tabs in Vue ([#2068](https://github.com/tailwindlabs/headlessui/pull/2068))
|
||||
|
||||
## [1.7.4] - 2022-11-03
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { createSSRApp, nextTick, ref } from 'vue'
|
||||
import { renderToString } from 'vue/server-renderer'
|
||||
import { createRenderTemplate, render } from '../../test-utils/vue-testing-library'
|
||||
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from './tabs'
|
||||
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
|
||||
@@ -554,6 +555,60 @@ describe('Rendering', () => {
|
||||
assertTabs({ active: 2 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('SSR', () => {
|
||||
it('should be possible to server side render the first Tab and Panel', async () => {
|
||||
let app = createSSRApp({
|
||||
components: { TabGroup, TabList, Tab, TabPanels, TabPanel },
|
||||
template: html`
|
||||
<TabGroup>
|
||||
<TabList>
|
||||
<Tab>Tab 1</Tab>
|
||||
<Tab>Tab 2</Tab>
|
||||
<Tab>Tab 3</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel>Content 1</TabPanel>
|
||||
<TabPanel>Content 2</TabPanel>
|
||||
<TabPanel>Content 3</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
`,
|
||||
})
|
||||
|
||||
let contents = await renderToString(app)
|
||||
expect(contents).toContain(`Content 1`)
|
||||
expect(contents).not.toContain(`Content 2`)
|
||||
expect(contents).not.toContain(`Content 3`)
|
||||
})
|
||||
|
||||
it('should be possible to server side render the defaultIndex Tab and Panel', async () => {
|
||||
let app = createSSRApp({
|
||||
components: { TabGroup, TabList, Tab, TabPanels, TabPanel },
|
||||
template: html`
|
||||
<TabGroup :defaultIndex="1">
|
||||
<TabList>
|
||||
<Tab>Tab 1</Tab>
|
||||
<Tab>Tab 2</Tab>
|
||||
<Tab>Tab 3</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel>Content 1</TabPanel>
|
||||
<TabPanel>Content 2</TabPanel>
|
||||
<TabPanel>Content 3</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
`,
|
||||
})
|
||||
|
||||
let contents = await renderToString(app)
|
||||
expect(contents).not.toContain(`Content 1`)
|
||||
expect(contents).toContain(`Content 2`)
|
||||
expect(contents).not.toContain(`Content 3`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('`selectedIndex`', () => {
|
||||
|
||||
@@ -58,6 +58,10 @@ function useTabsContext(component: string) {
|
||||
return context
|
||||
}
|
||||
|
||||
let TabsSSRContext = Symbol('TabsSSRContext') as InjectionKey<
|
||||
Ref<{ tabs: string[]; panels: string[] } | null>
|
||||
>
|
||||
|
||||
// ---
|
||||
|
||||
export let TabGroup = defineComponent({
|
||||
@@ -84,7 +88,7 @@ export let TabGroup = defineComponent({
|
||||
)
|
||||
|
||||
let api = {
|
||||
selectedIndex,
|
||||
selectedIndex: computed(() => selectedIndex.value ?? props.defaultIndex ?? null),
|
||||
orientation: computed(() => (props.vertical ? 'vertical' : 'horizontal')),
|
||||
activation: computed(() => (props.manual ? 'manual' : 'auto')),
|
||||
tabs,
|
||||
@@ -116,6 +120,16 @@ export let TabGroup = defineComponent({
|
||||
|
||||
provide(TabsContext, api)
|
||||
|
||||
let SSRCounter = ref({ tabs: [], panels: [] })
|
||||
let mounted = ref(false)
|
||||
onMounted(() => {
|
||||
mounted.value = true
|
||||
})
|
||||
provide(
|
||||
TabsSSRContext,
|
||||
computed(() => (mounted.value ? null : SSRCounter.value))
|
||||
)
|
||||
|
||||
watchEffect(() => {
|
||||
if (api.tabs.value.length <= 0) return
|
||||
if (props.selectedIndex === null && selectedIndex.value !== null) return
|
||||
@@ -231,7 +245,22 @@ export let Tab = defineComponent({
|
||||
onMounted(() => api.registerTab(internalTabRef))
|
||||
onUnmounted(() => api.unregisterTab(internalTabRef))
|
||||
|
||||
let myIndex = computed(() => api.tabs.value.indexOf(internalTabRef))
|
||||
let SSRContext = inject(TabsSSRContext)!
|
||||
let mySSRIndex = computed(() => {
|
||||
if (SSRContext.value) {
|
||||
let mySSRIndex = SSRContext.value.tabs.indexOf(props.id)
|
||||
if (mySSRIndex === -1) return SSRContext.value.tabs.push(props.id) - 1
|
||||
return mySSRIndex
|
||||
}
|
||||
|
||||
return -1
|
||||
})
|
||||
|
||||
let myIndex = computed(() => {
|
||||
let myIndex = api.tabs.value.indexOf(internalTabRef)
|
||||
if (myIndex === -1) return mySSRIndex.value
|
||||
return myIndex
|
||||
})
|
||||
let selected = computed(() => myIndex.value === api.selectedIndex.value)
|
||||
|
||||
function activateUsing(cb: () => FocusResult) {
|
||||
@@ -391,7 +420,22 @@ export let TabPanel = defineComponent({
|
||||
onMounted(() => api.registerPanel(internalPanelRef))
|
||||
onUnmounted(() => api.unregisterPanel(internalPanelRef))
|
||||
|
||||
let myIndex = computed(() => api.panels.value.indexOf(internalPanelRef))
|
||||
let SSRContext = inject(TabsSSRContext)!
|
||||
let mySSRIndex = computed(() => {
|
||||
if (SSRContext.value) {
|
||||
let mySSRIndex = SSRContext.value.panels.indexOf(props.id)
|
||||
if (mySSRIndex === -1) return SSRContext.value.panels.push(props.id) - 1
|
||||
return mySSRIndex
|
||||
}
|
||||
|
||||
return -1
|
||||
})
|
||||
|
||||
let myIndex = computed(() => {
|
||||
let myIndex = api.panels.value.indexOf(internalPanelRef)
|
||||
if (myIndex === -1) return mySSRIndex.value
|
||||
return myIndex
|
||||
})
|
||||
let selected = computed(() => myIndex.value === api.selectedIndex.value)
|
||||
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user