Improve SSR for Tabs in Vue (#2068)

* improve SSR for Tabs in Vue

* update changelog
This commit is contained in:
Robin Malfait
2022-12-06 13:58:31 +01:00
committed by GitHub
parent 2f0dc8ce0a
commit 5ef5cf9b6f
3 changed files with 104 additions and 4 deletions
+1
View File
@@ -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 () => {