diff --git a/example/.env.example b/example/.env.example index 23a08f6..993e2ca 100644 --- a/example/.env.example +++ b/example/.env.example @@ -1,2 +1,3 @@ NEXT_PUBLIC_STORYBLOK_TOKEN= +STORYBLOK_PREVIEW_TOKEN= PREVIEW_TOKEN= diff --git a/example/src/graphql/queries/articleItem.graphql b/example/src/graphql/queries/articleItem.graphql index c9cd1eb..dfabc39 100644 --- a/example/src/graphql/queries/articleItem.graphql +++ b/example/src/graphql/queries/articleItem.graphql @@ -6,6 +6,7 @@ query articleItem($slug: ID!) { filename } intro + _editable } uuid } diff --git a/example/src/graphql/queries/galleryItem.graphql b/example/src/graphql/queries/galleryItem.graphql index 9d75f61..a58a226 100644 --- a/example/src/graphql/queries/galleryItem.graphql +++ b/example/src/graphql/queries/galleryItem.graphql @@ -4,6 +4,7 @@ query galleryItem($slug: ID!) { images { filename } + _editable } uuid } diff --git a/example/src/graphql/sdk.ts b/example/src/graphql/sdk.ts index 637e108..67edc83 100644 --- a/example/src/graphql/sdk.ts +++ b/example/src/graphql/sdk.ts @@ -935,7 +935,7 @@ export type ArticleItemQuery = { __typename?: 'QueryType' } & { content?: Maybe< { __typename?: 'ArticleComponent' } & Pick< ArticleComponent, - 'title' | 'intro' + 'title' | 'intro' | '_editable' > & { teaser_image?: Maybe< { __typename?: 'Asset' } & Pick @@ -989,11 +989,14 @@ export type GalleryItemQuery = { __typename?: 'QueryType' } & { GalleryItem?: Maybe< { __typename?: 'GalleryItem' } & Pick & { content?: Maybe< - { __typename?: 'GalleryComponent' } & { - images?: Maybe< - Array>> - >; - } + { __typename?: 'GalleryComponent' } & Pick< + GalleryComponent, + '_editable' + > & { + images?: Maybe< + Array>> + >; + } >; } >; @@ -1008,6 +1011,7 @@ export const ArticleItemDocument = gql` filename } intro + _editable } uuid } @@ -1040,6 +1044,7 @@ export const GalleryItemDocument = gql` images { filename } + _editable } uuid } diff --git a/example/src/pages/article/[slug].tsx b/example/src/pages/article/[slug].tsx index a46fa55..8e4d1b4 100644 --- a/example/src/pages/article/[slug].tsx +++ b/example/src/pages/article/[slug].tsx @@ -15,8 +15,6 @@ import { type ArticleProps = WithStoryProps; const Article = ({ story }: ArticleProps) => { - // console.log(story); - return ( [1]; + /** Storyblok API token (preview or publish) */ token: string; + /** + * Which version of the story to load. Defaults to `'draft'` in development, + * and `'published'` in production. + * + * @default `process.env.NODE_ENV === 'development' ? 'draft' : 'published'` + */ version?: 'draft' | 'published'; } diff --git a/src/image/Image.tsx b/src/image/Image.tsx index 30bca1c..e472ee2 100644 --- a/src/image/Image.tsx +++ b/src/image/Image.tsx @@ -12,12 +12,19 @@ interface ImageProps HTMLImageElement >, GetImagePropsOptions { - /* - It's recommended to put lazy=false on images that are already in viewport - on load - @default true - */ + /** + * It's recommended to put lazy=false on images that are already in viewport + * on load. If false, the image is loaded eagerly. + * + * @default true + */ lazy?: boolean; + /** + * The media attribute specifies a media condition (similar to a media query) + * that the user agent will evaluate for each element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture#the_media_attribute + */ media?: string; } diff --git a/src/image/getImageProps.ts b/src/image/getImageProps.ts index f47feeb..7988566 100644 --- a/src/image/getImageProps.ts +++ b/src/image/getImageProps.ts @@ -1,7 +1,23 @@ export interface GetImagePropsOptions { - fixed?: number[]; - fluid?: number | number[]; - /** @default true */ + /** + * Optimize the image sizes for a fixed size. Use if you know the exact size + * the image will be. + * Format: `[width, height]`. + */ + fixed?: [number, number]; + /** + * Optimize the image sizes for a fluid size. Fluid is for images that stretch + * a container of variable size (different size based on screen size). + * Use if you don't know the exact size the image will be. + * Format: `width` or `[width, height]`. + */ + fluid?: number | [number, number]; + /** + * Apply the `smart` filter. + * @see https://www.storyblok.com/docs/image-service#facial-detection-and-smart-cropping + * + * @default true + */ smart?: boolean; } diff --git a/src/next/previewHandlers.ts b/src/next/previewHandlers.ts index 7a6b2b4..c92f87d 100644 --- a/src/next/previewHandlers.ts +++ b/src/next/previewHandlers.ts @@ -1,7 +1,13 @@ import type { NextApiRequest, NextApiResponse } from 'next'; interface NextPreviewHandlersProps { + /** + * A secret token (random string of characters) to activate preview mode. + */ previewToken: string; + /** + * Storyblok API token with preview access (access to draft versions) + */ storyblokToken: string; } diff --git a/src/utils/getExcerpt.ts b/src/utils/getExcerpt.ts index 38e1abe..247ab61 100644 --- a/src/utils/getExcerpt.ts +++ b/src/utils/getExcerpt.ts @@ -1,6 +1,25 @@ import { Richtext } from '../story'; -import { getPlainText } from './getPlainText'; +import { getPlainText, GetPlainTextOptions } from './getPlainText'; -export const getExcerpt = (richtext: Richtext, maxLength = 320) => - getPlainText(richtext, { addNewlines: false, maxLength }); +interface GetExcerptOptions extends GetPlainTextOptions { + /** + * After how many characters the text should be cut off. + * + * @default 320 + */ + maxLength?: number; +} + +export const getExcerpt = ( + richtext: Richtext, + { maxLength, ...options }: GetExcerptOptions = { maxLength: 320 }, +) => { + const text = getPlainText(richtext, { addNewlines: false, ...options }); + + if (!text || !maxLength || text?.length < maxLength) { + return text; + } + + return `${text?.substring(0, maxLength)}…`; +}; diff --git a/src/utils/getPlainText.ts b/src/utils/getPlainText.ts index 525524b..5f8d6d2 100644 --- a/src/utils/getPlainText.ts +++ b/src/utils/getPlainText.ts @@ -41,19 +41,23 @@ const renderNodes = (nodes: any, addNewlines: boolean) => .map((node) => renderNode(node, addNewlines)) .filter((node) => node !== null) .join('') - // Remove multiple spaces with one + // Replace multiple spaces with one .replace(/[^\S\r\n]{2,}/g, ' '); -interface GetExcerptOptions { - /* @default true */ +export interface GetPlainTextOptions { + /** + * Whether to add newlines (`\n\n`) after nodes and instead of hr's and + * br's. + * + * @default true + */ addNewlines?: boolean; - maxLength?: number; } export const getPlainText = ( richtext: Richtext, - { addNewlines, maxLength }: GetExcerptOptions = {}, -) => { + { addNewlines }: GetPlainTextOptions = {}, +): string => { if (!richtext?.content?.length) { return ''; } @@ -63,9 +67,5 @@ export const getPlainText = ( addNewlines !== undefined ? addNewlines : true, ); - if (!text || !maxLength || text?.length < maxLength) { - return text; - } - - return `${text?.substring(0, maxLength)}…`; + return text; }; diff --git a/website/docs/api/Image.md b/website/docs/api/Image.md new file mode 100644 index 0000000..b157f32 --- /dev/null +++ b/website/docs/api/Image.md @@ -0,0 +1,86 @@ +--- +id: Image +title: Image +sidebar_label: Image +hide_title: true +--- + +# `Image` + +A component that renders optimized and responsive images using Storyblok's image service. With support for lazy loading and LQIP (Low-Quality Image Placeholders). + +The component will automatically try to load a WebP version of the image if the browser supports it. + +Lazy loading uses the native browser implementation if available, otherwise an IntersectionObserver (polyfilled if needed) is used as fallback. + +The low-quality image placeholder is a small (max 32 pixels wide), blurred version of the image that is loaded as fast as possible and presented while the full image is loading. As soon as the full image loads, the placeholder is faded out. + +## Parameters + +`Image` accepts the normal HTML `img` attributes, but the `src` is expected to be a Storyblok asset URL. + +There are two important parameters that make sure the images are responsive: `fixed` and `fluid`. You should use one or the other. +- `fixed`: use if you know the exact size the image will be. +- `fluid`: is made for images that stretch a container of variable size (different size based on screen size). Use if you don't know the exact size the image will be. + +```ts no-transpile +interface GetImagePropsOptions { + /** + * Optimize the image sizes for a fixed size. Use if you know the exact size + * the image will be. + * Format: `[width, height]`. + */ + fixed?: [number, number]; + /** + * Optimize the image sizes for a fluid size. Fluid is for images that stretch + * a container of variable size (different size based on screen size). + * Use if you don't know the exact size the image will be. + * Format: `width` or `[width, height]`. + */ + fluid?: number | [number, number]; + /** + * Apply the `smart` filter. + * @see https://www.storyblok.com/docs/image-service#facial-detection-and-smart-cropping + * + * @default true + */ + smart?: boolean; +} + +interface ImageProps + extends React.DetailedHTMLProps< + React.ImgHTMLAttributes, + HTMLImageElement + >, + GetImagePropsOptions { + /** + * It's recommended to put lazy=false on images that are already in viewport + * on load. If false, the image is loaded eagerly. + * + * @default true + */ + lazy?: boolean; + /** + * The media attribute specifies a media condition (similar to a media query) + * that the user agent will evaluate for each element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture#the_media_attribute + */ + media?: string; +} + +const Image: (props: ImageProps) => JSX.Element +``` + +## Usage + +### Basic example + +```ts +{storyblok_image?.alt} +``` diff --git a/website/docs/api/StoryProvider.md b/website/docs/api/StoryProvider.md new file mode 100644 index 0000000..917976e --- /dev/null +++ b/website/docs/api/StoryProvider.md @@ -0,0 +1,41 @@ +--- +id: StoryProvider +title: StoryProvider +sidebar_label: StoryProvider +hide_title: true +--- + +# `StoryProvider` + +A global provider that provides the context to make `withStory` work, holding the current story. Also makes sure the Storyblok JS Bridge gets loaded when needed. + +## Parameters + +`StoryProvider` accepts the following properties: + +```ts no-transpile +interface ProviderProps { + children: ReactNode; + /** + * Relations that need to be resolved in preview mode, for example: + * `['Post.author']` + */ + resolveRelations?: string[]; +} + +const StoryProvider: (props: ProviderProps) => JSX.Element +``` + +## Usage + +### Basic example + +Wrap your entire app in the provider. For example in Next.js, in the render function of `_app`: + +```ts +// Other providers + + // The rest of your app + + +``` diff --git a/website/docs/api/getClient.md b/website/docs/api/getClient.md new file mode 100644 index 0000000..ac48dd0 --- /dev/null +++ b/website/docs/api/getClient.md @@ -0,0 +1,87 @@ +--- +id: getClient +title: getClient +sidebar_label: GraphQL Client +hide_title: true +--- + +# `getClient` + +A function that properly configures a `graphql-request` client to interact with the [Storyblok GraphQL API](https://www.storyblok.com/docs/graphql-api). + +This function expects the dependencies `graphql-request` and `graphql` to be installed. + +## Parameters + +`getClient` accepts a configuration object parameter, with the following options: + +```ts no-transpile +interface ClientOptions { + /** + * Custom fetch init parameters, `graphql-request` version. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#parameters + */ + additionalOptions?: ConstructorParameters[1]; + /** Storyblok API token (preview or publish) */ + token: string; + /** + * Which version of the story to load. Defaults to `'draft'` in development, + * and `'published'` in production. + * + * @default `process.env.NODE_ENV === 'development' ? 'draft' : 'published'` + */ + version?: 'draft' | 'published'; +} + +type SdkFunctionWrapper = (action: () => Promise) => Promise; +type GetSdk = (client: GraphQLClient, withWrapper?: SdkFunctionWrapper) => T; + +const getClient: (options: ClientOptions) => GraphQLClient +``` + +The Storyblok API `token` is required. + +## Usage + +### Basic example + +```ts +import { gql } from 'graphql-request'; + +const client = getClient({ + token: process.env.NEXT_PUBLIC_STORYBLOK_TOKEN, +}); + +const query = gql` + { + ArticleItem(id: "article/article-1") { + content { + title + teaser_image { + filename + } + intro + _editable + } + uuid + } + } +` + +const result = await client.request(query); +``` + +### Recommended: with GraphQL Code Generator + +In combination with [GraphQL Code Generator](https://www.graphql-code-generator.com/) you can generate a fully typed GraphQL SDK. + +The client returned by `getClient` can be wrapped in `getSdk`: + +```ts +const sdk = getSdk(client); +``` + +For a full configuration, please see the [example](https://github.com/storyofams/storyblok-toolkit/edit/master/example). The relevant configuration files are `./.graphqlrc.yaml`, `./lib/graphqlClient.ts` and `./graphql`. + +For more information on this configuration of GraphQL Code Generator and its options, [check out the docs](https://www.graphql-code-generator.com/docs/plugins/typescript-graphql-request). diff --git a/website/docs/api/getExcerpt.md b/website/docs/api/getExcerpt.md new file mode 100644 index 0000000..1423358 --- /dev/null +++ b/website/docs/api/getExcerpt.md @@ -0,0 +1,67 @@ +--- +id: getExcerpt +title: getExcerpt +sidebar_label: getExcerpt +hide_title: true +--- + +# `getExcerpt` + +A utility function that converts Storyblok Richtext to plain text, cut off after a specified amount of characters. + +## Parameters + +`getExcerpt` accepts a richtext object and a configuration object parameter, with the following options: + +```ts no-transpile +interface GetPlainTextOptions { + /** + * Whether to add newlines (`\n\n`) after nodes and instead of hr's and + * br's. + * + * @default true + */ + addNewlines?: boolean; +} + +interface GetExcerptOptions extends GetPlainTextOptions { + /** + * After how many characters the text should be cut off. + * + * @default 320 + */ + maxLength?: number; +} + +const getExcerpt = ( + richtext: Richtext, + options?: GetExcerptOptions, +) => string +``` + +## Usage + +### Basic example + +```ts +const richtext = { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + text: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + type: 'text', + }, + ], + }, + ], +}; + +const excerpt = getExcerpt(richtext, { maxLength: 50 }); + +// console.log(excerpt); +// Lorem ipsum dolor sit amet, consectetur adipiscing… +``` diff --git a/website/docs/api/getImageProps.md b/website/docs/api/getImageProps.md new file mode 100644 index 0000000..1f891e6 --- /dev/null +++ b/website/docs/api/getImageProps.md @@ -0,0 +1,59 @@ +--- +id: getImageProps +title: getImageProps +sidebar_label: getImageProps +hide_title: true +--- + +# `getImageProps` + +A utility function that returns optimized (responsive) image attributes `src`, `srcSet`, etc. + +Used internally by the `Image` component. + +## Parameters + +`getImageProps` accepts an image URL (Storyblok asset URL!) and a configuration object parameter, with the following options: + +```ts no-transpile +interface GetImagePropsOptions { + /** + * Optimize the image sizes for a fixed size. Use if you know the exact size + * the image will be. + * Format: `[width, height]`. + */ + fixed?: [number, number]; + /** + * Optimize the image sizes for a fluid size. Fluid is for images that stretch + * a container of variable size (different size based on screen size). + * Use if you don't know the exact size the image will be. + * Format: `width` or `[width, height]`. + */ + fluid?: number | [number, number]; + /** + * Apply the `smart` filter. + * @see https://www.storyblok.com/docs/image-service#facial-detection-and-smart-cropping + * + * @default true + */ + smart?: boolean; +} + +const getImageProps: (imageUrl: string, options?: GetImagePropsOptions) => { + src?: undefined; + srcSet?: undefined; + width?: undefined; + height?: undefined; + sizes?: undefined; +} +``` + +## Usage + +### Basic example + +```ts +const imageProps = getImageProps(storyblok_image?.filename, { + fluid: 696 +}); +``` diff --git a/website/docs/api/getPlainText.md b/website/docs/api/getPlainText.md new file mode 100644 index 0000000..311cb6e --- /dev/null +++ b/website/docs/api/getPlainText.md @@ -0,0 +1,58 @@ +--- +id: getPlainText +title: getPlainText +sidebar_label: getPlainText +hide_title: true +--- + +# `getPlainText` + +A utility function that converts Storyblok Richtext to plain text. + +## Parameters + +`getPlainText` accepts a richtext object and a configuration object parameter, with the following options: + +```ts no-transpile +interface GetPlainTextOptions { + /** + * Whether to add newlines (`\n\n`) after nodes and instead of hr's and + * br's. + * + * @default true + */ + addNewlines?: boolean; +} + +const getPlainText = ( + richtext: Richtext, + options?: GetPlainTextOptions, +) => string +``` + +## Usage + +### Basic example + +```ts +const richtext = { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + text: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + type: 'text', + }, + ], + }, + ], +}; + +const text = getPlainText(richtext); + +// console.log(text); +// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +``` diff --git a/website/docs/api/getStaticPropsWithSdk.md b/website/docs/api/getStaticPropsWithSdk.md new file mode 100644 index 0000000..69a63f1 --- /dev/null +++ b/website/docs/api/getStaticPropsWithSdk.md @@ -0,0 +1,79 @@ +--- +id: getStaticPropsWithSdk +title: getStaticPropsWithSdk +sidebar_label: getStaticPropsWithSdk +hide_title: true +--- + +# `getStaticPropsWithSdk` + +A wrapper function that injects a client from `getClient`, typed by codegen, into Next.js `getStaticProps`. + +It supports Next.js's Preview mode, by making sure to query the correct version (draft or published) of the content. + +This function requires that GraphQL Code Generator is already set up, refer to [`getClients` example](/docs/api/getClient#recommended-with-graphql-code-generator) for more information. + +## Parameters + +`getStaticPropsWithSdk` expects a client from `getClient`, `getSdk` from `graphql-codegen`, and a Storyblok preview API token to be provided. It returns a function that can be wrapper around `getStaticProps`. + +```ts no-transpile +type SdkFunctionWrapper = (action: () => Promise) => Promise; +type GetSdk = (client: GraphQLClient, withWrapper?: SdkFunctionWrapper) => T; + +type GetStaticPropsWithSdk< + R, + P extends { [key: string]: any } = { [key: string]: any }, + Q extends ParsedUrlQuery = ParsedUrlQuery +> = ( + context: GetStaticPropsContext & { sdk: R }, +) => Promise>; + +const getStaticPropsWithSdk: (getSdk: GetSdk, client: GraphQLClient, storyblokToken?: string, additionalClientOptions?: ConstructorParameters[1]) => (getStaticProps: GetStaticPropsWithSdk) => (context: GetStaticPropsContext) => Promise<...> +``` + +## Usage + +### Basic example + +```ts +import { gql } from 'graphql-request'; + +const client = getClient({ + token: process.env.NEXT_PUBLIC_STORYBLOK_TOKEN, +}); + +const staticPropsWithSdk = getStaticPropsWithSdk( + getSdk, + client, + process.env.STORYBLOK_PREVIEW_TOKEN, +); + +export const getStaticProps: GetStaticProps = staticPropsWithSdk( + async ({ params: { slug }, sdk }) => { + // Example usage to request a story + let story; + let notFound = false; + + try { + story = (await sdk.articleItem({ slug: `article/${slug}` })).ArticleItem; + } catch (e) { + notFound = true; + } + + return { + props: { + story, + }, + notFound: notFound || !story, + revalidate: 60, + }; + }, +); +``` + +For a full configuration, please see the [example](https://github.com/storyofams/storyblok-toolkit/edit/master/example). + +For more information on this configuration of GraphQL Code Generator and its options, [check out the docs](https://www.graphql-code-generator.com/docs/plugins/typescript-graphql-request). diff --git a/website/docs/api/nextPreviewHandlers.md b/website/docs/api/nextPreviewHandlers.md new file mode 100644 index 0000000..6c769c4 --- /dev/null +++ b/website/docs/api/nextPreviewHandlers.md @@ -0,0 +1,54 @@ +--- +id: nextPreviewHandlers +title: nextPreviewHandlers +sidebar_label: nextPreviewHandlers +hide_title: true +--- + +# `nextPreviewHandlers` + +A function that provides API handlers to implement Next.js's preview mode. + +## Parameters + +`nextPreviewHandlers` accepts a configuration object parameter, with the following options: + +```ts no-transpile +interface NextPreviewHandlersProps { + /** + * A secret token (random string of characters) to activate preview mode. + */ + previewToken: string; + /** + * Storyblok API token with preview access (access to draft versions) + */ + storyblokToken: string; +} + +const nextPreviewHandlers: (options: NextPreviewHandlersProps) => (req: NextApiRequest, res: NextApiResponse) => Promise> +``` + +## Usage + +### Basic example + +Create the file `./pages/api/preview/[[...slug]].ts` with the following contents: + +```ts +import { nextPreviewHandlers } from '@storyofams/storyblok-toolkit'; + +export default nextPreviewHandlers({ + previewToken: process.env.PREVIEW_TOKEN, + storyblokToken: process.env.STORYBLOK_PREVIEW_TOKEN, +}); +``` + +To open preview mode of a story at `/article/article-1`, go to: +`/api/preview?token=YOUR_PREVIEW_TOKEN&slug=/article/article-1` + +You can configure preview mode as a preview URL in Storyblok: +`YOUR_WEBSITE/api/preview?token=YOUR_PREVIEW_TOKEN&slug=/` + +If you are using the preview handlers and are on a page configured with `withStory`, you will automatically be shown a small indicator to remind you that you are viewing the page in preview mode. It also allows you to exit preview mode. Alternatively you can go to `/api/preview/clear` to exit preview mode. + +![Next.js Preview mode](/img/preview-mode.png) diff --git a/website/docs/api/useStory.md b/website/docs/api/useStory.md new file mode 100644 index 0000000..cec776f --- /dev/null +++ b/website/docs/api/useStory.md @@ -0,0 +1,43 @@ +--- +id: useStory +title: useStory +sidebar_label: useStory +hide_title: true +--- + +# `useStory` + +A hook that wraps a `story`, and returns a version of that story that is in sync with the Visual Editor. + +## Parameters + +`useStory` expects a `story` as argument: + +```ts no-transpile +const useStory: (story: Story) => Story & { + [index: string]: any; +}> +``` + +## Usage + +### Basic example + +Wrap the `story` that you want to keep in sync: + +```ts +const Article = ({ providedStory }) => { + const story = useStory(providedStory); + + // You can use the story like normal: + return ( + +
+

+ {story?.content?.title} +

+
+
+ ); +}; +``` diff --git a/website/docs/api/withStory.md b/website/docs/api/withStory.md new file mode 100644 index 0000000..4667d27 --- /dev/null +++ b/website/docs/api/withStory.md @@ -0,0 +1,42 @@ +--- +id: withStory +title: withStory +sidebar_label: withStory +hide_title: true +--- + +# `withStory` + +HOC ([Higher-Order Component](https://reactjs.org/docs/higher-order-components.html)) that wraps a component/page where a story is loaded, and makes sure to that keep that story in sync with the Visual Editor. + +## Parameters + +`withStory` accepts a component with the `story` in its props: + +```ts no-transpile +const withStory: (WrappedComponent: React.ComponentType) => { + ({ story: providedStory, ...props }: T): JSX.Element; + displayName: string; +} +``` + +## Usage + +### Basic example + +Wrap the component where you want to keep the `story` in sync in `withStory`: + +```ts +const Article = ({ story }: WithStoryProps) => ( + +
+

+ {story?.content?.title} +

+ // The rest of the components +
+
+); + +export default withStory(Article); +``` diff --git a/website/docs/getting-started.md b/website/docs/getting-started.md index b984edc..ee0d65f 100644 --- a/website/docs/getting-started.md +++ b/website/docs/getting-started.md @@ -3,8 +3,35 @@ title: Getting Started slug: / --- -## Step 1: Installation +## Purpose + +The aim of this library is to make integrating Storyblok in a React frontend easy. + +We provide wrappers to abstract away the setup process (implementing the Storyblok JS Bridge, making the app work with the Visual Editor). We also provide an easy way to configure a GraphQL client, an optimized image component and some utility functions. + +## Installation ```bash npm2yarn npm install @storyofams/storyblok-toolkit ``` + +## Features + +The following API's are included: + +- `withStory()` and `StoryProvider`: `withStory` wraps a component/page where a story is loaded, and makes sure to keep it in sync with the Visual Editor. `StoryProvider` is a global provider that provides the context to make `withStory` work. +- `useStory()`: alternative to `withStory`, gets the synced story. +- `getClient()`: properly configures a `graphql-request` client to interact with Storyblok's GraphQL API. +- `Image`: automatically optimized and responsive images using Storyblok's image service. With LQIP (Low-Quality Image Placeholders) support. +- `getImageProps()`: get optimized image sizes without using `Image`. +- `getExcerpt()`: get an excerpt text from a richtext field. +- `getPlainText()`: get plaintext from a richtext field. + +Next.js specific: +- `getStaticPropsWithSdk()`: provides a properly configured `graphql-request` client, typed using `graphql-code-generator` to interact with Storyblok's GraphQL API, as a prop inside of `getStaticProps`. +- `nextPreviewHandlers()`: API handlers to implement Next.js's preview mode. + + +## Example + +Please see [the example](https://github.com/storyofams/storyblok-toolkit/edit/master/example) to see how this library can be used. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 241a492..a30cbc9 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -15,7 +15,8 @@ module.exports = { title: 'Storyblok Toolkit', logo: { alt: 'Story of AMS Logo', - src: 'img/logo.svg', + src: 'img/logo.png', + srcDark: 'img/logo-dark.png', }, items: [ { @@ -24,6 +25,16 @@ module.exports = { label: 'Docs', position: 'left', }, + { + href: '/docs', + label: 'Getting Started', + position: 'right', + }, + { + href: '/docs/api/StoryProvider', + label: 'API', + position: 'right', + }, { href: 'https://github.com/storyofams/storyblok-toolkit', label: 'GitHub', @@ -53,7 +64,7 @@ module.exports = { ], }, ], - copyright: `Copyright © ${new Date().getFullYear()} Story of AMS.`, + copyright: `Copyright © ${new Date().getFullYear()} Story of AMS.`, }, }, presets: [ diff --git a/website/sidebars.js b/website/sidebars.js index cea258f..6c08a19 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -5,5 +5,36 @@ module.exports = { label: 'Introduction', items: ['getting-started'], }, + { + type: 'category', + label: 'API', + collapsed: false, + items: [ + { + type: 'category', + label: 'General', + items: ['api/StoryProvider', 'api/withStory', 'api/useStory'], + }, + { + type: 'doc', + id: 'api/getClient', + }, + { + type: 'category', + label: 'Images', + items: ['api/Image', 'api/getImageProps'], + }, + { + type: 'category', + label: 'Utilities', + items: ['api/getPlainText', 'api/getExcerpt'], + }, + { + type: 'category', + label: 'Next.js Specific', + items: ['api/getStaticPropsWithSdk', 'api/nextPreviewHandlers'], + }, + ], + }, ], }; diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 718fdd2..8d34725 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -33,6 +33,10 @@ html[data-theme="dark"] { padding: 0 var(--ifm-pre-padding); } +.navbar__logo { + margin-right: 24px; +} + .hero__subtitle { color: var(--hero-subtitle); } diff --git a/website/static/img/docusaurus.png b/website/static/img/docusaurus.png deleted file mode 100644 index f458149..0000000 Binary files a/website/static/img/docusaurus.png and /dev/null differ diff --git a/website/static/img/favicon.ico b/website/static/img/favicon.ico index c01d54b..ffb1094 100644 Binary files a/website/static/img/favicon.ico and b/website/static/img/favicon.ico differ diff --git a/website/static/img/logo-dark.png b/website/static/img/logo-dark.png new file mode 100644 index 0000000..f21d68a Binary files /dev/null and b/website/static/img/logo-dark.png differ diff --git a/website/static/img/logo.png b/website/static/img/logo.png new file mode 100644 index 0000000..83d059c Binary files /dev/null and b/website/static/img/logo.png differ diff --git a/website/static/img/logo.svg b/website/static/img/logo.svg deleted file mode 100644 index 9db6d0d..0000000 --- a/website/static/img/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/static/img/preview-mode.png b/website/static/img/preview-mode.png new file mode 100644 index 0000000..6eaa8c2 Binary files /dev/null and b/website/static/img/preview-mode.png differ diff --git a/website/static/img/undraw_docusaurus_mountain.svg b/website/static/img/undraw_docusaurus_mountain.svg deleted file mode 100644 index 431cef2..0000000 --- a/website/static/img/undraw_docusaurus_mountain.svg +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/static/img/undraw_docusaurus_react.svg b/website/static/img/undraw_docusaurus_react.svg deleted file mode 100644 index e417050..0000000 --- a/website/static/img/undraw_docusaurus_react.svg +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/static/img/undraw_docusaurus_tree.svg b/website/static/img/undraw_docusaurus_tree.svg deleted file mode 100644 index a05cc03..0000000 --- a/website/static/img/undraw_docusaurus_tree.svg +++ /dev/null @@ -1 +0,0 @@ -docu_tree \ No newline at end of file