├── e2e-projects └── nextjs │ ├── .gitignore │ ├── app │ ├── layout.tsx │ ├── PrismicToolbar │ │ └── page.tsx │ ├── PrismicLink │ │ ├── client │ │ │ └── page.tsx │ │ └── page.tsx │ ├── PrismicImage │ │ ├── client │ │ │ ├── page.tsx │ │ │ └── ClientTest.tsx │ │ └── page.tsx │ ├── PrismicText │ │ └── page.tsx │ ├── PrismicTable │ │ └── page.tsx │ ├── SliceZone │ │ └── page.tsx │ └── PrismicRichText │ │ └── page.tsx │ ├── next.config.ts │ ├── package.json │ ├── prismicio.ts │ ├── tsconfig.json │ └── prismic-types.d.ts ├── tests ├── infra │ ├── index.ts │ ├── content │ │ ├── index.ts │ │ ├── table.ts │ │ ├── link.ts │ │ ├── page.ts │ │ ├── image.ts │ │ └── richtext.ts │ ├── teardown.ts │ ├── setup.ts │ ├── test.ts │ └── client.ts ├── PrismicToolbar.spec.ts ├── PrismicText.spec.ts ├── PrismicTable.spec.ts ├── SliceZone.spec.ts ├── PrismicLink.spec.ts ├── PrismicImage.spec.ts └── PrismicRichText.spec.ts ├── examples ├── with-typescript │ ├── package.json │ ├── README.md │ └── index.tsx ├── custom-slicezone-props │ ├── package.json │ ├── README.md │ ├── types.ts │ └── index.tsx └── router-link │ ├── package.json │ ├── README.md │ └── index.tsx ├── .size-limit.ts ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── prerelease-pr-cleanup.yml │ ├── ci.yml │ ├── prerelease-canary.yml │ └── prerelease-pr.yml ├── .gitattributes ├── .editorconfig ├── .versionrc ├── tsconfig.json ├── .env.test.example ├── .prettierrc ├── src ├── lib │ └── devMsg.ts ├── index.ts ├── PrismicToolbar.tsx ├── PrismicText.tsx ├── PrismicTable.tsx ├── PrismicImage.tsx ├── PrismicLink.tsx ├── SliceZone.tsx └── PrismicRichText.tsx ├── .gitignore ├── .prettierignore ├── vite.config.ts ├── messages ├── classname-is-not-a-valid-prop.md ├── prismictext-works-only-with-rich-text-and-title-fields.md ├── alt-must-be-an-empty-string.md └── missing-link-properties.md ├── eslint.config.mjs ├── playwright.config.ts ├── package.json ├── README.md ├── CONTRIBUTING.md └── LICENSE /e2e-projects/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.next/ 3 | /out/ 4 | next-env.d.ts 5 | -------------------------------------------------------------------------------- /tests/infra/index.ts: -------------------------------------------------------------------------------- 1 | export { expect } from "@playwright/test"; 2 | export { test } from "./test"; 3 | -------------------------------------------------------------------------------- /examples/with-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@prismicio/react": "../../src" 5 | }, 6 | "devDependencies": { 7 | "@prismicio/client": "^7.11.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/infra/content/index.ts: -------------------------------------------------------------------------------- 1 | export * as image from "./image"; 2 | export * as link from "./link"; 3 | export * as page from "./page"; 4 | export * as richText from "./richtext"; 5 | export * as table from "./table"; 6 | -------------------------------------------------------------------------------- /examples/custom-slicezone-props/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@prismicio/react": "../../src" 5 | }, 6 | "devDependencies": { 7 | "@prismicio/client": "^7.5.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.size-limit.ts: -------------------------------------------------------------------------------- 1 | import type { SizeLimitConfig } from "size-limit"; 2 | import { exports } from "./package.json"; 3 | 4 | module.exports = [ 5 | { 6 | name: "@prismicio/react", 7 | path: exports["."].default, 8 | }, 9 | ] satisfies SizeLimitConfig; 10 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | export default function RootLayout({ children }: { children: ReactNode }) { 4 | return ( 5 | 6 | {children} 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /examples/router-link/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@prismicio/react": "../../src", 5 | "react-router-dom": "^5.2.0" 6 | }, 7 | "devDependencies": { 8 | "@prismicio/client": "^7.5.0", 9 | "@types/react-router-dom": "^5.1.8" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 👪 Prismic Community Forum 4 | url: https://community.prismic.io 5 | about: Ask a question about the package or raise an issue directly related to Prismic. You will usually get support there more quickly! 6 | -------------------------------------------------------------------------------- /examples/router-link/README.md: -------------------------------------------------------------------------------- 1 | # Router Link 2 | 3 | This example shows how to use a router-specific Link component with ``. This is helpful when links within your app need to use a special component for internal Links. `react-router-dom`, for example, requires using its `` component. 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # asserts everything is text 2 | * text eol=lf 3 | 4 | # treats lock files as binaries to prevent merge headache 5 | package-lock.json -diff 6 | yarn.lock -diff 7 | 8 | # treats assets as binaries 9 | *.png binary 10 | *.jpg binary 11 | *.jpeg binary 12 | *.gif binary 13 | *.ico binary 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/app/PrismicToolbar/page.tsx: -------------------------------------------------------------------------------- 1 | import { PrismicToolbar } from "@prismicio/react"; 2 | 3 | import { createClient } from "@/prismicio"; 4 | 5 | export default async function Page() { 6 | const client = await createClient(); 7 | 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /examples/custom-slicezone-props/README.md: -------------------------------------------------------------------------------- 1 | # Custom `SliceZone` Props 2 | 3 | This example shows how to pass custom props to Slice Zone components. This is helpful when your Slice components require data not contained with a Slice. It can also be helpful if your Slice component was not written specifically to accept a Slice as a prop. 4 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | import { fileURLToPath } from "node:url"; 3 | 4 | const nextConfig: NextConfig = { 5 | outputFileTracingRoot: fileURLToPath(new URL("../..", import.meta.url)), 6 | images: { 7 | remotePatterns: [{ hostname: "images.prismic.io" }], 8 | }, 9 | }; 10 | 11 | export default nextConfig; 12 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { 4 | "type": "feat", 5 | "section": "Features" 6 | }, 7 | { 8 | "type": "fix", 9 | "section": "Bug Fixes" 10 | }, 11 | { 12 | "type": "refactor", 13 | "section": "Refactor" 14 | }, 15 | { 16 | "type": "docs", 17 | "section": "Documentation" 18 | }, 19 | { 20 | "type": "chore", 21 | "section": "Chore" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/app/PrismicLink/client/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { PrismicLink } from "@prismicio/react"; 5 | 6 | export default function Page() { 7 | const [ref, setRef] = useState(null); 8 | 9 | return ( 10 | 11 | tagname: {ref?.tagName} 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /examples/with-typescript/README.md: -------------------------------------------------------------------------------- 1 | # With TypeScript 2 | 3 | This example shows how `@prismicio/react` is used in TypeScript projects. `@prismicio/react` is written in TypeScript using types from [`@prismicio/client`](https://github.com/prismicio/prismic-client). 4 | 5 | You can write types for your documents using `PrismicDocument`. Components from `@prismicio/react` will type check against your document and its fields. 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "skipLibCheck": true, 5 | 6 | "target": "esnext", 7 | "module": "esnext", 8 | "moduleResolution": "bundler", 9 | "resolveJsonModule": true, 10 | 11 | "jsx": "react-jsx", 12 | "lib": ["esnext", "dom"], 13 | 14 | "declaration": true, 15 | "outDir": "./dist", 16 | 17 | "types": ["react"] 18 | }, 19 | "exclude": ["node_modules", "dist", "e2e-projects"] 20 | } 21 | -------------------------------------------------------------------------------- /.env.test.example: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # The following environment variables are used to run Playwright tests. 3 | # Create a specific account for testing to avoid issues. 4 | ############################################################################### 5 | 6 | # The email address for your Prismic account. 7 | PLAYWRIGHT_PRISMIC_EMAIL= 8 | # The password to your Prismic account. 9 | PLAYWRIGHT_PRISMIC_PASSWORD= 10 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/app/PrismicImage/client/page.tsx: -------------------------------------------------------------------------------- 1 | import { isFilled } from "@prismicio/client"; 2 | import assert from "assert"; 3 | 4 | import { createClient } from "@/prismicio"; 5 | import { ClientTest } from "./ClientTest"; 6 | 7 | export default async function Page() { 8 | const client = await createClient(); 9 | const { data: tests } = await client.getSingle("image_test"); 10 | 11 | assert(isFilled.image(tests.filled)); 12 | 13 | return ; 14 | } 15 | -------------------------------------------------------------------------------- /tests/PrismicToolbar.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "./infra"; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto("/PrismicToolbar"); 5 | }); 6 | 7 | test("adds the Prismic toolbar", async ({ appPage }) => { 8 | await expect(appPage.toolbarIframe).toHaveCount(1); 9 | }); 10 | 11 | test("includes the repository name in the script element", async ({ 12 | appPage, 13 | }) => { 14 | await expect(appPage.toolbarScript).toHaveAttribute( 15 | "data-repository-name", 16 | appPage.repository.domain, 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/app/PrismicImage/client/ClientTest.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { ImageField } from "@prismicio/client"; 5 | import { PrismicImage } from "@prismicio/react"; 6 | 7 | export function ClientTest(props: { field: ImageField }) { 8 | const { field } = props; 9 | 10 | const [ref, setRef] = useState(null); 11 | 12 | return ( 13 |

14 | 15 | tagname: {ref?.tagName} 16 |

17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tests/infra/teardown.ts: -------------------------------------------------------------------------------- 1 | import { STORAGE_STATE } from "../../playwright.config"; 2 | import { test as teardown } from "./test"; 3 | 4 | teardown("delete repo", async ({ page, prismic }) => { 5 | const cookies = await page.context().cookies(); 6 | const repoName = cookies.find((c) => c.name === "repository-name")?.value; 7 | if (!repoName) return; 8 | const repo = prismic.getRepo(repoName); 9 | await repo.delete(); 10 | await page.context().clearCookies({ name: "repository-name" }); 11 | await page.context().storageState({ path: STORAGE_STATE }); 12 | }); 13 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-jsdoc"], 3 | "jsdocSeparateReturnsFromParam": true, 4 | "jsdocSeparateTagGroups": true, 5 | "jsdocSingleLineComment": false, 6 | "tsdoc": true, 7 | "printWidth": 80, 8 | "useTabs": true, 9 | "semi": true, 10 | "singleQuote": false, 11 | "quoteProps": "as-needed", 12 | "jsxSingleQuote": false, 13 | "trailingComma": "all", 14 | "bracketSpacing": true, 15 | "bracketSameLine": false, 16 | "arrowParens": "always", 17 | "requirePragma": false, 18 | "insertPragma": false, 19 | "htmlWhitespaceSensitivity": "css", 20 | "endOfLine": "lf" 21 | } 22 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev --port=4321 --turbo", 6 | "build": "next build", 7 | "start": "next start --port=4321" 8 | }, 9 | "dependencies": { 10 | "@prismicio/client": "^7.16.0", 11 | "@prismicio/react": "*", 12 | "next": "15.1.9", 13 | "react": "19.2.1", 14 | "react-dom": "19.2.1" 15 | }, 16 | "devDependencies": { 17 | "@prismicio/types-internal": "^3.6.0", 18 | "@types/node": "^22", 19 | "@types/react": "^19", 20 | "@types/react-dom": "^19", 21 | "typescript": "^5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/devMsg.ts: -------------------------------------------------------------------------------- 1 | import { version } from "../../package.json"; 2 | 3 | /** 4 | * Returns a `prismic.dev/msg` URL for a given message slug. 5 | * 6 | * @example 7 | * 8 | * ```ts 9 | * devMsg("missing-param"); 10 | * // => "https://prismic.dev/msg/react/v1.2.3/missing-param" 11 | * ``` 12 | * 13 | * @param slug - Slug for the message. This corresponds to a Markdown file in 14 | * the Git repository's `/messages` directory. 15 | * 16 | * @returns The `prismic.dev/msg` URL for the given slug. 17 | */ 18 | export function devMsg(slug: string) { 19 | return `https://prismic.dev/msg/react/v${version}/${slug}`; 20 | } 21 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/prismicio.ts: -------------------------------------------------------------------------------- 1 | import * as prismic from "@prismicio/client"; 2 | import { cookies } from "next/headers"; 3 | import assert from "node:assert"; 4 | 5 | export async function createClient(config: prismic.ClientConfig = {}) { 6 | const cookieJar = await cookies(); 7 | const repositoryName = cookieJar.get("repository-name")?.value; 8 | assert(repositoryName, "A repository-name cookie is required."); 9 | 10 | const client = prismic.createClient(repositoryName, { 11 | routes: [{ type: "page", path: "/page" }], 12 | fetchOptions: { cache: "no-store" }, 13 | ...config, 14 | }); 15 | 16 | return client; 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # custom 2 | dist 3 | examples/**/package-lock.json 4 | *.tgz 5 | 6 | # os 7 | .DS_Store 8 | ._* 9 | 10 | # node 11 | logs 12 | *.log 13 | node_modules 14 | 15 | # yarn 16 | yarn-debug.log* 17 | yarn-error.log* 18 | lerna-debug.log* 19 | .yarn-integrity 20 | yarn.lock 21 | 22 | # npm 23 | npm-debug.log* 24 | 25 | # tests 26 | coverage 27 | .eslintcache 28 | .nyc_output 29 | 30 | # .env 31 | .env 32 | .env.test 33 | .env*.local 34 | 35 | # vscode 36 | .vscode/* 37 | !.vscode/tasks.json 38 | !.vscode/launch.json 39 | *.code-workspace 40 | /test-results/ 41 | /playwright-report/ 42 | /blob-report/ 43 | /playwright/.cache/ 44 | /tests/infra/.storage-state.json 45 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # custom 2 | dist 3 | examples/**/package-lock.json 4 | *.tgz 5 | CHANGELOG.md 6 | e2e-projects/**/.next 7 | 8 | # os 9 | .DS_Store 10 | ._* 11 | 12 | # node 13 | logs 14 | *.log 15 | node_modules 16 | 17 | # yarn 18 | yarn-debug.log* 19 | yarn-error.log* 20 | lerna-debug.log* 21 | .yarn-integrity 22 | yarn.lock 23 | 24 | # npm 25 | npm-debug.log* 26 | 27 | # tests 28 | coverage 29 | .eslintcache 30 | .nyc_output 31 | 32 | # .env 33 | .env 34 | .env.test 35 | .env*.local 36 | 37 | # vscode 38 | .vscode/* 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | *.code-workspace 42 | /test-results/ 43 | /playwright-report/ 44 | /blob-report/ 45 | /playwright/.cache/ 46 | /tests/.storage-state.json 47 | -------------------------------------------------------------------------------- /examples/custom-slicezone-props/types.ts: -------------------------------------------------------------------------------- 1 | import * as prismic from "@prismicio/client"; 2 | 3 | export type HeroSlice = prismic.SharedSlice< 4 | "hero", 5 | prismic.SharedSliceVariation< 6 | "default", 7 | { 8 | heading: prismic.KeyTextField; 9 | buttonText: prismic.KeyTextField; 10 | cards: prismic.GroupField<{ 11 | title: prismic.KeyTextField; 12 | content: prismic.KeyTextField; 13 | }>; 14 | } 15 | > 16 | >; 17 | 18 | export type CallToActionSlice = prismic.SharedSlice< 19 | "call_to_action", 20 | prismic.SharedSliceVariation< 21 | "default", 22 | { 23 | text: prismic.KeyTextField; 24 | } 25 | > 26 | >; 27 | 28 | export type Slices = HeroSlice | CallToActionSlice; 29 | 30 | export type ExampleSliceZone = prismic.SliceZone; 31 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import typescript from "@rollup/plugin-typescript"; 4 | import preserveDirectives from "rollup-preserve-directives"; 5 | 6 | import { dependencies, peerDependencies } from "./package.json"; 7 | 8 | export default defineConfig({ 9 | plugins: [react()], 10 | build: { 11 | lib: { 12 | entry: { 13 | index: "./src/index.ts", 14 | }, 15 | formats: ["es"], 16 | }, 17 | minify: false, 18 | sourcemap: true, 19 | rollupOptions: { 20 | output: { 21 | preserveModules: true, 22 | preserveModulesRoot: "./src", 23 | }, 24 | external: [ 25 | ...Object.keys(dependencies), 26 | ...Object.keys(peerDependencies), 27 | ].map((name) => new RegExp(`^${name}(?:/.*)?$`)), 28 | plugins: [typescript({ rootDir: "./src" }), preserveDirectives()], 29 | }, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🙋‍♀️ Feature request 3 | about: Suggest an idea or enhancement for the package. 4 | title: "" 5 | labels: "enhancement" 6 | assignees: "" 7 | --- 8 | 9 | 10 | 11 | ### Is your feature request related to a problem? Please describe. 12 | 13 | 14 | 15 | ### Describe the solution you'd like 16 | 17 | 18 | 19 | ### Describe alternatives you've considered 20 | 21 | 22 | 23 | ### Additional context 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/router-link/index.tsx: -------------------------------------------------------------------------------- 1 | import * as prismic from "@prismicio/client"; 2 | import { PrismicLink, LinkProps } from "@prismicio/react"; 3 | import { Link } from "react-router-dom"; 4 | 5 | // This is an example Link field value. It contains a URL internal to the app. 6 | const field: prismic.LinkField = { 7 | link_type: prismic.LinkType.Web, 8 | url: "/internal-url", 9 | }; 10 | 11 | // This React component acts as a "shim" to convert the `href` prop provided by 12 | // `` to the `to` prop required by react-router-dom's ``. 13 | const LinkShim = ({ href, ...props }: LinkProps) => { 14 | return ; 15 | }; 16 | 17 | // We render the Link field using ``. Since the field contains an 18 | // internal URL, react-router-dom's `` component will render. 19 | export const App = () => { 20 | return ( 21 |
22 | 23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /messages/classname-is-not-a-valid-prop.md: -------------------------------------------------------------------------------- 1 | # `className` is not a valid prop 2 | 3 | `` and `` do not accept a `className` prop. These components render an array of React components and do not have a wrapping element. 4 | 5 | To add a `className` as a wrapper around the output of `` or ``, add a wrapper element with the `className`. 6 | 7 | ```tsx 8 | // ✅ Correct 9 |
10 | 11 |
12 | ``` 13 | 14 | ```tsx 15 | // ❌ Incorrect 16 | 17 | ``` 18 | 19 | To add a `className` to a specific block type when using ``, provide a custom component. 20 | 21 | ```tsx 22 | ( 26 |

{children}

27 | ), 28 | }} 29 | /> 30 | ``` 31 | -------------------------------------------------------------------------------- /.github/workflows/prerelease-pr-cleanup.yml: -------------------------------------------------------------------------------- 1 | name: prerelease-pr-cleanup 2 | 3 | permissions: 4 | contents: read 5 | id-token: write 6 | 7 | on: 8 | pull_request: 9 | types: [closed] 10 | 11 | jobs: 12 | cleanup: 13 | if: github.repository_owner == 'prismicio' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v6 17 | - uses: actions/setup-node@v6 18 | with: 19 | node-version: 24 20 | registry-url: "https://registry.npmjs.org" 21 | - name: "Deprecate PR prerelease" 22 | run: | 23 | PACKAGE_NAME=$(jq -r ".name" package.json) 24 | TAG="pr-${{ github.event.number }}" 25 | VERSION=$(npm view "$PACKAGE_NAME" dist-tags."$TAG" 2>/dev/null || echo "") 26 | if [ -n "$VERSION" ]; then 27 | npm deprecate "$PACKAGE_NAME@$VERSION" "PR ${{ github.event.number }} was closed" 28 | npm dist-tag rm "$PACKAGE_NAME" "$TAG" 29 | fi 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚨 Bug report 3 | about: Report a bug report to help improve the package. 4 | title: "" 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | 16 | 17 | ### Versions 18 | 19 | - `@prismicio/react`: 20 | - `react`: 21 | - Node.js: 22 | 23 | ### Reproduction 24 | 25 | 26 | 27 |
28 | Additional Details 29 |
30 | 31 |
32 | 33 | ### Steps to reproduce 34 | 35 | ### What is expected? 36 | 37 | ### What is actually happening? 38 | -------------------------------------------------------------------------------- /tests/PrismicText.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "./infra"; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto("/PrismicText"); 5 | }); 6 | 7 | test("renders text from rich text", async ({ page }) => { 8 | const text = page.getByTestId("filled"); 9 | await expect(text).toContainText("foo bar"); 10 | }); 11 | 12 | test("renders null when passed an empty field", async ({ page }) => { 13 | const text = page.getByTestId("empty"); 14 | await expect(text).toBeEmpty(); 15 | }); 16 | 17 | test("renders fallback when passed an empty field", async ({ page }) => { 18 | const text = page.getByTestId("fallback"); 19 | await expect(text).toContainText("foo"); 20 | }); 21 | 22 | test("renders null when passed a string field", async ({ page }) => { 23 | const keytext = page.getByTestId("keytext"); 24 | await expect(keytext).toBeEmpty(); 25 | const select = page.getByTestId("select"); 26 | await expect(select).toBeEmpty(); 27 | }); 28 | 29 | test("supports custom separator", async ({ page }) => { 30 | const text = page.getByTestId("custom-separator"); 31 | await expect(text).toContainText("fooxbar"); 32 | }); 33 | -------------------------------------------------------------------------------- /messages/prismictext-works-only-with-rich-text-and-title-fields.md: -------------------------------------------------------------------------------- 1 | # `` works only with Rich Text and Title fields 2 | 3 | `` works only with [Rich Text and Title fields][rich-text-title-field]. It renders the field's value as plain text (i.e. with no formatting, paragraphs, or headings). 4 | 5 | ```tsx 6 | // Will render a plain text version of the Rich Text field's value. 7 | 8 | ``` 9 | 10 | Other text-based field types, such as [Key Text][key-text-field] and [Select][select-field], cannot be rendered using ``. 11 | 12 | Since Key Text and Select field values are already plain text, you can render them inline without a special component. 13 | 14 | ```tsx 15 | // Will render the Key Text field's value. 16 | {doc.data.keyTextField} 17 | 18 | // Will render the Select field's value. 19 | {doc.data.selectField} 20 | ``` 21 | 22 | [rich-text-title-field]: https://prismic.io/docs/core-concepts/rich-text-title 23 | [key-text-field]: https://prismic.io/docs/core-concepts/key-text 24 | [select-field]: https://prismic.io/docs/core-concepts/select 25 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from "@eslint/js"; 4 | import tseslint from "typescript-eslint"; 5 | import prettier from "eslint-plugin-prettier/recommended"; 6 | import tsdoc from "eslint-plugin-tsdoc"; 7 | import react from "eslint-plugin-react"; 8 | import reactHooks from "eslint-plugin-react-hooks"; 9 | 10 | export default tseslint.config( 11 | { 12 | ignores: ["dist/", "playwright-report/", "**/.next/"], 13 | }, 14 | eslint.configs.recommended, 15 | tseslint.configs.recommended, 16 | prettier, 17 | react.configs.flat.recommended, 18 | react.configs.flat["jsx-runtime"], 19 | 20 | { 21 | settings: { 22 | react: { 23 | version: "detect", 24 | }, 25 | }, 26 | plugins: { 27 | tsdoc, 28 | // @ts-expect-error - Incompatible types 29 | "react-hooks": reactHooks, 30 | }, 31 | // @ts-expect-error - Incompatible types 32 | rules: { 33 | ...reactHooks.configs.recommended.rules, 34 | "@typescript-eslint/no-unused-vars": [ 35 | "error", 36 | { 37 | argsIgnorePattern: "^_", 38 | varsIgnorePattern: "^_", 39 | }, 40 | ], 41 | "tsdoc/syntax": "warn", 42 | }, 43 | }, 44 | ); 45 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { PrismicLink } from "./PrismicLink.js"; 2 | export type { PrismicLinkProps, LinkProps } from "./PrismicLink.js"; 3 | 4 | export { PrismicTable } from "./PrismicTable.js"; 5 | export type { PrismicTableProps } from "./PrismicTable.js"; 6 | 7 | export { PrismicText } from "./PrismicText.js"; 8 | export type { PrismicTextProps } from "./PrismicText.js"; 9 | 10 | export { PrismicRichText } from "./PrismicRichText.js"; 11 | export type { 12 | PrismicRichTextProps, 13 | JSXMapSerializer, 14 | JSXFunctionSerializer, 15 | } from "./PrismicRichText.js"; 16 | 17 | export { Element } from "@prismicio/client/richtext"; 18 | 19 | export { PrismicImage } from "./PrismicImage.js"; 20 | export type { PrismicImageProps } from "./PrismicImage.js"; 21 | 22 | export { SliceZone, TODOSliceComponent } from "./SliceZone.js"; 23 | export type { 24 | SliceComponentProps, 25 | SliceComponentType, 26 | SliceLike, 27 | SliceLikeGraphQL, 28 | SliceLikeRestV2, 29 | SliceZoneComponents, 30 | SliceZoneLike, 31 | SliceZoneProps, 32 | } from "./SliceZone.js"; 33 | 34 | export { PrismicToolbar } from "./PrismicToolbar.js"; 35 | export type { PrismicToolbarProps } from "./PrismicToolbar.js"; 36 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: push 4 | 5 | jobs: 6 | ci: 7 | name: Prepare (${{ matrix.os}}, Node.js ${{ matrix.node }}) 8 | 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest] 12 | node: [18, 20, 22] 13 | fail-fast: false 14 | 15 | runs-on: ${{ matrix.os }} 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node }} 22 | registry-url: "https://registry.npmjs.org/" 23 | - run: npm ci 24 | - run: npx playwright install --with-deps 25 | - run: npm run lint 26 | - run: npm run build 27 | - uses: andresz1/size-limit-action@v1 28 | if: ${{ github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' && matrix.node == 22 }} 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | - run: npm run e2e 32 | env: 33 | PLAYWRIGHT_PRISMIC_EMAIL: ${{ secrets.PLAYWRIGHT_PRISMIC_EMAIL }} 34 | PLAYWRIGHT_PRISMIC_PASSWORD: ${{ secrets.PLAYWRIGHT_PRISMIC_PASSWORD }} 35 | - uses: actions/upload-artifact@v4 36 | if: ${{ !cancelled() }} 37 | with: 38 | name: playwright-report (${{ matrix.os }}, Node.js ${{ matrix.node }}) 39 | path: playwright-report/ 40 | retention-days: 30 41 | -------------------------------------------------------------------------------- /.github/workflows/prerelease-canary.yml: -------------------------------------------------------------------------------- 1 | name: prerelease-canary 2 | 3 | permissions: 4 | contents: read 5 | id-token: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | publish: 14 | if: github.repository_owner == 'prismicio' 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v6 18 | - uses: actions/setup-node@v6 19 | with: 20 | node-version: 24 21 | registry-url: "https://registry.npmjs.org" 22 | - name: "Deprecate previous canary" 23 | run: | 24 | PACKAGE_NAME=$(jq -r ".name" package.json) 25 | PREVIOUS_VERSION=$(npm view "$PACKAGE_NAME" dist-tags.canary 2>/dev/null) 26 | if [ -n "$PREVIOUS_VERSION" ]; then 27 | npm deprecate "$PACKAGE_NAME@$PREVIOUS_VERSION" "Replaced by newer canary version" 28 | fi 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | - run: npm ci 32 | - name: "Generate new version" 33 | run: | 34 | SHORT_SHA=$(git rev-parse --short HEAD) 35 | CURRENT_VERSION=$(jq -r '.version' package.json) 36 | VERSION="${CURRENT_VERSION}-canary.${SHORT_SHA}" 37 | npm version $VERSION --no-git-tag-version 38 | - run: npm publish --provenance --tag canary 39 | env: 40 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 41 | -------------------------------------------------------------------------------- /e2e-projects/nextjs/app/PrismicText/page.tsx: -------------------------------------------------------------------------------- 1 | import { isFilled } from "@prismicio/client"; 2 | import { PrismicText } from "@prismicio/react"; 3 | import assert from "assert"; 4 | 5 | import { createClient } from "@/prismicio"; 6 | 7 | export default async function Page() { 8 | const client = await createClient(); 9 | const { data: tests } = await client.getSingle("rich_text_test"); 10 | 11 | assert(isFilled.richText(tests.filled)); 12 | assert(!isFilled.richText(tests.empty)); 13 | assert(isFilled.keyText(tests.keytext)); 14 | assert(isFilled.select(tests.select)); 15 | 16 | return ( 17 | <> 18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | {/* @ts-expect-error - We are purposely providing an field. */} 32 | 33 |
34 |
35 | {/* @ts-expect-error - We are purposely providing an field. */} 36 | 37 |
38 | 39 |
40 | 41 |
42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/prerelease-pr.yml: -------------------------------------------------------------------------------- 1 | name: prerelease-pr 2 | 3 | permissions: 4 | contents: read 5 | id-token: write 6 | 7 | on: 8 | pull_request: 9 | 10 | jobs: 11 | publish: 12 | if: github.repository_owner == 'prismicio' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | - uses: actions/setup-node@v6 17 | with: 18 | node-version: 24 19 | registry-url: "https://registry.npmjs.org" 20 | - name: "Deprecate previous PR prerelease" 21 | run: | 22 | PACKAGE_NAME=$(jq -r ".name" package.json) 23 | TAG="pr-${{ github.event.number }}" 24 | PREVIOUS_VERSION=$(npm view "$PACKAGE_NAME" dist-tags."$TAG" 2>/dev/null) 25 | if [ -n "$PREVIOUS_VERSION" ]; then 26 | npm deprecate "$PACKAGE_NAME@$PREVIOUS_VERSION" "Replaced by newer prerelease version" 27 | fi 28 | env: 29 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | - run: npm ci 31 | - name: "Generate new version" 32 | run: | 33 | SHORT_SHA=$(git rev-parse --short HEAD) 34 | CURRENT_VERSION=$(jq -r '.version' package.json) 35 | VERSION="${CURRENT_VERSION}-pr.${{ github.event.number }}.${SHORT_SHA}" 36 | npm version $VERSION --no-git-tag-version 37 | - run: npm publish --provenance --tag pr-${{ github.event.number }} 38 | env: 39 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | -------------------------------------------------------------------------------- /src/PrismicToolbar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FC, useEffect } from "react"; 4 | import { getToolbarSrc } from "@prismicio/client"; 5 | 6 | /** Props for ``. */ 7 | export type PrismicToolbarProps = { 8 | /** 9 | * The name of the Prismic repository. For example, `"my-repo"` if the 10 | * repository URL is `my-repo.prismic.io`. 11 | */ 12 | repositoryName: string; 13 | }; 14 | 15 | /** 16 | * Renders the Prismic Toolbar script to support draft previews. 17 | * 18 | * @example 19 | * 20 | * ```tsx 21 | * ; 22 | * ``` 23 | * 24 | * @see Learn how to set up preview functionality and the toolbar's role in preview sessions: {@link https://prismic.io/docs/previews} 25 | */ 26 | export const PrismicToolbar: FC = (props) => { 27 | const { repositoryName } = props; 28 | 29 | const src = getToolbarSrc(repositoryName); 30 | 31 | useEffect(() => { 32 | const existingScript = document.querySelector(`script[src="${src}"]`); 33 | 34 | if (!existingScript) { 35 | const script = document.createElement("script"); 36 | script.src = src; 37 | script.defer = true; 38 | 39 | // Used to distinguish the toolbar element from other elements. 40 | script.dataset.prismicToolbar = ""; 41 | script.dataset.repositoryName = repositoryName; 42 | 43 | // Disable Happy DOM `