Skip to content

Commit d1edeba

Browse files
authored
feat!: use @portabletext/vue in <SanityContent> (#1251)
1 parent a4beca2 commit d1edeba

File tree

12 files changed

+459
-399
lines changed

12 files changed

+459
-399
lines changed
Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,85 @@
11
## Global helper
22

3-
This module defines a global `<SanityContent>` component that can turn portable text into HTML. It is a lightweight functional component without an instance.
3+
This module defines a global `<SanityContent>` component that can turn [portable text](https://www.sanity.io/guides/beginners-guide-to-portable-text) into HTML. It is a lightweight functional component without an instance.
44

5-
::alert{icon=💡}
6-
If you want to use the same serializers in multiple places, consider creating your own component (e.g. `<MySanityContent>` which wraps SanityContent, but with your default serializers. By creating `~/components/MySanityContent.vue` you should be able to use this everywhere in your app without importing it.
5+
As of v2, `<SanityContent>` uses [`@portabletext/vue`](https://github.com/portabletext/vue-portabletext) for rendering portable text. This means features and properties available to `@portabletext/vue` also work with `<SanityContent>`. Please refer to their [Usage guide](https://github.com/portabletext/vue-portabletext?tab=readme-ov-file#basic-usage) for advanced configuration options.
6+
7+
::warning
8+
This render change introduces **breaking changes** for `<SanityContent>` v2 components. Refer to the following upgrade guide:
9+
* To reflect `@portabletext/vue`'s props, `blocks``value` and `serializers``components` attribute name changes have been made. The property types remain the same.
10+
* Custom components now receive their data nested within a `props.value` object. When defining components, you need to extract your props from this structure using object spreading: `{...props.value}`. This applies to all component types (blocks, marks, styles).
711
::
812

913
### Example
1014

1115
```vue
1216
<template>
13-
<SanityContent :blocks="content" />
17+
<SanityContent :value="content" />
1418
</template>
1519
```
1620

17-
### Example with custom serializers
21+
### Example with custom components
1822

1923
```vue
2024
<template>
21-
<SanityContent :blocks="content" :serializers="serializers" />
25+
<SanityContent :value="content" :components="components" />
2226
</template>
2327
2428
<script setup>
29+
import { defineAsyncComponent, h, resolveComponent } from 'vue'
2530
import CustomBlockComponent from '~/components/CustomBlockComponent.vue'
26-
import { resolveComponent } from 'vue'
2731
28-
const serializers = {
32+
const components = {
2933
types: {
3034
// This is how to access a component registered by `@nuxt/components`
31-
lazyRegisteredComponent: resolveComponent('LazyCustomSerializer'),
35+
lazyRegisteredComponent: props => h(resolveComponent('LazyCustomSerializer'), {
36+
...props.value,
37+
}),
3238
// A directly imported component
33-
importedComponent: CustomBlockComponent,
39+
importedComponent: props => h(CustomBlockComponent, {
40+
...props.value,
41+
}),
3442
// Example of a more complex async component
35-
dynamicComponent: defineAsyncComponent({
43+
dynamicComponent: props => h(defineAsyncComponent({
3644
loadingComponent: () => 'Loading...',
3745
loader: () => import('~/other/component.vue'),
46+
}), {
47+
...props.value,
3848
}),
3949
},
4050
marks: {
41-
// You can also just pass a string for a custom serializer if it's an HTML element
42-
internalLink: 'a'
51+
// Custom marks handling
52+
internalLink: props => h('a', { href: props.value.href }, props.text)
4353
}
4454
}
4555
</script>
4656
```
4757

58+
::warning
59+
If you want to use the same components in multiple places, consider creating your own component (e.g. `<MySanityContent>`) which wraps SanityContent with your default components. By creating `~/components/MySanityContent.vue` you should be able to use this everywhere in your app without importing it.
60+
::
61+
62+
### Advanced Props
63+
64+
The `SanityContent` component accepts all props from `@portabletext/vue`:
65+
66+
```vue
67+
<template>
68+
<SanityContent
69+
:value="content"
70+
:components="components"
71+
:onMissingComponent="handleMissingComponent"
72+
:listNestingMode="'html'"
73+
/>
74+
</template>
75+
76+
<script setup>
77+
const handleMissingComponent = (message, options) => {
78+
console.warn(`Missing component: ${options.type} (${options.nodeType})`)
79+
}
80+
</script>
81+
```
82+
4883
## Other resources
4984

50-
- [sanity-blocks-vue-component](https://github.com/rdunk/sanity-blocks-vue-component){ .text-primary-500 }
85+
- [@portabletext/vue](https://github.com/portabletext/vue-portabletext){ .text-primary-500 }

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"dependencies": {
5151
"@nuxt/kit": "^3.17.7",
5252
"@portabletext/types": "^2.0.13",
53+
"@portabletext/vue": "^1.0.12",
5354
"@sanity/client": "^7.6.0",
5455
"@sanity/comlink": "^3.0.5",
5556
"@sanity/core-loader": "^1.8.10",

playground/pages/movie/[slug].vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
asset-id="image-e22a88d23751a84df81f03ef287ae85fc992fe12-780x1170-jpg"
2828
/>
2929
<div :data-sanity="encodeDataAttribute?.(['overview'])">
30-
<SanityContent :blocks="details.overview" />
30+
<SanityContent :value="details.overview" />
3131
</div>
3232
</template>
3333
<template v-else>

playground/pages/text.vue

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,54 @@
11
<template>
22
<div>
3+
<h1 class="text-2xl font-bold">
4+
Sanity Content Example
5+
</h1>
6+
<p>This page demonstrates the use of custom serializers with Sanity content blocks.</p>
7+
<h2 class="text-xl font-semibold">
8+
SanityContent (Vue-PortableText)
9+
</h2>
10+
<p>This example uses the <a href="https://github.com/portabletext/vue-portabletext">vue-portabletext</a> library for rendering content.</p>
311
<SanityContent
4-
:blocks="blocks"
5-
:serializers="serializers"
12+
:value="value"
13+
:components="components"
614
/>
715
</div>
816
</template>
917

1018
<script setup>
1119
import { defineComponent, defineAsyncComponent, resolveComponent, h } from 'vue'
1220
13-
const serializers = {
21+
const components = {
1422
types: {
1523
// This registered by `@nuxt/components`
16-
lazyRegisteredComponent: resolveComponent('LazyCustomSerializer'),
24+
lazyRegisteredComponent: props => h(resolveComponent('LazyCustomSerializer'), {
25+
...props.value,
26+
}),
1727
// An example of an inline component/directly imported component
18-
importedComponent: defineComponent({
28+
importedComponent: props => h(defineComponent({
1929
props: { someProp: String },
20-
render: props => h('p', 'An inline/imported custom component: ' + props.someProp),
30+
render(componentProps) {
31+
return h('p', 'An inline/imported custom component: ' + componentProps.someProp)
32+
},
33+
}), {
34+
...props.value,
2135
}),
2236
// Example of an async component
23-
dynamicComponent: defineAsyncComponent({
37+
dynamicComponent: props => h(defineAsyncComponent({
2438
loadingComponent: () => 'Loading...',
2539
loader: () => Promise.resolve(defineComponent({
2640
props: { someProp: String },
27-
render: props => h('p', 'An dynamically imported custom component: ' + props.someProp),
41+
render(componentProps) {
42+
return h('p', 'An dynamically imported custom component: ' + componentProps.someProp)
43+
},
2844
})),
45+
}), {
46+
...props.value,
2947
}),
3048
},
3149
}
3250
33-
const blocks = [
51+
const value = [
3452
{
3553
_type: 'lazyRegisteredComponent',
3654
someProp: 'some value',

pnpm-lock.yaml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)