From bc2adb66df407032aca70f39e8d63ae708873e9a Mon Sep 17 00:00:00 2001 From: Bart van den Aardweg Date: Wed, 24 Mar 2021 09:46:28 +0100 Subject: [PATCH 1/2] chore: docs styling --- example/.env.example | 1 + .../src/graphql/queries/articleItem.graphql | 1 + .../src/graphql/queries/galleryItem.graphql | 1 + example/src/graphql/sdk.ts | 17 +- example/src/pages/article/[slug].tsx | 2 - website/docusaurus.config.js | 2 +- website/src/css/custom.css | 4 + website/static/img/docusaurus.png | Bin 5142 -> 0 bytes website/static/img/favicon.ico | Bin 3626 -> 15086 bytes website/static/img/logo.png | Bin 0 -> 1292 bytes website/static/img/logo.svg | 1 - .../static/img/undraw_docusaurus_mountain.svg | 170 ------------------ .../static/img/undraw_docusaurus_react.svg | 169 ----------------- website/static/img/undraw_docusaurus_tree.svg | 1 - 14 files changed, 19 insertions(+), 350 deletions(-) delete mode 100644 website/static/img/docusaurus.png create mode 100644 website/static/img/logo.png delete mode 100644 website/static/img/logo.svg delete mode 100644 website/static/img/undraw_docusaurus_mountain.svg delete mode 100644 website/static/img/undraw_docusaurus_react.svg delete mode 100644 website/static/img/undraw_docusaurus_tree.svg 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 ( 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 F{0H{b<4^zq diff --git a/website/static/img/favicon.ico b/website/static/img/favicon.ico index c01d54bcd39a5f853428f3cd5aa0f383d963c484..ffb1094cd6ee6ce47b6be6ac8b5e7b721d41b5db 100644 GIT binary patch literal 15086 zcmeHNO^94Y5bie_DfAVd$kYy3fkU_hcLSwk*@UKDZ=B8lXnm*7dgM0VXkKrf;v zuMq?xdJy!Mz~&$l5HEUBK^BDsiNfaOs>43Nue-mVnz!$7X5NelsZg)_r|PTf>Yvy1 zvd(3$?+zb!kjw6qhn#!DIk&tV>-(Mi8EyL^+5hSu=gvdn5$M3godUDn(}O`&J+rKr zu`kN2DEwGWc*}3r+BZAPJz}CT6x#0I`8UY zYTKvSr@dE=4#3ya9IT^SHwU~wa_&E?;i`S4_~`?be1__DPk;VmPaOh`{Mfvzt?GRl z&u@PZldiQ_8GU2l)!L3M#^5f)pll8l*NF&d?i0E%_}>h5w!|jMSEzOJO!LY3>S7qV zPYm~ux3Ahig|R1JokUI$#nEu5eokHLnmNb zmwRG>5@x8*^qG8B-RZ&{gx`NvW7Ir#c2{dVWW5WZ-*l(rU8n3Dl4761U9I&f^xd%O z8-mUnD-}O&L1{iibv{q+<3n(#cx;SKWqZcz9X+4OqZhM|_`YJiZN?!T|Bf>lbB#SX zOY{En1o6`blrTbdj!)$--Vfq;bw3YHYiAC3wYG^!`O}?_=MC3L8S5;X!(DCc_<8?o z%DejKeOGH74BoZ3L+K2d_o6yJ6LlLg=c%B*(8lLDx^3{f)8Srd!`*qjg>)+ew=yuB z3~c;`_|&7m)i@QORNv&hOZC$8!K#= z+{xBfSuaYqmGT-!#6HQ5eyA7a=0a%CLDcU`4pstQQSPj=oqwmb41OA%{!R;j%hiIu zg3b<@-+1(YT9;_|>`Qwy$|^Gb{08$qn7`G8Lrh{%kufO$%p=Qfn0~EqB}6fyDKg_|9=EniEsFXHlkq>L`ZaQ0AU!v+k&t$n3?B&?sDu=~7hO z6Juoz8b5WUN88#x$Nn-h{*R$w{xvR~W#K%Iw$fpL$`i_LpUA(NgVM2K4&EeoBzxX! z?97Ls8-peNwo-HZ77UaF;m7lM*IA|hv(bq#+WJg-!?VEW+P;$*3)r_S_w>7Keufwc zcC3Q2|1$XWYfEEfUdnP$-PY#~b*sp%!{gA=9B7=}Uu3r1bw}Na)*bCE4cE@dJj*|& zEi;aB-Zj=$U0G|$=2~Nxf6YnjcTJr`+h^9m5wvR#wBG2SW%;L0Cv#66(QqD0-%0X4 z$-71_YNc6+e4lVHYVAp~&A)4mZY}17omdN8i)EbNMH50do0)morCisyOL>Y0`YGcn zApdu!#qs|a15ga}^i7}ihi?`Kz~4lk=#Di>+&T;4_as=~fld-jeUsp}wds2NTPuUb z?UljO_R97W|FML_)H4ote3WskIPmj)VKdMB8+m^23i$0jzj7`zK6~&JhZtX9@a-kf zx0mut;wOs8{7%8o63gK9okDLrw3QIgc)o+Kf$xg_eMmCtKJXu4>iG8oBuV|Rqs%_? z^I(#-KaINjsY|l`7XLf+t%Ki<%re_(&lspnl5fdR(MFxWqm)0}MjeuDY?*P(hIyb* zvc}CmJ2q5DXfJB~8y)H?NA#oEe?*x&Wu$mGW*;x^;`gKcSgf;+eI)6IT6n6B`eFQ8 z_8Q8}oyUV@A4$5Qw)=S*O|)U$8V}o=NVun=D4&`OwvnV0YUNL^Kbv!wwI<6gcDKHW zCXSuV^1R88)0P|K!1>ps667=FSoUp=#=%bZ7h!*~&i?CPox6VBxy^;ZTZ{N%{$Aj} TmT(~h4^{&2tYT90A+!E3c*33; literal 3626 zcmb`Je@s(X6vrR`EK3%b%orErlDW({vnABqA zcfaS{d+xbU5JKp0*;0YOg+;Fl!eT)XRuapIwFLL`=imZCSon$`se`_<%@MB=M~KG+ z=EW^FL`w|Bo>*ktlaS^(fut!95`iG5u=SZ8nfDHO#GaTlH1-XG^;vsjUb^gWTVz0+ z^=WR1wv9-2oeR=_;fL0H7rNWqAzGtO(D;`~cX(RcN0w2v24Y8)6t`cS^_ghs`_ho? z{0ka~1Dgo8TfAP$r*ua?>$_V+kZ!-(TvEJ7O2f;Y#tezt$&R4 zLI}=-y@Z!grf*h3>}DUL{km4R>ya_I5Ag#{h_&?+HpKS!;$x3LC#CqUQ8&nM?X))Q zXAy2?`YL4FbC5CgJu(M&Q|>1st8XXLZ|5MgwgjP$m_2Vt0(J z&Gu7bOlkbGzGm2sh?X`){7w69Y$1#@P@7DF{ZE=4%T0NDS)iH`tiPSKpDNW)zmtn( zw;4$f>k)4$LBc>eBAaTZeCM2(iD+sHlj!qd z2GjRJ>f_Qes(+mnzdA^NH?^NB(^o-%Gmg$c8MNMq&`vm@9Ut;*&$xSD)PKH{wBCEC z4P9%NQ;n2s59ffMn8*5)5AAg4-93gBXBDX`A7S& zH-|%S3Wd%T79fk-e&l`{!?lve8_epXhE{d3Hn$Cg!t=-4D(t$cK~7f&4s?t7wr3ZP z*!SRQ-+tr|e1|hbc__J`k3S!rMy<0PHy&R`v#aJv?`Y?2{avK5sQz%=Us()jcNuZV z*$>auD4cEw>;t`+m>h?f?%VFJZj8D|Y1e_SjxG%J4{-AkFtT2+ZZS5UScS~%;dp!V>)7zi`w(xwSd*FS;Lml=f6hn#jq)2is4nkp+aTrV?)F6N z>DY#SU0IZ;*?Hu%tSj4edd~kYNHMFvS&5}#3-M;mBCOCZL3&;2obdG?qZ>rD|zC|Lu|sny76pn2xl|6sk~Hs{X9{8iBW zwiwgQt+@hi`FYMEhX2W%*fuTU;X$ z!2-J5#|61%-nAVyYS$qf#$W+0o4-HhxFe+e_pkB;0ur>7RxBIFJ$x$nlGikpj;ptK z=Bq{|5{X12kw_#Gi9{liNF)-8=;Lk5bvPW}(8~xiEQ+ES!UJ9=?b9@!KrWnWmuUtM z_`0hhes4WVk~4U~`4FQsJ)(oTfdk6294-hr#CZh`1_Nhjn)dw>j&U9ko#hOUpnadh zF-`+=Am@gUa4T!q736&3lWSQ!le9}Y*KsLpXBFg9&JCaCOxDfV_kPY!#IrDZK$L0mbjrNQ68fcz(V$rqEffSl(vOL(NN*R-Dk_K3<_vVvHGq=PzF zuj;^V%G%;C=1eEZf=h}D{ zqZ(#cU?3%Hwxp%x+_2w__B(|=BC?jKpa~eX37L*dy+4CJBC=)&Brv>MAB$3M0s{$I zvjXDEnV3OGpFM=c~F=8P_MPDtpcKw<;poX z*}m$D=3rneYvX{Foaq23;9>k_lsn8|DQlYw%D`i$Dd#%eo=Al=fg#2Lac{4b20tjU zj!;izENi2H1mvv9VGrx82Ofr5X7UgAO*U3?rn3kJ%d1hS z>5hOm1f?;W1~D088Tx^dthEF3wW`zyCbHHFNJ!4Rgtn};0^)YgyGSMLPC6ysBRSX6 zlC@3+W#}hbvhED1PjarKBkQdJ^-#`rI%T~zpgzjEPM54(CEa5=*V!uTZ55Q^X|Pq+ ztpW8}&ULoQx-p<9Am=)*vTmI25SZ3b6Ju}S?>AT{+=_3T26-fDAvxzoQHQ%k~savfcs0EpI7Ha|u2xSuX=3F|pSa`|f?(5bB9mvQDZlRLR-;ZY4baT#XKr zvQ`zOlH!CDtoLa{s3%&=dLXo9e \ No newline at end of file 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 From 506f0bcaaf29eb8200b3a584e4c51e3d8840a6de Mon Sep 17 00:00:00 2001 From: Bart van den Aardweg Date: Wed, 24 Mar 2021 16:18:03 +0100 Subject: [PATCH 2/2] feat(docs): api reference --- jest.config.js | 1 + src/bridge/context.tsx | 2 +- src/client/index.ts | 12 +++ src/image/Image.tsx | 17 +++-- src/image/getImageProps.ts | 22 +++++- src/next/previewHandlers.ts | 6 ++ src/utils/getExcerpt.ts | 25 ++++++- src/utils/getPlainText.ts | 22 +++--- website/docs/api/Image.md | 86 +++++++++++++++++++++ website/docs/api/StoryProvider.md | 41 ++++++++++ website/docs/api/getClient.md | 87 ++++++++++++++++++++++ website/docs/api/getExcerpt.md | 67 +++++++++++++++++ website/docs/api/getImageProps.md | 59 +++++++++++++++ website/docs/api/getPlainText.md | 58 +++++++++++++++ website/docs/api/getStaticPropsWithSdk.md | 79 ++++++++++++++++++++ website/docs/api/nextPreviewHandlers.md | 54 ++++++++++++++ website/docs/api/useStory.md | 43 +++++++++++ website/docs/api/withStory.md | 42 +++++++++++ website/docs/getting-started.md | 29 +++++++- website/docusaurus.config.js | 13 +++- website/sidebars.js | 31 ++++++++ website/static/img/logo-dark.png | Bin 0 -> 1826 bytes website/static/img/preview-mode.png | Bin 0 -> 28848 bytes 23 files changed, 771 insertions(+), 25 deletions(-) create mode 100644 website/docs/api/Image.md create mode 100644 website/docs/api/StoryProvider.md create mode 100644 website/docs/api/getClient.md create mode 100644 website/docs/api/getExcerpt.md create mode 100644 website/docs/api/getImageProps.md create mode 100644 website/docs/api/getPlainText.md create mode 100644 website/docs/api/getStaticPropsWithSdk.md create mode 100644 website/docs/api/nextPreviewHandlers.md create mode 100644 website/docs/api/useStory.md create mode 100644 website/docs/api/withStory.md create mode 100644 website/static/img/logo-dark.png create mode 100644 website/static/img/preview-mode.png diff --git a/jest.config.js b/jest.config.js index df732f4..911f0b7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -20,4 +20,5 @@ module.exports = { '@testing-library/jest-dom/extend-expect', './jest.setup.js', ], + collectCoverageFrom: ['src/**/*.{ts,tsx}'], }; diff --git a/src/bridge/context.tsx b/src/bridge/context.tsx index 960886c..1dfce61 100644 --- a/src/bridge/context.tsx +++ b/src/bridge/context.tsx @@ -18,7 +18,7 @@ interface ContextProps { interface ProviderProps { children: ReactNode; - /* + /** * Relations that need to be resolved in preview mode, for example: * `['Post.author']` */ diff --git a/src/client/index.ts b/src/client/index.ts index df3ac6b..5990b82 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -3,8 +3,20 @@ import { GraphQLClient } from 'graphql-request'; import type { GetStaticPropsResult, GetStaticPropsContext } from 'next'; 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'; } 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 099ebf0..a30cbc9 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -16,6 +16,7 @@ module.exports = { logo: { alt: 'Story of AMS Logo', 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/static/img/logo-dark.png b/website/static/img/logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f21d68a25482a245abe6519061baee3e22aa511b GIT binary patch literal 1826 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 z