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
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ jobs:

- name: Tests
run: pnpm run test

- name: Docs
run: pnpm run docs:build

- name: Generated API reference is up to date
run: git diff --exit-code packages/docs/api
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ In this monorepository:
| Package | Description |
|---------|-------------|
|[@vue/apollo-composable](./packages/vue-apollo-composable) |Composition API|
|[@vue/apollo-components](./packages/vue-apollo-components) |Components API, built on the composables|

## Special Sponsor

Expand Down
2 changes: 2 additions & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export default antfu(
'ts/no-namespace': 'off',
'ts/no-empty-object-type': 'off',
'import/first': 'off',
'vue/attribute-hyphenation': ['error', 'never'],
'vue/v-on-event-hyphenation': ['error', 'never'],
},
},
{
Expand Down
14 changes: 14 additions & 0 deletions packages/docs/.vitepress/apiFlavors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const API_FLAVORS = [
{ value: 'composition', label: 'Composition API' },
{ value: 'components', label: 'Components API' },
] as const

export type ApiFlavor = typeof API_FLAVORS[number]['value']

export const DEFAULT_FLAVOR: ApiFlavor = 'composition'

export const STORAGE_KEY = 'vue-apollo:api-flavor'

export function isApiFlavor(value: unknown): value is ApiFlavor {
return API_FLAVORS.some(flavor => flavor.value === value)
}
102 changes: 99 additions & 3 deletions packages/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
import { transformerTwoslash } from '@shikijs/vitepress-twoslash'
import container from 'markdown-it-container'
import { defineConfig } from 'vitepress'
import typedocSidebar from '../api/composable/typedoc-sidebar.json'
import { API_FLAVORS, DEFAULT_FLAVOR, STORAGE_KEY } from './apiFlavors.ts'

/**
* Applies the stored flavor before first paint.
*
* Runs from `head`, so it beats hydration: without it every page would render the default
* flavor and then visibly swap for anyone who picked another one.
*/
const flavorScript = `
try {
var f = localStorage.getItem(${JSON.stringify(STORAGE_KEY)}) || ${JSON.stringify(DEFAULT_FLAVOR)}
if (${JSON.stringify(API_FLAVORS.map(flavor => flavor.value))}.indexOf(f) !== -1) {
document.documentElement.classList.add('api-pref-' + f)
}
} catch (e) {}
`.trim()

/** Generated so `apiFlavors.ts` stays the only place a flavor is declared. */
const flavorStyle = [
`html:not([class*='api-pref-']) .api-flavor--${DEFAULT_FLAVOR} { display: block }`,
...API_FLAVORS.flatMap(({ value }, index) => [
`html.api-pref-${value} { --api-flavor-index: ${index} }`,
`html.api-pref-${value} .api-flavor--${value} { display: block }`,
`html.api-pref-${value} .api-preference__option[data-flavor='${value}'] { color: var(--vp-c-brand-1) }`,
]),
].join('\n')

// Shared sidebar for guide sections
const guideSidebar = [
Expand Down Expand Up @@ -79,6 +106,7 @@ const guideSidebar = [
{ text: 'What\'s changed in v5', link: '/migration/whats-changed' },
{ text: 'Migration guide', link: '/migration/guide' },
{ text: 'Compat layer', link: '/migration/compat' },
{ text: 'Components', link: '/migration/components' },
],
},
]
Expand All @@ -89,10 +117,48 @@ export default defineConfig({
description: 'Apollo/GraphQL integration for VueJS',
markdown: {
codeTransformers: [
transformerTwoslash() as any,
transformerTwoslash({
twoslashOptions: {
/*
* Drops one diagnostic from generated code the reader never sees.
*
* A template whose only root is one of our generic SFCs makes Vue language tools
* read `$el` off that component's instance type, which under twoslash's setup
* does not carry `ComponentPublicInstance`. `vue-tsc` checks the same examples
* cleanly, so the example itself is fine.
*
* `filterNode` runs before error validation, so the node is gone rather than
* merely expected. Scoped to this message so real 2339s still fail the build.
*/
filterNode(node) {
return !(node.type === 'error' && node.code === 2339 && node.text.includes('\'$el\''))
},
},
}) as any,
],
config(md) {
/*
* One container per flavor, `:::: composition-api` to `::::`, shown or hidden by CSS.
*
* Written with four colons rather than three so a flavor block can wrap the
* three-colon containers (`code-group`, `tip`, `warning`) it usually needs to.
* markdown-it-container only nests when the outer marker is the longer one.
*/
for (const { value } of API_FLAVORS) {
md.use(container, `${value}-api`, {
render: (tokens: { nesting: number }[], index: number) =>
tokens[index].nesting === 1
? `<div class="api-flavor api-flavor--${value}">\n`
: '</div>\n',
})
}
},
},
head: [['link', { rel: 'icon', href: '/favicon.png' }]],
head: [
['link', { rel: 'icon', href: '/favicon.png' }],
['script', {}, flavorScript],
['style', {}, flavorStyle],
],
themeConfig: {
socialLinks: [{ icon: 'github', link: 'https://github.com/vuejs/apollo' }],
footer: {
Expand All @@ -106,7 +172,14 @@ export default defineConfig({
nav: [
{ text: 'Home', link: '/' },
{ text: 'Guide', link: '/guide/' },
{ text: 'API Reference', link: '/api/composable/' },
{
text: 'API Reference',
items: [
{ text: 'Overview', link: '/api/' },
{ text: '@vue/apollo-composable', link: '/api/composable/' },
{ text: '@vue/apollo-components', link: '/api/components/' },
],
},
{
text: 'Sponsor',
link: 'https://github.com/sponsors/Akryum',
Expand All @@ -123,13 +196,36 @@ export default defineConfig({
'/networking/': guideSidebar,
'/ssr/': guideSidebar,
'/migration/': guideSidebar,
'/api/': [
{
text: 'API Reference',
link: '/api/',
items: [
{ text: '@vue/apollo-composable', link: '/api/composable/' },
{ text: '@vue/apollo-components', link: '/api/components/' },
],
},
],
'/api/composable/': [
{
text: '@vue/apollo-composable',
link: '/api/composable/',
items: typedocSidebar,
},
],
'/api/components/': [
{
text: '@vue/apollo-components',
link: '/api/components/',
items: [
{ text: 'ApolloQuery', link: '/api/components/ApolloQuery' },
{ text: 'ApolloMutation', link: '/api/components/ApolloMutation' },
{ text: 'ApolloSubscription', link: '/api/components/ApolloSubscription' },
{ text: 'ApolloSubscribeToMore', link: '/api/components/ApolloSubscribeToMore' },
{ text: 'ApolloFragment', link: '/api/components/ApolloFragment' },
],
},
],
},
search: {
provider: 'local',
Expand Down
43 changes: 43 additions & 0 deletions packages/docs/.vitepress/theme/components/ApiPreference.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { useRoute } from 'vitepress'
import { computed } from 'vue'
import { API_FLAVORS } from '../../apiFlavors.ts'
import { useApiFlavor } from '../composables/useApiFlavor.ts'

const route = useRoute()
const flavor = useApiFlavor()

const apiFlavorCount = API_FLAVORS.length

const hasFlavors = computed(() => !/^\/api\/(?:composable|components)\//.test(route.path))
</script>

<template>
<div v-if="hasFlavors" class="api-preference">
<div
class="api-preference__group"
role="radiogroup"
aria-label="API style the documentation is written in"
>
<span class="api-preference__thumb" aria-hidden="true" />
<button
v-for="option in API_FLAVORS"
:key="option.value"
type="button"
role="radio"
class="api-preference__option"
:data-flavor="option.value"
:aria-checked="flavor === option.value"
@click="flavor = option.value"
>
{{ option.label }}
</button>
</div>
</div>
</template>

<style scoped>
.api-preference {
--api-flavor-count: v-bind(apiFlavorCount);
}
</style>
28 changes: 28 additions & 0 deletions packages/docs/.vitepress/theme/composables/useApiFlavor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ApiFlavor } from '../../apiFlavors.ts'
import { createSharedComposable, useLocalStorage } from '@vueuse/core'
import { watch } from 'vue'
import { API_FLAVORS, DEFAULT_FLAVOR, isApiFlavor, STORAGE_KEY } from '../../apiFlavors.ts'

/** Nothing rendered depends on this. It drives `aria-checked` and the click handler. */
function useApiFlavorState() {
const flavor = useLocalStorage<ApiFlavor>(STORAGE_KEY, DEFAULT_FLAVOR, {
// Read after hydration, so the first client render matches the server's default.
initOnMounted: true,
listenToStorageChanges: false,
serializer: {
read: raw => (isApiFlavor(raw) ? raw : DEFAULT_FLAVOR),
write: value => value,
},
})

// Deliberately not `immediate`: the `head` script already applied the stored flavor
watch(flavor, (value) => {
for (const { value: candidate } of API_FLAVORS) {
document.documentElement.classList.toggle(`api-pref-${candidate}`, candidate === value)
}
})

return flavor
}

export const useApiFlavor = createSharedComposable(useApiFlavorState)
5 changes: 5 additions & 0 deletions packages/docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import type { EnhanceAppContext } from 'vitepress'
import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client'
import Theme from 'vitepress/theme'
import { h } from 'vue'
import ApiPreference from './components/ApiPreference.vue'
import SponsorButton from './components/SponsorButton.vue'

import '@shikijs/vitepress-twoslash/style.css'
import './styles/index.css'

export default {
extends: Theme,
Layout: () => h(Theme.Layout, null, {
'sidebar-nav-before': () => h(ApiPreference),
}),
enhanceApp({ app }: EnhanceAppContext) {
app.use(TwoslashFloatingVue)
app.component('SponsorButton', SponsorButton)
Expand Down
70 changes: 70 additions & 0 deletions packages/docs/.vitepress/theme/styles/api-flavor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Which flavor is visible is decided by the `api-pref-*` class a `head` script puts on
* <html> before first paint. Those per-flavor rules are generated from `apiFlavors.ts` in
* `config.ts`, so adding a flavor needs no change here.
*/

.api-flavor {
display: none;

& > :first-child {
margin-top: 0;
}
}

.api-preference {
margin-bottom: 16px;
padding-top: 16px;
padding-bottom: 16px;
border-bottom: 1px solid var(--vp-c-divider);
}

.api-preference__group {
position: relative;
display: grid;
padding: 3px;
border-radius: 8px;
background-color: var(--vp-c-default-soft);

& .api-preference__thumb {
position: absolute;
top: 3px;
right: 3px;
left: 3px;
height: calc((100% - 6px) / var(--api-flavor-count, 2));
border-radius: 6px;
background-color: var(--vp-c-bg);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.09);
transform: translateY(calc(100% * var(--api-flavor-index, 0)));
transition: transform 0.25s ease;

@media (prefers-reduced-motion: reduce) {
transition: none;
}
}

& .api-preference__option {
position: relative;
padding: 5px 10px;
border: 0;
border-radius: 6px;
background-color: transparent;
font-size: 12px;
font-weight: 600;
line-height: 20px;
text-align: left;
white-space: nowrap;
color: var(--vp-c-text-2);
cursor: pointer;
transition: color 0.25s;

&:hover {
color: var(--vp-c-text-1);
}

&:focus-visible {
outline: 2px solid var(--vp-c-brand-1);
outline-offset: 2px;
}
}
}
2 changes: 2 additions & 0 deletions packages/docs/.vitepress/theme/styles/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import './api-flavor.css';

:root {
--vp-c-brand-1: #5591d8;
--vp-c-brand-2: #336cb0;
Expand Down
Loading
Loading