├── .eslintrc.json ├── .github ├── issue_template.md ├── pull.yml └── pull_request_template.md ├── .gitignore ├── .sanity └── runtime │ ├── app.js │ └── index.html ├── .storybook ├── main.ts ├── manager-head.html ├── preview-head.html └── preview.tsx ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── blocks ├── block0 │ ├── Block0.tsx │ ├── [Code].json │ ├── block0.query.tsx │ ├── block0.schema.tsx │ ├── block0.stories.tsx │ └── block0.test.tsx ├── block1 │ ├── Block1.tsx │ ├── [Text and Media].json │ ├── block1.options.ts │ ├── block1.query.tsx │ ├── block1.schema.tsx │ ├── block1.stories.tsx │ └── block1.test.tsx ├── block10 │ ├── Block10.tsx │ ├── [FAQ Accordions].json │ ├── block10.query.tsx │ ├── block10.schema.tsx │ ├── block10.stories.tsx │ └── block10.test.tsx ├── block12 │ ├── Block12.tsx │ ├── [Resources grid].json │ ├── block12.query.tsx │ ├── block12.schema.tsx │ ├── block12.stories.tsx │ └── block12.test.tsx ├── block13 │ ├── Block13.tsx │ ├── [Related resources].json │ ├── block13.query.tsx │ ├── block13.schema.tsx │ ├── block13.stories.tsx │ └── block13.test.tsx ├── block14 │ ├── Block14.tsx │ ├── [Article].json │ ├── block14.query.tsx │ ├── block14.schema.tsx │ ├── block14.stories.tsx │ └── block14.test.tsx ├── block15 │ ├── Block15.tsx │ ├── [Split text].json │ ├── block15.query.tsx │ ├── block15.schema.tsx │ ├── block15.stories.tsx │ └── block15.test.tsx ├── block16 │ ├── Block16.tsx │ ├── [Logo grid].json │ ├── block16.query.tsx │ ├── block16.schema.tsx │ ├── block16.stories.tsx │ └── block16.test.tsx ├── block17 │ ├── Block17.tsx │ ├── [Testimonial poster].json │ ├── block17.query.tsx │ ├── block17.schema.tsx │ ├── block17.stories.tsx │ └── block17.test.tsx ├── block18 │ ├── Block18.tsx │ ├── [Card grid].json │ ├── block18.classes.ts │ ├── block18.options.ts │ ├── block18.query.tsx │ ├── block18.schema.tsx │ ├── block18.stories.tsx │ └── block18.test.tsx ├── block2 │ ├── Block2.tsx │ ├── [Feature section with icons].json │ ├── block2.query.tsx │ ├── block2.schema.tsx │ ├── block2.stories.tsx │ └── block2.test.tsx ├── block3 │ ├── Block3.tsx │ ├── [Pricing plans].json │ ├── block3.classes.ts │ ├── block3.query.tsx │ ├── block3.schema.tsx │ ├── block3.stories.tsx │ └── block3.test.tsx ├── block4 │ ├── Block4.tsx │ ├── [Default content].json │ ├── block4.query.tsx │ ├── block4.schema.tsx │ ├── block4.stories.tsx │ └── block4.test.tsx ├── block5 │ ├── Block5.tsx │ ├── [Pricing comparison tables].json │ ├── block5.query.tsx │ ├── block5.schema.tsx │ ├── block5.stories.tsx │ └── block5.test.tsx └── block7 │ ├── Block7.tsx │ ├── [Item shelf].json │ ├── block7.query.tsx │ ├── block7.schema.tsx │ ├── block7.stories.tsx │ └── block7.test.tsx ├── cli ├── config │ ├── build.test.ts │ ├── build.ts │ └── format-theme.ts ├── create-block │ ├── create-info.test.ts │ ├── create-info.ts │ ├── create-query.test.ts │ ├── create-query.ts │ ├── create-react-component.test.ts │ ├── create-react-component.ts │ ├── create-schema.test.ts │ ├── create-schema.ts │ ├── create-story.test.ts │ ├── create-story.ts │ ├── create-tests.test.ts │ ├── create-tests.ts │ ├── format-name.test.ts │ ├── format-name.ts │ ├── index.ts │ ├── inject-builder.test.ts │ ├── inject-builder.ts │ ├── inject-page-query.test.ts │ ├── inject-page-query.ts │ ├── inject-schema.test.ts │ ├── inject-schema.ts │ ├── inject-types.test.ts │ ├── inject-types.ts │ └── templates │ │ ├── builder.ts │ │ ├── page-query.ts │ │ ├── query.ts │ │ ├── react-component.ts │ │ ├── schema.ts │ │ ├── story.ts │ │ └── tests.ts ├── create-page │ ├── create-schema.test.ts │ ├── create-schema.ts │ ├── format-name.test.ts │ ├── format-name.ts │ ├── index.ts │ ├── inject-desk-structure.test.ts │ ├── inject-desk-structure.ts │ ├── inject-schema.test.ts │ ├── inject-schema.ts │ ├── inject-types.test.ts │ ├── inject-types.ts │ └── templates │ │ ├── article-page.ts │ │ ├── content-page.ts │ │ ├── singleton-page.ts │ │ ├── structure-collection.ts │ │ ├── structure-document-list.ts │ │ └── structure-singleton.ts ├── setup │ ├── delete-project.sh │ ├── delete-tenant.sh │ ├── setup-env.sh │ ├── setup.sh │ └── tenant.sh └── utils │ ├── inject-line.test.ts │ ├── inject-line.ts │ ├── is-write.ts │ ├── pascal-case.test.ts │ ├── pascal-case.ts │ ├── prettier-file.ts │ ├── render-field.test.ts │ ├── render-field.ts │ ├── sort-lines.test.ts │ └── sort-lines.ts ├── components ├── accordion │ ├── Accordion.tsx │ └── accordion.stories.tsx ├── block │ ├── Background.tsx │ ├── Bleed.tsx │ ├── Spacing.tsx │ ├── Width.tsx │ ├── Wrapper.tsx │ ├── background.options.ts │ ├── background.stories.tsx │ ├── bleed.options.ts │ ├── bleed.stories.tsx │ ├── block.options.ts │ ├── block.preset.theme.tsx │ ├── block.preset.tsx │ ├── block.schema.ts │ ├── spacing.options.ts │ ├── spacing.stories.tsx │ ├── width.options.ts │ ├── width.stories.tsx │ └── wrapper.stories.tsx ├── breadcrumb │ ├── Breadcrumb.tsx │ ├── breadcrumb.query.ts │ ├── breadcrumb.stories.tsx │ └── breadcrumb.test.tsx ├── buttons │ ├── Button.tsx │ ├── ButtonGroup.tsx │ ├── Link.tsx │ ├── TextTransform.tsx │ ├── button.options.ts │ ├── button.preset.tsx │ ├── button.query.ts │ ├── button.schema.tsx │ ├── button.stories.tsx │ ├── button.test.tsx │ ├── buttongroup.options.ts │ ├── buttongroup.schema.tsx │ ├── buttongroup.stories.tsx │ ├── link.schema.tsx │ └── link.test.tsx ├── cards │ ├── ComposableCard.tsx │ ├── ImageCard.tsx │ ├── ResourceCard.tsx │ ├── TestimonialCard.tsx │ ├── composablecard.options.ts │ ├── composablecard.schema.tsx │ ├── composablecard.stories.tsx │ ├── composablecard.test.tsx │ ├── imagecard.schema.tsx │ ├── imagecard.stories.tsx │ ├── imagecard.test.tsx │ ├── resourcecard.stories.tsx │ ├── testimonialcard.schema.tsx │ ├── testimonialcard.stories.tsx │ └── testimonialcard.test.tsx ├── date │ ├── DateDisplay.test.tsx │ ├── DateDisplay.tsx │ └── datedisplay.stories.tsx ├── decorations │ ├── CSSDecoration.tsx │ ├── Decoration.tsx │ ├── Decorations.tsx │ ├── cssdecoration.schema.tsx │ ├── decoration.options.ts │ ├── decoration.preset.tsx │ ├── decoration.query.ts │ ├── decoration.schema.tsx │ └── decoration.stories.tsx ├── faq │ ├── FAQ.tsx │ ├── faq.query.ts │ ├── faq.schema.tsx │ └── faq.stories.tsx ├── gradient │ ├── Gradient.stories.tsx │ ├── Gradient.tsx │ └── GradientOptions.ts ├── highlight │ ├── Highlight.tsx │ ├── highlight.schema.tsx │ └── highlight.stories.tsx ├── images │ ├── FigCaption.tsx │ ├── IconLoader.tsx │ ├── ResponsiveImage.tsx │ ├── SimpleImage.tsx │ ├── iconloader.stories.tsx │ ├── image.options.ts │ ├── image.query.ts │ ├── image.schema.tsx │ └── simpleimage.stories.tsx ├── lightbox │ ├── Fanbybox.stories.tsx │ └── Fancybox.tsx ├── loaders │ ├── LoadingAnimation.tsx │ ├── Spinner.tsx │ ├── loadinganimation.stories.tsx │ └── spinner.stories.tsx ├── meta │ ├── ScriptJsonLd.tsx │ └── Seo.tsx ├── pagelock │ └── PageLock.tsx ├── portabletext │ ├── PortableText.stories.tsx │ ├── PortableText.tsx │ ├── portabletext.query.ts │ ├── portabletext.test.tsx │ ├── portabletextbasic.schema.tsx │ ├── portabletextfull.schema.tsx │ ├── portabletextplain.schema.ts │ └── portabletextsimple.schema.tsx ├── previewmode │ ├── BlockPreviewTools.tsx │ ├── ComponentPreviewTools.tsx │ └── ScreenCapture.tsx ├── script │ ├── Script.tsx │ ├── script.options.ts │ └── script.schema.tsx ├── slider │ ├── MobileScroller.tsx │ ├── Slider.tsx │ ├── mobilescroller.stories.tsx │ ├── slider.options.ts │ └── slider.stories.tsx ├── social │ ├── SocialShare.tsx │ └── socialshare.stories.tsx ├── table │ ├── PricingTable.stories.tsx │ ├── PricingTable.tsx │ ├── Table.tsx │ ├── pricingtable.test.tsx │ └── table.stories.tsx ├── tags │ ├── Tag.stories.tsx │ └── Tag.tsx ├── testimonials │ ├── TestimonialPoster.tsx │ ├── Testimonials.tsx │ ├── testimonialposter.stories.tsx │ ├── testimonialposter.test.tsx │ ├── testimonials.query.ts │ ├── testimonials.schema.tsx │ ├── testimonials.stories.tsx │ └── testimonials.test.tsx ├── text │ ├── Text.tsx │ ├── text.options.ts │ ├── text.preset.theme.tsx │ ├── text.schema.ts │ └── text.stories.tsx ├── title │ ├── Title.tsx │ ├── title.options.ts │ ├── title.preset.theme.tsx │ ├── title.schema.ts │ └── title.stories.tsx └── video │ ├── MuxPlayer.tsx │ ├── Video.tsx │ ├── VideoPlayer.tsx │ ├── VimeoPlayer.tsx │ ├── YoutubePlayer.tsx │ ├── video.query.ts │ ├── video.schema.tsx │ └── video.stories.tsx ├── context ├── PageContext.ts └── SiteContext.ts ├── docs ├── Creating Blocks.md ├── Creating Pages.md ├── Engine.md └── Getting Started.md ├── env.d.ts ├── helpers ├── sanity │ ├── config.ts │ ├── image-url.ts │ └── server.ts ├── sitemap │ ├── getFlatBreadcrumb.test.ts │ ├── getFlatBreadcrumb.ts │ ├── getPathForId.test.ts │ ├── getPathForId.ts │ ├── getURLForPath.test.ts │ ├── getURLForPath.ts │ ├── isInternalLink.test.ts │ └── isInternalLink.ts └── utils │ ├── array.ts │ ├── color.ts │ ├── date.ts │ ├── debounce.ts │ ├── number.ts │ ├── object.test.ts │ ├── object.ts │ ├── portabletext.test.ts │ ├── portabletext.ts │ ├── string.test.ts │ └── string.ts ├── hooks ├── useBreakpoint.ts ├── useDebounce.ts ├── useHover.ts ├── useInView.ts ├── useInviewAnimation.ts ├── useSize.ts ├── useTranslation.ts └── useWindowSize.ts ├── jest.config.js ├── jest.utils.tsx ├── languages.ts ├── layout ├── footer │ ├── Footer.Breadcrumb.tsx │ ├── Footer.Logo.tsx │ ├── Footer.Menu.tsx │ ├── Footer.tsx │ ├── footer.query.ts │ ├── footer.schema.tsx │ └── footer.stories.tsx ├── navigation │ ├── LanguageSwitch.tsx │ ├── MobileNav.tsx │ ├── Navigation.Breadcrumb.tsx │ ├── Navigation.tsx │ ├── TopNav.Banner.tsx │ ├── TopNav.Buttons.tsx │ ├── TopNav.Logo.tsx │ ├── TopNav.Menu.tsx │ ├── TopNav.tsx │ ├── mobilenav.stories.tsx │ ├── navigation.options.ts │ ├── navigation.query.ts │ ├── navigation.schema.tsx │ ├── navigation.stories.tsx │ └── topnav.stories.tsx ├── pagebuilder │ ├── BlockBuilder.tsx │ ├── BlockErrorBoundary.tsx │ ├── BlockLoadInView.tsx │ └── ErrorBoundary.tsx └── pages │ └── Page.tsx ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── 404.tsx ├── [[...slug]].tsx ├── _app.tsx ├── _document.tsx ├── api │ ├── opengraph-image.tsx │ ├── page-password │ │ └── login.ts │ ├── preview │ │ ├── open-preview.ts │ │ └── open-production-url.ts │ ├── revalidate.ts │ ├── sanity-client.ts │ ├── search │ │ ├── export.query.ts │ │ ├── export.test.ts │ │ └── export.ts │ ├── sitemap-xml.ts │ └── styles │ │ └── engine.ts ├── sitemap.tsx └── turbopreview.tsx ├── postcss.config.js ├── production-1720544495.tar.gz ├── public ├── downloads │ └── .gitkeep ├── fonts │ ├── Inter-Black.ttf │ ├── Inter-Bold.ttf │ ├── Inter-ExtraBold.ttf │ ├── Inter-ExtraLight.ttf │ ├── Inter-Light.ttf │ ├── Inter-Medium.ttf │ ├── Inter-Regular.ttf │ ├── Inter-SemiBold.ttf │ └── Inter-Thin.ttf ├── robots.txt └── storybook │ ├── amazon-2500x752.svg │ ├── demo-icon-30x30.svg │ ├── demoimage1-880x528.jpg │ ├── demoimage2-1296x893.jpg │ ├── demoimage3-800x1200.jpg │ ├── demoimage4-970x546.webp │ ├── google-2500x816.svg │ └── pricing.csv ├── queries ├── config.query.ts ├── page.query.ts ├── sitemap.query.ts └── translations.query.ts ├── sanity.cli.ts ├── sanity.config.tsx ├── stories ├── Colors.stories.mdx ├── Typography.stories.mdx └── content.tsx ├── studio ├── components │ ├── ArrayItemPreviewHighlight.tsx │ ├── BlockJSONEditor.tsx │ ├── BlockSelectDialog.tsx │ ├── BlockSlugField.tsx │ ├── CaptureScreenshot │ │ └── CaptureScreenshot.tsx │ ├── CharacterCounter.tsx │ ├── ColorInput.tsx │ ├── CopyPaste │ │ ├── CopyPaste.tsx │ │ └── match-schema.ts │ ├── Decorations │ │ ├── DecorationLocationSelect.tsx │ │ └── DecorationPositionInput.tsx │ ├── IconPicker.tsx │ ├── Logo.tsx │ ├── PageBuilder.tsx │ ├── PagePasswordComponent.tsx │ ├── Presets │ │ ├── Preset.tsx │ │ └── PresetUsage.tsx │ ├── Preview │ │ ├── DocumentPreview.tsx │ │ └── actions.ts │ ├── PublishAction.tsx │ ├── StylesPanel │ │ ├── ColorPicker.tsx │ │ ├── Select.tsx │ │ ├── Space.tsx │ │ ├── StylesPanel.tsx │ │ ├── TextInput.tsx │ │ ├── ThemeImportSelect.tsx │ │ ├── Toggle.tsx │ │ ├── stylespanel.module.css │ │ ├── typings.d.ts │ │ └── utils.ts │ ├── SwapSchema.tsx │ ├── Theme │ │ ├── CodeEditor.tsx │ │ ├── ThemeColors.tsx │ │ ├── ThemeFontFamily.tsx │ │ ├── ThemeFontSize.tsx │ │ ├── ThemeFontWeight.tsx │ │ └── ThemeIcons.tsx │ ├── UnsetObjectButton.tsx │ ├── Warning.tsx │ └── productionURLPane.tsx ├── schemas │ ├── documents │ │ ├── config.cms.tsx │ │ ├── config.deployment.tsx │ │ ├── config.general.tsx │ │ ├── config.icons.tsx │ │ ├── config.integrations.tsx │ │ ├── config.seo.tsx │ │ ├── config.social.tsx │ │ ├── config.theme.tsx │ │ ├── config.translations.tsx │ │ ├── page-fields.ts │ │ ├── page.blog.tsx │ │ ├── page.blogs.tsx │ │ ├── page.casestudies.tsx │ │ ├── page.casestudy.tsx │ │ ├── page.content.tsx │ │ ├── page.event.tsx │ │ ├── page.events.tsx │ │ ├── page.guide.tsx │ │ ├── page.guides.tsx │ │ ├── page.home.tsx │ │ ├── page.landing.tsx │ │ ├── page.mediacoverage.tsx │ │ ├── page.mediacoveragearticle.tsx │ │ ├── page.news.tsx │ │ ├── page.newsarticle.tsx │ │ ├── page.newsroom.tsx │ │ ├── page.notfound.tsx │ │ ├── page.podcast.tsx │ │ ├── page.podcasts.tsx │ │ ├── page.pressrelease.tsx │ │ ├── page.pressreleases.tsx │ │ ├── page.pricing.tsx │ │ ├── page.resources.tsx │ │ ├── page.search.tsx │ │ ├── page.sitemap.tsx │ │ ├── page.tag.tsx │ │ ├── page.tags.tsx │ │ ├── page.tool.tsx │ │ ├── page.tools.tsx │ │ ├── page.video.tsx │ │ ├── page.videos.tsx │ │ ├── password.tsx │ │ ├── person.tsx │ │ ├── pricing.feature.tsx │ │ ├── pricing.plan.tsx │ │ ├── redirect.tsx │ │ └── studio.divider.tsx │ ├── index.ts │ └── objects │ │ ├── copypaste.tsx │ │ ├── preset.tsx │ │ ├── styles.tsx │ │ ├── swapschema.tsx │ │ └── tools.tsx ├── structure.tsx ├── utils │ ├── datetime.ts │ ├── desk │ │ ├── clean-desk.ts │ │ ├── documentList.ts │ │ ├── get-icon-for-schema.ts │ │ ├── get-structure-path.test.ts │ │ ├── get-structure-path.ts │ │ ├── group.ts │ │ ├── isPathUnique.ts │ │ ├── list.ts │ │ └── singleton.ts │ ├── document │ │ ├── getDocumentIcon.tsx │ │ └── getDocumentTitle.ts │ ├── fields │ │ └── optionsToList.ts │ ├── language │ │ ├── field-translation.ts │ │ ├── get-current-languages.ts │ │ └── reference-filter-current-language.ts │ ├── portableText │ │ └── portableTextToText.tsx │ ├── schemas │ │ ├── getLinkableTypes.ts │ │ └── getSchemas.ts │ └── slugify.ts └── views │ ├── EmbedIframe.tsx │ ├── PreviewIframe.tsx │ ├── SeoPane.tsx │ └── Sitemap.tsx ├── styles ├── radix.css ├── styles.css └── swiper.css ├── tailwind.config.js ├── test ├── fixtures │ ├── breadcrumb.ts │ └── sitemap.ts └── styleMock.js ├── theme.ts ├── tsconfig.json ├── types.sanity.ts ├── types.ts ├── vercel.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | 27 | ## Your Environment 28 | 29 | * Version used: 30 | * Browser Name and version: 31 | * Operating System and version (desktop or mobile): 32 | * Link to your project: 33 | -------------------------------------------------------------------------------- /.github/pull.yml: -------------------------------------------------------------------------------- 1 | #https://github.com/wei/pull#trigger-manually 2 | 3 | version: "1" 4 | rules: 5 | - base: main 6 | upstream: Mawla:main 7 | mergeMethod: hardreset 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | node_modules 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # vercel 28 | .vercel 29 | 30 | # env 31 | .env 32 | .env.* 33 | /studio/.env* 34 | 35 | # idea config 36 | .idea 37 | storybook-static/ 38 | build-storybook.log 39 | 40 | # TypeScript incremental compilation cache 41 | *.tsbuildinfo 42 | 43 | public/cms 44 | *.tar.gz 45 | !development-demo.tar.gz 46 | 47 | tmp/ 48 | 49 | # Vim 50 | Session.vim 51 | 52 | production-* 53 | development-* 54 | 55 | engine.config.js 56 | public/engine.styles.css 57 | locales.js 58 | public/downloads/* 59 | !public/downloads/.gitkeep -------------------------------------------------------------------------------- /.sanity/runtime/app.js: -------------------------------------------------------------------------------- 1 | 2 | // This file is auto-generated on 'sanity dev' 3 | // Modifications to this file is automatically discarded 4 | import {renderStudio} from "sanity" 5 | import studioConfig from "../../sanity.config.tsx" 6 | 7 | renderStudio( 8 | document.getElementById("sanity"), 9 | studioConfig, 10 | {reactStrictMode: false, basePath: "/"} 11 | ) 12 | -------------------------------------------------------------------------------- /.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mawla/Agency-Starter-Framework/523bb98b20cc317c81ac61131ef32a65c2c40f5d/.storybook/preview-head.html -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true, 4 | "prettier.trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /blocks/block0/[Code].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Code", 3 | "description": "HTML code block", 4 | "name": "Block 0", 5 | "schemaName": "block.block0", 6 | "date": "2023-07-20T13:21:09.729Z", 7 | "fields": ["title"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block0/block0.query.tsx: -------------------------------------------------------------------------------- 1 | import { LanguageType } from "../../languages"; 2 | import groq from "groq"; 3 | 4 | export const getBlock0Query = (language: LanguageType) => groq` 5 | _type == "block.block0" => { 6 | _key, 7 | _type, 8 | bodyHTML, 9 | headHTML, 10 | baseURL, 11 | tailwindConfig 12 | }`; 13 | -------------------------------------------------------------------------------- /blocks/block0/block0.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Block0 } from "./Block0"; 2 | import { Meta } from "@storybook/react"; 3 | import React from "react"; 4 | 5 | export default { 6 | component: Block0, 7 | title: "Blocks/0. Code", 8 | } as Meta; 9 | 10 | export const Default = () => ( 11 | This should show a tailwind styled paragraph

`} 13 | /> 14 | ); 15 | 16 | export const WithoutWebsiteStyles = () => ( 17 | This should not include a tailwind config from the site, but be an object.

`} 19 | theme={{ 20 | code: { 21 | removeWebsiteStyles: true, 22 | }, 23 | }} 24 | /> 25 | ); 26 | 27 | export const WithoutTailwindCompiler = () => ( 28 | This should render as an unstyled paragraph and be undefined.

`} 30 | theme={{ 31 | code: { 32 | removeTailwindCompiler: true, 33 | }, 34 | }} 35 | /> 36 | ); 37 | -------------------------------------------------------------------------------- /blocks/block0/block0.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import Block0 from "./Block0"; 3 | import "@testing-library/jest-dom"; 4 | 5 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 6 | 7 | describe("Block0", () => { 8 | it("renders title", async () => { 9 | await act(() => { 10 | render(); 11 | }); 12 | expect(screen.getByTitle("iframe")).toBeInTheDocument(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /blocks/block1/[Text and Media].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Text and Media", 3 | "description": "Text, image or video and feature list left or right", 4 | "name": "Block 1", 5 | "schemaName": "block.block1", 6 | "date": "2023-06-28T07:47:00.559Z", 7 | "fields": ["title", "intro", "image", "buttons"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block1/block1.options.ts: -------------------------------------------------------------------------------- 1 | import { pick } from "../../helpers/utils/object"; 2 | import { SIZES } from "../../types"; 3 | 4 | export const MEDIA_POSITION_OPTIONS = { 5 | left: "Left", 6 | right: "Right", 7 | }; 8 | export type mediaPositionType = keyof typeof MEDIA_POSITION_OPTIONS; 9 | 10 | export const LAYOUT_COLUMN_OPTIONS = { 11 | "1/2": "50/50", 12 | "1/4": "1/4", 13 | "3/4": "3/4", 14 | "1/3": "1/3", 15 | "2/3": "2/3", 16 | "5/7": "5/7", 17 | "2/7": "7/5", 18 | "5/8": "5/8", 19 | "3/8": "3/8", 20 | }; 21 | export type layoutColumnType = keyof typeof LAYOUT_COLUMN_OPTIONS; 22 | 23 | export const GAP_OPTIONS = pick( 24 | SIZES, 25 | "none", 26 | "2xs", 27 | "xs", 28 | "sm", 29 | "md", 30 | "lg", 31 | "xl", 32 | "2xl", 33 | ); 34 | export type GapType = keyof typeof GAP_OPTIONS; 35 | 36 | // need all breakpoints defined in these classes to calculate the slider gap 37 | export const gapClasses: Record = { 38 | none: "lg:gap-x-0", 39 | "2xs": "lg:gap-x-4 xl:gap-x-4 2xl:gap-x-4", 40 | xs: "lg:gap-x-6 xl:gap-x-6 2xl:gap-x-6", 41 | sm: "lg:gap-x-8 xl:gap-x-8 2xl:gap-x-8", 42 | md: "lg:gap-x-10 xl:gap-x-10 2xl:gap-x-10", 43 | lg: "lg:gap-x-16 xl:gap-x-20 2xl:gap-x-22", 44 | xl: "lg:gap-x-20 xl:gap-x-24 2xl:gap-x-30", 45 | "2xl": "lg:gap-x-24 xl:gap-x-32 2xl:gap-x-40", 46 | }; 47 | -------------------------------------------------------------------------------- /blocks/block1/block1.query.tsx: -------------------------------------------------------------------------------- 1 | import { buttonQuery } from "../../components/buttons/button.query"; 2 | import { getImageQuery, imageQuery } from "../../components/images/image.query"; 3 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 4 | import { videoQuery } from "../../components/video/video.query"; 5 | import { LanguageType } from "../../languages"; 6 | import groq from "groq"; 7 | 8 | export const getBlock1Query = (language: LanguageType) => groq` 9 | _type == "block.block1" => { 10 | _key, 11 | _type, 12 | title, 13 | intro[] ${richTextQuery}, 14 | body[] ${richTextQuery}, 15 | footer[] ${richTextQuery}, 16 | "image": ${imageQuery}, 17 | "mobileImage": ${getImageQuery("mobileImage")}, 18 | "video": ${videoQuery}, 19 | script -> { 20 | title, 21 | items[] 22 | }, 23 | buttons[] ${buttonQuery}, 24 | }`; 25 | -------------------------------------------------------------------------------- /blocks/block10/[FAQ Accordions].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "FAQ Accordions", 3 | "description": "Can be used to expand the answer to each question based on user interaction.", 4 | "name": "Block 10", 5 | "schemaName": "block.block10", 6 | "date": "2023-07-05T08:57:36.968Z", 7 | "fields": [ 8 | "title", 9 | "intro", 10 | "buttons" 11 | ] 12 | } -------------------------------------------------------------------------------- /blocks/block10/block10.query.tsx: -------------------------------------------------------------------------------- 1 | import { buttonQuery } from "../../components/buttons/button.query"; 2 | import { getFaqQuery } from "../../components/faq/faq.query"; 3 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 4 | import { LanguageType } from "../../languages"; 5 | import groq from "groq"; 6 | 7 | export const getBlock10Query = (language: LanguageType) => groq` 8 | _type == "block.block10" => { 9 | _key, 10 | _type, 11 | title, 12 | intro[] ${richTextQuery}, 13 | buttons[] ${buttonQuery}, 14 | faq[] ${getFaqQuery(language)} 15 | }`; 16 | -------------------------------------------------------------------------------- /blocks/block10/block10.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import Block10 from "./Block10"; 3 | import "@testing-library/jest-dom"; 4 | import { TextEncoder, TextDecoder } from "util"; 5 | 6 | Object.assign(global, { TextDecoder, TextEncoder }); 7 | 8 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 9 | 10 | describe("Block10", () => { 11 | it("renders title", async () => { 12 | await act(() => { 13 | render(); 14 | }); 15 | expect(screen.getByText("Hello", { selector: "h2" })).toBeInTheDocument(); 16 | }); 17 | }); 18 | 19 | describe("Block10", () => { 20 | it("renders intro", async () => { 21 | await act(() => { 22 | render(Hello

} />); 23 | }); 24 | expect(screen.getByText("Hello", { selector: "p" })).toBeInTheDocument(); 25 | }); 26 | }); 27 | 28 | describe("Block10", () => { 29 | it("renders faq", async () => { 30 | await act(() => { 31 | render(); 32 | }); 33 | expect(screen.getByText("hello")).toBeInTheDocument(); 34 | }); 35 | }); 36 | 37 | describe("Block10", () => { 38 | it("renders buttons", async () => { 39 | await act(() => { 40 | render(); 41 | }); 42 | expect(screen.getByText("hello")).toBeInTheDocument(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /blocks/block12/[Resources grid].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Resources grid", 3 | "description": "Feed of automatically loaded resources(like blogs or events) in a grid of cards", 4 | "name": "Block 12", 5 | "schemaName": "block.block12", 6 | "date": "2023-07-26T09:07:52.754Z", 7 | "fields": ["title", "intro"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block12/block12.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import Block12 from "./Block12"; 3 | import "@testing-library/jest-dom"; 4 | 5 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 6 | 7 | describe("Block12", () => { 8 | it("renders title", async () => { 9 | await act(() => { 10 | render(); 11 | }); 12 | expect(screen.getByText("Hello", { selector: "h2" })).toBeInTheDocument(); 13 | }); 14 | }); 15 | 16 | describe("Block12", () => { 17 | it("renders intro", async () => { 18 | await act(() => { 19 | render(Hello

} />); 20 | }); 21 | expect(screen.getByText("Hello", { selector: "p" })).toBeInTheDocument(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /blocks/block13/[Related resources].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Related resources", 3 | "description": "Feed of 4 automatically loaded resources (like blogs or events) in a row", 4 | "name": "Block 13", 5 | "schemaName": "block.block13", 6 | "date": "2023-07-27T06:45:00.872Z", 7 | "fields": ["title", "intro"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block13/block13.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import { demoImage } from "../../stories/content"; 3 | import Block13 from "./Block13"; 4 | import "@testing-library/jest-dom"; 5 | 6 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 7 | 8 | describe("Block13", () => { 9 | it("renders title", async () => { 10 | await act(() => { 11 | render( 12 | Hello

} 15 | items={[ 16 | { 17 | title: "Hello", 18 | _id: "x", 19 | date: "", 20 | image: demoImage, 21 | href: "/", 22 | intro: "", 23 | }, 24 | ]} 25 | />, 26 | ); 27 | }); 28 | expect(screen.getByText("Hello", { selector: "h2" })).toBeInTheDocument(); 29 | expect(screen.getByText("Hello", { selector: "p" })).toBeInTheDocument(); 30 | }); 31 | 32 | it("it doesn't render title with no items", async () => { 33 | const { container } = render(); 34 | expect(container).toBeEmptyDOMElement(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /blocks/block14/[Article].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Article", 3 | "description": "Long form resource content with sidebar", 4 | "name": "Block 14", 5 | "schemaName": "block.block14", 6 | "date": "2023-07-27T10:39:26.213Z", 7 | "fields": ["title", "intro"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block14/block14.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import Block14 from "./Block14"; 3 | import "@testing-library/jest-dom"; 4 | 5 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 6 | 7 | describe("Block14", () => { 8 | it("renders body", async () => { 9 | await act(() => { 10 | render(Hello

} />); 11 | }); 12 | expect(screen.getByText("Hello", { selector: "p" })).toBeInTheDocument(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /blocks/block15/[Split text].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Split text", 3 | "description": "Side by side text block", 4 | "name": "Block 15", 5 | "schemaName": "block.block15", 6 | "date": "2023-07-31T11:55:20.097Z", 7 | "fields": [ 8 | "title", 9 | "intro" 10 | ] 11 | } -------------------------------------------------------------------------------- /blocks/block15/block15.query.tsx: -------------------------------------------------------------------------------- 1 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 2 | import { LanguageType } from "../../languages"; 3 | import groq from "groq"; 4 | 5 | export const getBlock15Query = (language: LanguageType) => groq` 6 | _type == "block.block15" => { 7 | _key, 8 | _type, 9 | title, 10 | intro[] ${richTextQuery}, 11 | body[] ${richTextQuery}, 12 | }`; 13 | -------------------------------------------------------------------------------- /blocks/block15/block15.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import Block15 from "./Block15"; 3 | import "@testing-library/jest-dom"; 4 | 5 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 6 | 7 | describe("Block15", () => { 8 | it("renders title", async () => { 9 | await act(() => { 10 | render(); 11 | }); 12 | expect(screen.getByText("Hello", { selector: "h2" })).toBeInTheDocument(); 13 | }); 14 | 15 | it("renders intro", async () => { 16 | await act(() => { 17 | render(Hello

} />); 18 | }); 19 | expect(screen.getByText("Hello", { selector: "p" })).toBeInTheDocument(); 20 | }); 21 | 22 | it("renders body", async () => { 23 | await act(() => { 24 | render(Hello

} />); 25 | }); 26 | expect(screen.getByText("Hello", { selector: "p" })).toBeInTheDocument(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /blocks/block16/[Logo grid].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Logo grid", 3 | "description": "Displays a list of logos", 4 | "name": "Block 16", 5 | "schemaName": "block.block16", 6 | "date": "2023-08-09T11:59:46.526Z", 7 | "fields": [ 8 | "title", 9 | "intro", 10 | "buttons", 11 | "items" 12 | ] 13 | } -------------------------------------------------------------------------------- /blocks/block16/block16.query.tsx: -------------------------------------------------------------------------------- 1 | import { buttonQuery } from "../../components/buttons/button.query"; 2 | import { imageQuery } from "../../components/images/image.query"; 3 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 4 | import { LanguageType } from "../../languages"; 5 | import groq from "groq"; 6 | 7 | export const getBlock16Query = (language: LanguageType) => groq` 8 | _type == "block.block16" => { 9 | _key, 10 | _type, 11 | title, 12 | intro[] ${richTextQuery}, 13 | buttons[] ${buttonQuery}, 14 | items[] { 15 | _key, 16 | title, 17 | "image": ${imageQuery} 18 | } 19 | }`; 20 | -------------------------------------------------------------------------------- /blocks/block17/[Testimonial poster].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Testimonial", 3 | "description": "Large testimonial quote", 4 | "name": "Block 17", 5 | "schemaName": "block.block17", 6 | "date": "2023-08-10T05:37:36.154Z", 7 | "fields": ["title"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block17/block17.query.tsx: -------------------------------------------------------------------------------- 1 | import { getTestimonialQuery } from "../../components/testimonials/testimonials.query"; 2 | import { LanguageType } from "../../languages"; 3 | import groq from "groq"; 4 | 5 | export const getBlock17Query = (language: LanguageType) => groq` 6 | _type == "block.block17" => { 7 | _key, 8 | _type, 9 | testimonials[] ${getTestimonialQuery()}, 10 | icon 11 | }`; 12 | -------------------------------------------------------------------------------- /blocks/block17/block17.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import Block17 from "./Block17"; 3 | import "@testing-library/jest-dom"; 4 | 5 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 6 | 7 | describe("Block17", () => { 8 | it("renders testimonials", async () => { 9 | await act(() => { 10 | render(); 11 | }); 12 | expect(screen.getByText("hello")).toBeInTheDocument(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /blocks/block18/[Card grid].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Card grid", 3 | "description": "Generic grid of theme-able cards", 4 | "name": "Block 18", 5 | "schemaName": "block.block18", 6 | "date": "2023-08-10T11:23:13.269Z", 7 | "fields": ["title", "intro", "items", "buttons"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block18/block18.options.ts: -------------------------------------------------------------------------------- 1 | import { pick } from "../../helpers/utils/object"; 2 | import { SIZES } from "../../types"; 3 | 4 | export const COLUMN_OPTIONS = { 5 | 1: "One column", 6 | 2: "Two columns", 7 | 3: "Three columns", 8 | 4: "Four columns", 9 | 5: "Five columns", 10 | 6: "Six columns", 11 | }; 12 | export type ColumnType = keyof typeof COLUMN_OPTIONS; 13 | 14 | export const GAP_OPTIONS = pick( 15 | SIZES, 16 | "none", 17 | "2xs", 18 | "xs", 19 | "sm", 20 | "md", 21 | "lg", 22 | "xl", 23 | "2xl", 24 | ); 25 | export type GapType = keyof typeof GAP_OPTIONS; 26 | -------------------------------------------------------------------------------- /blocks/block2/[Feature section with icons].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Feature section with icons", 3 | "description": "Feature section with icons", 4 | "name": "Block 2", 5 | "schemaName": "block.block2", 6 | "date": "2023-06-28T16:04:53.511Z", 7 | "fields": [ 8 | "title", 9 | "intro", 10 | "items", 11 | "buttons" 12 | ] 13 | } -------------------------------------------------------------------------------- /blocks/block2/block2.query.tsx: -------------------------------------------------------------------------------- 1 | import { buttonQuery } from "../../components/buttons/button.query"; 2 | import { imageQuery } from "../../components/images/image.query"; 3 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 4 | import { LanguageType } from "../../languages"; 5 | import groq from "groq"; 6 | 7 | export const getBlock2Query = (language: LanguageType) => groq` 8 | _type == "block.block2" => { 9 | _key, 10 | _type, 11 | title, 12 | intro[] ${richTextQuery}, 13 | buttons[] ${buttonQuery}, 14 | items[] { 15 | _key, 16 | title, 17 | intro[] ${richTextQuery}, 18 | "image": ${imageQuery}, 19 | theme 20 | }, 21 | }`; 22 | -------------------------------------------------------------------------------- /blocks/block2/block2.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import Block2 from "./Block2"; 3 | import "@testing-library/jest-dom"; 4 | 5 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 6 | 7 | describe("Block2", () => { 8 | it("renders title", async () => { 9 | await act(() => { 10 | render(); 11 | }); 12 | expect(screen.getByText("Hello", { selector: "h2" })).toBeInTheDocument(); 13 | }); 14 | }); 15 | 16 | describe("Block2", () => { 17 | it("renders intro", async () => { 18 | await act(() => { 19 | render(Hello

} />); 20 | }); 21 | expect(screen.getByText("Hello", { selector: "p" })).toBeInTheDocument(); 22 | }); 23 | }); 24 | 25 | describe("Block2", () => { 26 | it("renders items", async () => { 27 | await act(() => { 28 | render(); 29 | }); 30 | expect(screen.getByText("Hello", { selector: "h3" })).toBeInTheDocument(); 31 | }); 32 | }); 33 | 34 | describe("Block2", () => { 35 | it("renders buttons", async () => { 36 | await act(() => { 37 | render(); 38 | }); 39 | expect(screen.getByText("hello")).toBeInTheDocument(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /blocks/block3/[Pricing plans].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Pricing plans", 3 | "description": "Grid with pricing plans and call to actions", 4 | "name": "Block 3", 5 | "schemaName": "block.block3", 6 | "date": "2023-10-04T06:05:32.137Z", 7 | "fields": ["title", "intro"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block3/block3.classes.ts: -------------------------------------------------------------------------------- 1 | export type ColumnType = 0 | 1 | 2 | 3 | 4; 2 | 3 | export const gridClasses: Record = { 4 | 0: "sm:grid-cols-2 lg:grid-cols-4", 5 | 1: "grid-cols-3", 6 | 2: "sm:grid-cols-2", 7 | 3: "sm:grid-cols-2 sm:[&>div:nth-of-type(3)]:col-span-2 lg:grid-cols-3 lg:[&>div:nth-of-type(3)]:col-span-1", 8 | 4: "sm:grid-cols-2 lg:grid-cols-3", 9 | }; 10 | -------------------------------------------------------------------------------- /blocks/block3/block3.query.tsx: -------------------------------------------------------------------------------- 1 | import { buttonQuery } from "../../components/buttons/button.query"; 2 | import { imageQuery } from "../../components/images/image.query"; 3 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 4 | import { LanguageType } from "../../languages"; 5 | import groq from "groq"; 6 | 7 | export const getBlock3Query = (language: LanguageType) => groq` 8 | _type == "block.block3" => { 9 | _key, 10 | _type, 11 | title, 12 | intro[] ${richTextQuery}, 13 | "plans": *[_type == "pricing.plan" && language == "${language}"] { 14 | _id, 15 | _type, 16 | title, 17 | description, 18 | price { 19 | amount, unit 20 | }, 21 | features[] ${richTextQuery}, 22 | buttons[] ${buttonQuery}, 23 | "image": ${imageQuery}, 24 | orderRank 25 | } | order(orderRank asc) 26 | }`; 27 | -------------------------------------------------------------------------------- /blocks/block4/[Default content].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Default content", 3 | "description": "Basic title, intro, buttons and image or video block", 4 | "name": "Block 4", 5 | "schemaName": "block.block4", 6 | "date": "2023-06-29T07:26:29.125Z", 7 | "fields": ["title", "intro", "image", "buttons"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block4/block4.query.tsx: -------------------------------------------------------------------------------- 1 | import { buttonQuery } from "../../components/buttons/button.query"; 2 | import { imageQuery } from "../../components/images/image.query"; 3 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 4 | import { videoQuery } from "../../components/video/video.query"; 5 | import { LanguageType } from "../../languages"; 6 | import groq from "groq"; 7 | 8 | export const getBlock4Query = (language: LanguageType) => groq` 9 | _type == "block.block4" => { 10 | _key, 11 | _type, 12 | title, 13 | subtitle, 14 | intro[] ${richTextQuery}, 15 | body[] ${richTextQuery}, 16 | "image": ${imageQuery}, 17 | buttons[] ${buttonQuery}, 18 | ${videoQuery}, 19 | }`; 20 | -------------------------------------------------------------------------------- /blocks/block5/[Pricing comparison tables].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Pricing comparison tables", 3 | "description": "Compare pricing features ", 4 | "name": "Block 5", 5 | "schemaName": "block.block5", 6 | "date": "2023-10-04T08:11:05.274Z", 7 | "fields": ["title", "intro"] 8 | } 9 | -------------------------------------------------------------------------------- /blocks/block5/block5.query.tsx: -------------------------------------------------------------------------------- 1 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 2 | import { LanguageType } from "../../languages"; 3 | import groq from "groq"; 4 | 5 | export const getBlock5Query = (language: LanguageType) => groq` 6 | _type == "block.block5" => { 7 | _key, 8 | _type, 9 | title, 10 | intro[] ${richTextQuery}, 11 | "features": *[_type == "pricing.feature" && language == "${language}"] { 12 | _id, 13 | title, 14 | "csv": coalesce(csv, file.asset->url), 15 | orderRank 16 | } | order(orderRank asc) 17 | }`; 18 | -------------------------------------------------------------------------------- /blocks/block5/block5.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render, screen } from "../../jest.utils"; 2 | import Block5 from "./Block5"; 3 | import "@testing-library/jest-dom"; 4 | 5 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 6 | 7 | describe("Block5", () => { 8 | it("renders title", async () => { 9 | await act(() => { 10 | render(); 11 | }); 12 | expect(screen.getByText("Hello", { selector: "h2" })).toBeInTheDocument(); 13 | }); 14 | }); 15 | 16 | describe("Block5", () => { 17 | it("renders intro", async () => { 18 | await act(() => { 19 | render(Hello

} />); 20 | }); 21 | expect(screen.getByText("Hello", { selector: "p" })).toBeInTheDocument(); 22 | }); 23 | }); 24 | 25 | describe("Block5", () => { 26 | it("renders items", async () => { 27 | await act(() => { 28 | render( 29 | , 37 | ); 38 | }); 39 | expect(screen.getByText("Hello", { selector: "h3" })).toBeInTheDocument(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /blocks/block7/[Item shelf].json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Item shelf", 3 | "description": "Item shelf", 4 | "name": "Block 7", 5 | "schemaName": "block.block7", 6 | "date": "2023-08-10T12:39:53.756Z", 7 | "fields": [ 8 | "title", 9 | "intro", 10 | "buttons", 11 | "items" 12 | ] 13 | } -------------------------------------------------------------------------------- /blocks/block7/block7.query.tsx: -------------------------------------------------------------------------------- 1 | import { buttonQuery, linkQuery } from "../../components/buttons/button.query"; 2 | import { imageQuery } from "../../components/images/image.query"; 3 | import { richTextQuery } from "../../components/portabletext/portabletext.query"; 4 | import { LanguageType } from "../../languages"; 5 | import groq from "groq"; 6 | 7 | export const getBlock7Query = (language: LanguageType) => groq` 8 | _type == "block.block7" => { 9 | _key, 10 | _type, 11 | title, 12 | intro[] ${richTextQuery}, 13 | buttons[] ${buttonQuery}, 14 | items[] { 15 | _key, 16 | title, 17 | "image": ${imageQuery}, 18 | link ${linkQuery} 19 | }, 20 | }`; 21 | -------------------------------------------------------------------------------- /cli/create-block/create-info.test.ts: -------------------------------------------------------------------------------- 1 | import { createInfo } from "./create-info"; 2 | 3 | test("create info file", () => { 4 | const result = createInfo({ 5 | blockName: "Block 1", 6 | blockTitle: "Test", 7 | blockDescription: "testing 123", 8 | fields: ["title", "intro", "buttons", "image", "items"], 9 | }); 10 | 11 | const resultObj = JSON.parse(result); 12 | resultObj.date = "test-date"; 13 | 14 | expect(resultObj).toEqual({ 15 | title: "Test", 16 | description: "testing 123", 17 | name: "Block 1", 18 | schemaName: "block.block1", 19 | fields: ["title", "intro", "buttons", "image", "items"], 20 | date: "test-date", 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /cli/create-block/create-info.ts: -------------------------------------------------------------------------------- 1 | import { AnswersType } from "."; 2 | import { write } from "../utils/is-write"; 3 | import { prettierFile } from "../utils/prettier-file"; 4 | import { formatName } from "./format-name"; 5 | 6 | /** 7 | * Create the schema file 8 | */ 9 | const fs = require("fs"); 10 | const path = require("path"); 11 | 12 | export function createInfo(answers: AnswersType) { 13 | let { blockName, blockTitle, blockDescription, fields } = answers; 14 | let { lowerName, schemaName } = formatName(blockName); 15 | 16 | const lines = JSON.stringify( 17 | { 18 | title: blockTitle, 19 | description: blockDescription, 20 | name: blockName, 21 | schemaName: schemaName, 22 | date: new Date().toISOString(), 23 | fields: fields, 24 | }, 25 | null, 26 | 2, 27 | ); 28 | 29 | if (write) { 30 | let filePath = `${__dirname}/../../blocks/${lowerName}/[${blockTitle}].json`; 31 | 32 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 33 | fs.writeFileSync(filePath, lines); 34 | prettierFile(filePath); 35 | } 36 | 37 | return lines; 38 | } 39 | -------------------------------------------------------------------------------- /cli/create-block/create-query.ts: -------------------------------------------------------------------------------- 1 | import { AnswersType } from "."; 2 | import { write } from "../utils/is-write"; 3 | import { prettierFile } from "../utils/prettier-file"; 4 | import { formatName } from "./format-name"; 5 | import { getQuerySnippet } from "./templates/query"; 6 | 7 | /** 8 | * Create the schema file 9 | */ 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | 13 | export function createQuery( 14 | answers: Pick, 15 | ) { 16 | let { blockName, fields } = answers; 17 | let { lowerName, pascalName, schemaName } = formatName(blockName); 18 | 19 | const lines = getQuerySnippet({ pascalName, schemaName, fields }); 20 | 21 | if (write) { 22 | let filePath = `${__dirname}/../../blocks/${lowerName}/${lowerName}.query.tsx`; 23 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 24 | fs.writeFileSync(filePath, lines); 25 | prettierFile(filePath); 26 | } 27 | 28 | return lines; 29 | } 30 | -------------------------------------------------------------------------------- /cli/create-block/create-react-component.ts: -------------------------------------------------------------------------------- 1 | import { AnswersType } from "."; 2 | import { write } from "../utils/is-write"; 3 | import { prettierFile } from "../utils/prettier-file"; 4 | import { formatName } from "./format-name"; 5 | import { getReactComponentSnippet } from "./templates/react-component"; 6 | 7 | /** 8 | * Create the schema file 9 | */ 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | 13 | export function createReactComponent( 14 | answers: Pick, 15 | ) { 16 | let { blockName, fields } = answers; 17 | let { lowerName, pascalName } = formatName(blockName); 18 | 19 | let lines = getReactComponentSnippet({ pascalName, lowerName, fields }); 20 | 21 | if (write) { 22 | let filePath = `${__dirname}/../../blocks/${lowerName}/${pascalName}.tsx`; 23 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 24 | fs.writeFileSync(filePath, lines); 25 | prettierFile(filePath); 26 | } 27 | 28 | return lines; 29 | } 30 | -------------------------------------------------------------------------------- /cli/create-block/create-schema.ts: -------------------------------------------------------------------------------- 1 | import { AnswersType } from "."; 2 | import { write } from "../utils/is-write"; 3 | import { prettierFile } from "../utils/prettier-file"; 4 | import { formatName } from "./format-name"; 5 | import { getSchemaSnippet } from "./templates/schema"; 6 | 7 | /** 8 | * Create the schema file 9 | */ 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | 13 | export function createSchema( 14 | answers: Pick< 15 | AnswersType, 16 | "blockName" | "fields" | "blockDescription" | "blockTitle" 17 | >, 18 | ) { 19 | let { blockName, fields, blockTitle, blockDescription } = answers; 20 | let { lowerName, schemaName } = formatName(blockName); 21 | 22 | const lines = getSchemaSnippet({ 23 | blockName, 24 | blockTitle, 25 | lowerName, 26 | schemaName, 27 | fields, 28 | blockDescription, 29 | }); 30 | 31 | if (write) { 32 | let filePath = `${__dirname}/../../blocks/${lowerName}/${lowerName}.schema.tsx`; 33 | 34 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 35 | fs.writeFileSync(filePath, lines); 36 | prettierFile(filePath); 37 | } 38 | 39 | return lines; 40 | } 41 | -------------------------------------------------------------------------------- /cli/create-block/create-story.ts: -------------------------------------------------------------------------------- 1 | import { AnswersType } from "."; 2 | import { write } from "../utils/is-write"; 3 | import { prettierFile } from "../utils/prettier-file"; 4 | import { formatName } from "./format-name"; 5 | import { getStorySnippet } from "./templates/story"; 6 | 7 | /** 8 | * Create the schema file 9 | */ 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | 13 | export function createStory( 14 | answers: Pick, 15 | ) { 16 | let { blockName, fields } = answers; 17 | let { lowerName, pascalName } = formatName(blockName); 18 | 19 | let lines = getStorySnippet({ pascalName, lowerName, fields }); 20 | 21 | if (write) { 22 | let filePath = `${__dirname}/../../blocks/${lowerName}/${lowerName}.stories.tsx`; 23 | 24 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 25 | fs.writeFileSync(filePath, lines); 26 | prettierFile(filePath); 27 | } 28 | 29 | return lines; 30 | } 31 | -------------------------------------------------------------------------------- /cli/create-block/create-tests.ts: -------------------------------------------------------------------------------- 1 | import { AnswersType } from "."; 2 | import { write } from "../utils/is-write"; 3 | import { prettierFile } from "../utils/prettier-file"; 4 | import { formatName } from "./format-name"; 5 | import { getTestSnippet } from "./templates/tests"; 6 | 7 | /** 8 | * Create the schema file 9 | */ 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | 13 | export function createTests( 14 | answers: Pick, 15 | ) { 16 | let { blockName, fields } = answers; 17 | let { lowerName, pascalName } = formatName(blockName); 18 | 19 | const lines = getTestSnippet({ pascalName, fields }); 20 | 21 | if (write) { 22 | let filePath = `${__dirname}/../../blocks/${lowerName}/${lowerName}.test.tsx`; 23 | 24 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 25 | fs.writeFileSync(filePath, lines); 26 | prettierFile(filePath); 27 | } 28 | 29 | return lines; 30 | } 31 | -------------------------------------------------------------------------------- /cli/create-block/format-name.test.ts: -------------------------------------------------------------------------------- 1 | import { formatName } from "./format-name"; 2 | 3 | test("create names", () => { 4 | const result = formatName("Test block"); 5 | 6 | expect(Object.keys(result)).toEqual([ 7 | "pascalName", 8 | "lowerName", 9 | "schemaName", 10 | ]); 11 | 12 | expect(result.pascalName).toEqual("TestBlock"); 13 | expect(result.lowerName).toEqual("testblock"); 14 | expect(result.schemaName).toEqual("block.testblock"); 15 | }); 16 | -------------------------------------------------------------------------------- /cli/create-block/format-name.ts: -------------------------------------------------------------------------------- 1 | import { pascalCase } from "../utils/pascal-case"; 2 | 3 | export function formatName(name: string) { 4 | name = name.trim(); 5 | let pascalName = pascalCase(name).replace(/\s/g, ""); 6 | let lowerName = name.toLowerCase().replace(/\s/g, ""); 7 | let schemaName = `block.${name.toLowerCase()}`.replace(/\s/g, ""); 8 | 9 | return { 10 | pascalName, 11 | lowerName, 12 | schemaName, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /cli/create-block/inject-builder.test.ts: -------------------------------------------------------------------------------- 1 | import { injectBuilder } from "./inject-builder"; 2 | 3 | test("inject block in page builder", () => { 4 | const result = injectBuilder({ 5 | blockName: "Test", 6 | }); 7 | 8 | expect( 9 | result.includes(`import { TestProps } from "../../blocks/test/Test";`), 10 | ).toBeTruthy(); 11 | 12 | expect( 13 | result.replace(/\s/g, "").includes( 14 | `const Test = lazy>( 15 | () => 16 | import( 17 | /* webpackChunkName: "Test" */ "../../blocks/test/Test" 18 | ), 19 | ); 20 | `.replace(/\s/g, ""), 21 | ), 22 | ).toBeTruthy(); 23 | 24 | expect( 25 | result.replace(/\s/g, "").includes( 26 | `{item._type === "block.test" && ( 27 | 28 | )}`.replace(/\s/g, ""), 29 | ), 30 | ).toBeTruthy(); 31 | }); 32 | -------------------------------------------------------------------------------- /cli/create-block/inject-builder.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Add the schema to the schema index file 3 | */ 4 | import { AnswersType } from "."; 5 | import { injectLine } from "../utils/inject-line"; 6 | import { write } from "../utils/is-write"; 7 | import { prettierFile } from "../utils/prettier-file"; 8 | import { formatName } from "./format-name"; 9 | import { 10 | getBuilderComponent, 11 | getBlockBuilderImport, 12 | } from "./templates/builder"; 13 | 14 | const fs = require("fs"); 15 | 16 | export function injectBuilder(answers: Pick) { 17 | let { pascalName, lowerName, schemaName } = formatName(answers.blockName); 18 | 19 | let filePath = `${__dirname}/../../layout/pagebuilder/BlockBuilder.tsx`; 20 | let lines = fs.readFileSync(filePath).toString().split("\n"); 21 | 22 | let imports = getBlockBuilderImport({ pascalName, lowerName, schemaName }); 23 | let needle = ""; 24 | 25 | lines = [imports, ...lines]; 26 | 27 | lines = injectLine({ 28 | addition: getBuilderComponent({ 29 | pascalName, 30 | lowerName, 31 | schemaName, 32 | }), 33 | lines, 34 | needle, 35 | offset: 0, 36 | }); 37 | 38 | lines = lines.join("\n"); 39 | 40 | if (write) { 41 | fs.writeFileSync(filePath, lines); 42 | prettierFile(filePath); 43 | } 44 | return lines; 45 | } 46 | -------------------------------------------------------------------------------- /cli/create-block/inject-page-query.test.ts: -------------------------------------------------------------------------------- 1 | import { injectPageQuery } from "./inject-page-query"; 2 | 3 | test("inject query page.query.ts", () => { 4 | const result = injectPageQuery({ 5 | blockName: "Test", 6 | }); 7 | 8 | expect( 9 | result.includes( 10 | `import { getTestQuery } from "../blocks/test/test.query";`, 11 | ), 12 | ).toBeTruthy(); 13 | 14 | expect(result.includes("${getTestQuery(language)},")).toBeTruthy(); 15 | }); 16 | -------------------------------------------------------------------------------- /cli/create-block/inject-page-query.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Add the schema to the schema index file 3 | */ 4 | import { AnswersType } from "."; 5 | import { injectLine } from "../utils/inject-line"; 6 | import { write } from "../utils/is-write"; 7 | import { prettierFile } from "../utils/prettier-file"; 8 | import { formatName } from "./format-name"; 9 | import { 10 | getBlockPageQuery, 11 | getBlockPageQueryImport, 12 | } from "./templates/page-query"; 13 | 14 | const fs = require("fs"); 15 | 16 | export function injectPageQuery(answers: Pick) { 17 | let { pascalName, lowerName } = formatName(answers.blockName); 18 | 19 | const filePath = `${__dirname}/../../queries/page.query.ts`; 20 | let lines = fs.readFileSync(filePath).toString().split("\n"); 21 | 22 | let needle = '"blocks": '; 23 | let imports = getBlockPageQueryImport({ pascalName, lowerName }); 24 | 25 | lines.push(imports); 26 | lines = injectLine({ 27 | addition: getBlockPageQuery({ pascalName, lowerName }), 28 | lines, 29 | needle, 30 | offset: 1, 31 | }); 32 | 33 | lines = lines.join("\n"); 34 | 35 | if (write) { 36 | fs.writeFileSync(filePath, lines); 37 | prettierFile(filePath); 38 | } 39 | return lines; 40 | } 41 | -------------------------------------------------------------------------------- /cli/create-block/inject-schema.test.ts: -------------------------------------------------------------------------------- 1 | import { injectSchema } from "./inject-schema"; 2 | 3 | test("inject schema in sanity studio/schemas/index.ts", () => { 4 | const result = injectSchema({ 5 | blockName: "Test", 6 | }); 7 | 8 | expect( 9 | result.includes(`import test from "../../blocks/test/test.schema";`), 10 | ).toBeTruthy(); 11 | 12 | expect(result.includes(`test,`)).toBeTruthy(); 13 | }); 14 | -------------------------------------------------------------------------------- /cli/create-block/inject-types.test.ts: -------------------------------------------------------------------------------- 1 | import { injectTypes } from "./inject-types"; 2 | 3 | test("inject types in types.sanity.ts", () => { 4 | const result = injectTypes({ 5 | blockName: "Test", 6 | }); 7 | 8 | expect(result.includes(`"block.test":`)).toBeTruthy(); 9 | expect(result.includes(`"block.test",`)).toBeTruthy(); 10 | }); 11 | -------------------------------------------------------------------------------- /cli/create-block/templates/builder.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | pascalName: string; 3 | lowerName: string; 4 | schemaName: string; 5 | }; 6 | 7 | export const getBlockBuilderImport = ({ pascalName, lowerName }: Props) => { 8 | return ` 9 | import { ${pascalName}Props } from "../../blocks/${lowerName}/${pascalName}"; 10 | const ${pascalName} = lazy>( 11 | () => 12 | import( 13 | /* webpackChunkName: "${pascalName}" */ "../../blocks/${lowerName}/${pascalName}" 14 | ), 15 | );`; 16 | }; 17 | 18 | export const getBuilderComponent = ({ pascalName, schemaName }: Props) => { 19 | return ` 20 | {item._type === "${schemaName}" && ( 21 | <${pascalName} {...(item as ${pascalName}Props)} /> 22 | )} 23 | `; 24 | }; 25 | -------------------------------------------------------------------------------- /cli/create-block/templates/page-query.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | pascalName: string; 3 | lowerName: string; 4 | }; 5 | 6 | export const getBlockPageQueryImport = ({ pascalName, lowerName }: Props) => { 7 | return `import { get${pascalName}Query } from "../blocks/${lowerName}/${lowerName}.query";`; 8 | }; 9 | 10 | export const getBlockPageQuery = ({ pascalName }: Props) => { 11 | return ` \${get${pascalName}Query(language)},`; 12 | }; 13 | -------------------------------------------------------------------------------- /cli/create-page/format-name.test.ts: -------------------------------------------------------------------------------- 1 | import { formatName } from "./format-name"; 2 | 3 | test("create names", () => { 4 | const result = formatName("Test page"); 5 | 6 | expect(Object.keys(result)).toEqual([ 7 | "pascalName", 8 | "lowerName", 9 | "schemaName", 10 | "documentId", 11 | ]); 12 | 13 | expect(result.pascalName).toEqual("TestPage"); 14 | expect(result.lowerName).toEqual("test page"); 15 | expect(result.schemaName).toEqual("page.testpage"); 16 | expect(result.documentId).toEqual("page_testpage"); 17 | }); 18 | -------------------------------------------------------------------------------- /cli/create-page/format-name.ts: -------------------------------------------------------------------------------- 1 | import { pascalCase } from "../utils/pascal-case"; 2 | 3 | export function formatName(name: string) { 4 | let pascalName = `${pascalCase(name)}`; 5 | let lowerName = name.toLowerCase(); 6 | let schemaName = `page.${name.toLowerCase().replace(/\s/g, "")}`; 7 | let documentId = schemaName.replace("page.", "page_"); 8 | 9 | return { 10 | pascalName, 11 | lowerName, 12 | schemaName, 13 | documentId, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /cli/create-page/inject-schema.test.ts: -------------------------------------------------------------------------------- 1 | import { injectSchema } from "./inject-schema"; 2 | 3 | test("inject schema in sanity studio/schemas/index.ts", () => { 4 | const result = injectSchema({ 5 | pageName: "Tests", 6 | }); 7 | 8 | expect( 9 | result.includes(`import pageTests from "./documents/page.tests";`), 10 | ).toBeTruthy(); 11 | 12 | expect(result.includes(`pageTests,`)).toBeTruthy(); 13 | }); 14 | -------------------------------------------------------------------------------- /cli/create-page/inject-types.test.ts: -------------------------------------------------------------------------------- 1 | import { injectTypes } from "./inject-types"; 2 | 3 | test("inject types in types.sanity.ts", () => { 4 | const result = injectTypes({ 5 | pageName: "Test", 6 | }); 7 | 8 | expect(result.includes(`"page.test":`)).toBeTruthy(); 9 | expect(result.includes(`"page.test",`)).toBeTruthy(); 10 | }); 11 | -------------------------------------------------------------------------------- /cli/create-page/templates/content-page.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | schemaName: string; 3 | pageName: string; 4 | }; 5 | 6 | export const getContentPageSchema = ({ schemaName, pageName }: Props) => { 7 | return ` 8 | import { SchemaName } from "../../../types.sanity"; 9 | import { 10 | DEFAULT_CONTENT_PAGE_ORDERINGS, 11 | DEFAULT_CONTENT_PAGE_PREVIEW, 12 | pageBase, 13 | PARENT_FIELD, 14 | } from "./page-fields"; 15 | import { Pages } from "@vectopus/atlas-icons-react"; 16 | import React from "react"; 17 | import { defineType } from "sanity"; 18 | 19 | export const SCHEMA_NAME: SchemaName = "${schemaName}"; 20 | 21 | export default defineType({ 22 | name: SCHEMA_NAME, 23 | title: "${pageName}", 24 | type: "document", 25 | icon: () => , 26 | orderings: DEFAULT_CONTENT_PAGE_ORDERINGS, 27 | preview: DEFAULT_CONTENT_PAGE_PREVIEW, 28 | groups: [...pageBase.groups], 29 | fields: [PARENT_FIELD, ...pageBase.fields], 30 | }); 31 | `; 32 | }; 33 | -------------------------------------------------------------------------------- /cli/create-page/templates/singleton-page.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | schemaName: string; 3 | pageName: string; 4 | }; 5 | 6 | export const getSingletonPageSchema = ({ schemaName, pageName }: Props) => { 7 | return ` 8 | import { SchemaName } from "../../../types.sanity"; 9 | import { 10 | DEFAULT_CONTENT_PAGE_PREVIEW, 11 | getI18nBaseFieldForSingleton, 12 | pageBase, 13 | PARENT_FIELD, 14 | } from "./page-fields"; 15 | import { Pages } from "@vectopus/atlas-icons-react"; 16 | import React from "react"; 17 | import { defineType } from "sanity"; 18 | 19 | export const SCHEMA_NAME: SchemaName = "${schemaName}"; 20 | 21 | export default defineType({ 22 | name: SCHEMA_NAME, 23 | title: "${pageName}", 24 | type: "document", 25 | options: { 26 | singleton: true, 27 | }, 28 | preview: DEFAULT_CONTENT_PAGE_PREVIEW, 29 | icon: () => , 30 | groups: [...pageBase.groups], 31 | fields: [ 32 | PARENT_FIELD, 33 | ...pageBase.fields.map((field) => { 34 | if (field.name === "i18n_base") { 35 | return getI18nBaseFieldForSingleton(SCHEMA_NAME); 36 | } 37 | return { ...field }; 38 | }), 39 | ], 40 | }); 41 | `; 42 | }; 43 | -------------------------------------------------------------------------------- /cli/create-page/templates/structure-collection.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | documentId: string; 3 | schemaName: string; 4 | pageName: string; 5 | articleName: string; 6 | articleSchemaName: string; 7 | }; 8 | 9 | export const getStructureCollection = ({ 10 | documentId, 11 | pageName, 12 | schemaName, 13 | articleName, 14 | articleSchemaName, 15 | }: Props) => { 16 | return `S.listItem() 17 | .title("${pageName}") 18 | .icon(getIconForSchema(S, "${schemaName}")) 19 | .child( 20 | list(S, { title: "${pageName}" }).items([ 21 | singleton(S, { 22 | id: \`${documentId}\`, 23 | type: "${schemaName}", 24 | language: language.id, 25 | }), 26 | documentList(S, { 27 | type: "${articleSchemaName}", 28 | title: "${articleName}", 29 | language: language.id, 30 | }), 31 | ]), 32 | ),`; 33 | }; 34 | -------------------------------------------------------------------------------- /cli/create-page/templates/structure-document-list.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | schemaName: string; 3 | pascalName: string; 4 | }; 5 | 6 | export const getStructureDocumentList = ({ pascalName, schemaName }: Props) => { 7 | return `documentList(S, { 8 | type: '${schemaName}', 9 | title: '${pascalName}', 10 | language: language.id, 11 | }),`; 12 | }; 13 | -------------------------------------------------------------------------------- /cli/create-page/templates/structure-singleton.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | documentId: string; 3 | schemaName: string; 4 | }; 5 | 6 | export const getStructureSingleton = ({ documentId, schemaName }: Props) => { 7 | return `singleton(S, { 8 | id: '${documentId}', 9 | type: '${schemaName}', 10 | language: language.id, 11 | }),`; 12 | }; 13 | -------------------------------------------------------------------------------- /cli/setup/delete-project.sh: -------------------------------------------------------------------------------- 1 | printf "\033[0;36mWhat 's the Sanity project id?\n› \033[0m" 2 | read projectId 3 | 4 | cd .. 5 | 6 | # get sanity auth token 7 | authToken=$(sanity debug --secrets | grep 'Auth token' | cut -d \' -f2) 8 | 9 | cd - 10 | 11 | # delete project 12 | curl -X DELETE "https://api.sanity.io/v2021-06-07/projects/$projectId" \ 13 | -H "Authorization: Bearer $authToken" \ 14 | -H 'Content-Type: application/json' -------------------------------------------------------------------------------- /cli/utils/inject-line.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Add a line to an array of lines after a flag 3 | * 4 | * injectLine({ 5 | * addition: 'foo', 6 | * lines: ['1', '2', '3', '4', '5'], 7 | * needle: '3' 8 | * }) 9 | * 10 | * [ '1', '2', '3', 'foo', '4', '5' ] 11 | */ 12 | 13 | type InjectLineProps = { 14 | addition: string; 15 | lines: string[]; 16 | needle: string; 17 | offset?: number; 18 | delimiter?: string; 19 | }; 20 | 21 | export const injectLine = ({ 22 | addition, 23 | lines, 24 | needle, 25 | offset = 1, 26 | delimiter, 27 | }: InjectLineProps) => { 28 | const newLines = [...lines]; 29 | const needleIndex = newLines.findIndex((line) => line.indexOf(needle) > -1); 30 | 31 | const relevantLines = newLines.slice(needleIndex); 32 | const delimiterIndex = !delimiter 33 | ? -1 34 | : relevantLines.findIndex((line) => line.indexOf(delimiter) > -1); 35 | 36 | // if needle and delimiter are on the same line we insert it on the same line 37 | if (delimiter && delimiterIndex === 0) { 38 | newLines[needleIndex + delimiterIndex] = newLines[ 39 | needleIndex + delimiterIndex 40 | ].replace(delimiter, `,${addition} ${delimiter}`); 41 | } else { 42 | newLines.splice(needleIndex + offset, 0, addition); 43 | } 44 | 45 | return newLines; 46 | }; 47 | -------------------------------------------------------------------------------- /cli/utils/is-write.ts: -------------------------------------------------------------------------------- 1 | const args = process.argv.slice(2); 2 | const write = args.includes("--write"); 3 | export { write }; 4 | -------------------------------------------------------------------------------- /cli/utils/pascal-case.test.ts: -------------------------------------------------------------------------------- 1 | import { pascalCase } from "./pascal-case"; 2 | 3 | test("pascal names", () => { 4 | const result = pascalCase("Test page"); 5 | expect(result).toEqual("TestPage"); 6 | }); 7 | -------------------------------------------------------------------------------- /cli/utils/pascal-case.ts: -------------------------------------------------------------------------------- 1 | export const pascalCase = (str: string) => { 2 | return str 3 | .replace(/-/g, " ") 4 | .replace(/(\w)(\w*)/g, function (g0, g1, g2) { 5 | return g1.toUpperCase() + g2.toLowerCase(); 6 | }) 7 | .replace(/\s+/g, ""); 8 | }; 9 | -------------------------------------------------------------------------------- /cli/utils/prettier-file.ts: -------------------------------------------------------------------------------- 1 | import { StdioPipe } from "child_process"; 2 | 3 | const { exec } = require("child_process"); 4 | 5 | export const prettierFile = (filePath: string) => { 6 | exec( 7 | `${__dirname}/../../node_modules/prettier/bin-prettier.js --write "${filePath}" --trailing-comma all`, 8 | (error: Error, stdout: StdioPipe, stderr: StdioPipe) => { 9 | if (error) { 10 | console.log(`error: ${error.message}`); 11 | console.log(stdout); 12 | return; 13 | } 14 | if (stderr) { 15 | if (stderr.indexOf("[warn] ") > -1) return; 16 | console.log(stderr); 17 | console.log(stdout); 18 | return; 19 | } 20 | } 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /cli/utils/render-field.test.ts: -------------------------------------------------------------------------------- 1 | import { render } from "./render-field"; 2 | 3 | test("has field", () => { 4 | expect(render()).toEqual(""); 5 | expect(render(["foo"])).toEqual(""); 6 | expect(render(undefined, "foo")).toEqual(""); 7 | expect(render(["a", "b", "c"], "c", "foo")).toEqual("foo"); 8 | expect(render(["a", "b", "c"], "a", "foo")).toEqual("foo"); 9 | expect(render(["a", "b", "c"], "d")).toEqual(""); 10 | }); 11 | -------------------------------------------------------------------------------- /cli/utils/render-field.ts: -------------------------------------------------------------------------------- 1 | export const render = (fields?: string[], fieldName?: string, snippet = "") => { 2 | if (!fields) return ""; 3 | if (!fieldName) return ""; 4 | if (!Array.isArray(fields)) return ""; 5 | if (fields.includes(fieldName)) return snippet; 6 | return ""; 7 | }; 8 | -------------------------------------------------------------------------------- /cli/utils/sort-lines.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Alphabetically sort a subset of lines 3 | * starting the line after fromNeedle and ending the line before toNeedle 4 | * 5 | * const lines = ['x','xx','xxx','start here','x','y','z','a','d','end here;','xxxx','xxxxx'] 6 | * sortLines({ lines, fromNeedle: 'start here', toNeedle: 'end here' }) 7 | * [ 'x', 'xx', 'xxx', 'start here', 'a', 'd', 'x', 'y', 'z', 'end here;', 'xxxx', 'xxxxx' ] 8 | */ 9 | 10 | type SortLinesProps = { 11 | lines: string[]; 12 | fromNeedle: string; 13 | toNeedle: string; 14 | adjustFromLine?: number; 15 | adjustToLine?: number; 16 | }; 17 | 18 | export const sortLines = ({ 19 | lines, 20 | fromNeedle, 21 | toNeedle, 22 | adjustFromLine = 0, 23 | adjustToLine = 0, 24 | }: SortLinesProps) => { 25 | const newLines = [...lines]; 26 | const startAt = newLines.findIndex((line) => line.indexOf(fromNeedle) > -1); 27 | 28 | if (newLines[startAt].endsWith(toNeedle)) return newLines; 29 | 30 | const linesToSort = newLines.slice(startAt + adjustFromLine + 1); 31 | const endAt = 32 | linesToSort.findIndex((line) => line.indexOf(toNeedle) > -1) + adjustToLine; 33 | const sortedLines = linesToSort.slice(0, endAt).sort(); 34 | newLines.splice( 35 | startAt + adjustFromLine + 1, 36 | sortedLines.length, 37 | ...sortedLines, 38 | ); 39 | return newLines; 40 | }; 41 | -------------------------------------------------------------------------------- /components/accordion/accordion.stories.tsx: -------------------------------------------------------------------------------- 1 | import { demoFAQList } from "../../stories/content"; 2 | import { Accordion } from "./Accordion"; 3 | import { Meta } from "@storybook/react"; 4 | import React from "react"; 5 | 6 | export default { 7 | component: Accordion, 8 | title: "Components/Accordion", 9 | } as Meta; 10 | 11 | export const Default = () => ; 12 | 13 | export const Colors = () => ( 14 | 23 | ); -------------------------------------------------------------------------------- /components/block/Bleed.tsx: -------------------------------------------------------------------------------- 1 | import { bleedClasses, BleedSpaceType } from "./bleed.options"; 2 | import cx from "clsx"; 3 | import React from "react"; 4 | 5 | export type BleedProps = { 6 | bleed?: BleedSpaceType; 7 | children: React.ReactElement | React.ReactNode; 8 | className?: string; 9 | id?: string; 10 | }; 11 | 12 | export const Bleed = React.forwardRef( 13 | ({ children, bleed, className, id }, ref) => { 14 | return ( 15 |
20 | {children} 21 |
22 | ); 23 | }, 24 | ); 25 | 26 | Bleed.displayName = "Bleed"; 27 | 28 | export default React.memo(Bleed); 29 | -------------------------------------------------------------------------------- /components/block/Spacing.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | SpaceType, 3 | paddingTopClasses, 4 | paddingBottomClasses, 5 | marginTopClasses, 6 | marginBottomClasses, 7 | } from "./spacing.options"; 8 | import cx from "clsx"; 9 | import React from "react"; 10 | import { twMerge } from "tailwind-merge"; 11 | 12 | export type SpacingProps = { 13 | padding?: SpaceType; 14 | margin?: SpaceType; 15 | children?: React.ReactElement | React.ReactNode; 16 | className?: string; 17 | }; 18 | 19 | export const Spacing = ({ 20 | padding, 21 | margin, 22 | children, 23 | className, 24 | }: SpacingProps) => { 25 | return ( 26 |
35 | {children} 36 |
37 | ); 38 | }; 39 | 40 | export default React.memo(Spacing); 41 | -------------------------------------------------------------------------------- /components/block/Width.tsx: -------------------------------------------------------------------------------- 1 | import { widthClasses, WidthType } from "./width.options"; 2 | import cx from "clsx"; 3 | import React from "react"; 4 | 5 | export type WidthProps = { 6 | width?: WidthType; 7 | children: React.ReactElement | React.ReactNode; 8 | className?: string; 9 | }; 10 | 11 | export const Width = ({ children, width, className }: WidthProps) => { 12 | return ( 13 |
20 | {children} 21 |
22 | ); 23 | }; 24 | 25 | export default React.memo(Width); 26 | -------------------------------------------------------------------------------- /components/block/background.options.ts: -------------------------------------------------------------------------------- 1 | import { pick } from "../../helpers/utils/object"; 2 | import { SIZES } from "../../types"; 3 | 4 | export const BLOCK_RADIUS_OPTIONS = pick(SIZES, "none", "md", "lg"); 5 | export type BlockRadiusType = keyof typeof BLOCK_RADIUS_OPTIONS; 6 | 7 | export type BlockRoundedType = { 8 | top?: BlockRadiusType; 9 | bottom?: BlockRadiusType; 10 | }; 11 | 12 | export const backgroundRoundedTopClasses: Record = { 13 | none: "rounded-t-none", 14 | md: "rounded-t-[16px] sm:rounded-t-[24px] md:rounded-t-[32px] xl:rounded-t-[40px]", 15 | lg: "rounded-t-[16px] sm:rounded-t-[24px] md:rounded-t-[40px] lg:rounded-t-[48px] xl:rounded-t-[64px]", 16 | }; 17 | 18 | export const backgroundRoundedBottomClasses: Record = { 19 | none: "rounded-b-none", 20 | md: "rounded-b-[16px] sm:rounded-b-[24px] md:rounded-b-[32px] xl:rounded-b-[40px]", 21 | lg: "rounded-b-[16px] sm:rounded-b-[24px] md:rounded-b-[40px] lg:rounded-b-[48px] xl:rounded-b-[64px]", 22 | }; 23 | -------------------------------------------------------------------------------- /components/block/background.stories.tsx: -------------------------------------------------------------------------------- 1 | import { COLORS } from "../../theme"; 2 | import { ColorType } from "../../types"; 3 | import { Background as BlockBackground } from "./Background"; 4 | import { BlockRadiusType, BLOCK_RADIUS_OPTIONS } from "./background.options"; 5 | import { Meta } from "@storybook/react"; 6 | import React from "react"; 7 | 8 | export default { 9 | component: BlockBackground, 10 | title: "Components/Block/Background", 11 | } as Meta; 12 | 13 | export const Background = () => ( 14 | <> 15 | {(Object.keys(COLORS) as ColorType[]).map((color: ColorType) => ( 16 |
17 | 18 |
Block background {color}
19 |
20 |
21 | ))} 22 | 23 | ); 24 | 25 | export const Rounded = () => ( 26 |
27 | {(Object.keys(BLOCK_RADIUS_OPTIONS) as BlockRadiusType[]).map( 28 | (radius: BlockRadiusType) => ( 29 | 36 |
{radius}
37 |
38 | ), 39 | )} 40 |
41 | ); 42 | -------------------------------------------------------------------------------- /components/block/bleed.options.ts: -------------------------------------------------------------------------------- 1 | import { pick } from "../../helpers/utils/object"; 2 | import { SIZES } from "../../types"; 3 | 4 | export const BLEED_SPACE_OPTIONS = pick(SIZES, "none", "sm", "md", "lg"); 5 | export type BleedSpaceType = keyof typeof BLEED_SPACE_OPTIONS; 6 | 7 | export const bleedClasses: Record = { 8 | none: "px-0", 9 | sm: "px-3 sm:px-4 lg:px-12 xl:px-20", 10 | md: "px-5 sm:px-8 lg:px-14 xl:px-20", 11 | lg: "px-8 sm:px-10 lg:px-16 xl:px-20", 12 | }; 13 | -------------------------------------------------------------------------------- /components/block/bleed.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Bleed as BleedComponent } from "./Bleed"; 2 | import { Meta } from "@storybook/react"; 3 | import React from "react"; 4 | 5 | export default { 6 | component: BleedComponent, 7 | title: "Components/Block/Bleed", 8 | } as Meta; 9 | 10 | export const BleedNone = () => ( 11 |
12 | 13 |
14 | 15 |
16 | ); 17 | 18 | export const BleedSmall = () => ( 19 |
20 | 21 |
22 | 23 |
24 | ); 25 | 26 | export const BleedDefault = () => ( 27 |
28 | 29 |
30 | 31 |
32 | ); 33 | 34 | export const BleedLarge = () => ( 35 |
36 | 37 |
38 | 39 |
40 | ); 41 | -------------------------------------------------------------------------------- /components/block/block.options.ts: -------------------------------------------------------------------------------- 1 | import { ColorType, HorizontalAlignType } from "../../types"; 2 | import { BlockRoundedType } from "./background.options"; 3 | import { SpaceType } from "./spacing.options"; 4 | import { WidthType } from "./width.options"; 5 | 6 | export type BlockThemeType = { 7 | padding?: SpaceType; 8 | margin?: SpaceType; 9 | background?: ColorType; 10 | outerBackground?: ColorType; 11 | text?: ColorType; 12 | rounded?: BlockRoundedType; 13 | width?: WidthType; 14 | align?: HorizontalAlignType; 15 | }; 16 | -------------------------------------------------------------------------------- /components/block/width.options.ts: -------------------------------------------------------------------------------- 1 | export const WIDTH_OPTIONS = { 2 | full: "Full", 3 | outer: "Outer", 4 | inner: "Inner", 5 | }; 6 | export type WidthType = keyof typeof WIDTH_OPTIONS; 7 | 8 | export const widthClasses: Record = { 9 | full: "max-w-full", 10 | outer: "max-w-[1760px]", 11 | inner: "max-w-[1370px]", 12 | }; 13 | -------------------------------------------------------------------------------- /components/block/width.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Width as WidthComponent } from "./Width"; 2 | import { WidthType, WIDTH_OPTIONS } from "./width.options"; 3 | import { Meta } from "@storybook/react"; 4 | import React from "react"; 5 | 6 | export default { 7 | component: WidthComponent, 8 | title: "Components/Block/Width", 9 | } as Meta; 10 | 11 | export const Widths = () => ( 12 |
13 | {(Object.keys(WIDTH_OPTIONS) as WidthType[]).map((width: WidthType) => ( 14 |
15 | 16 |
{width}
17 |
18 |
19 | ))} 20 |
21 | ); 22 | -------------------------------------------------------------------------------- /components/breadcrumb/breadcrumb.stories.tsx: -------------------------------------------------------------------------------- 1 | import { DEMO_FLAT_BREADCRUMB } from "../../test/fixtures/breadcrumb"; 2 | import { Breadcrumb as BreadcrumbComponent } from "./Breadcrumb"; 3 | import { Meta } from "@storybook/react"; 4 | import React from "react"; 5 | 6 | export default { 7 | title: "Components/Breadcrumb", 8 | } as Meta; 9 | 10 | export const Breadcrumb = () => { 11 | return ; 12 | }; 13 | 14 | export const BreadcrumbLevel1 = () => { 15 | return ; 16 | }; 17 | 18 | BreadcrumbLevel1.story = { 19 | parameters: { 20 | nextRouter: { 21 | asPath: "/page1", 22 | }, 23 | }, 24 | }; 25 | 26 | export const BreadcrumbLevel2 = () => { 27 | return ; 28 | }; 29 | 30 | BreadcrumbLevel2.story = { 31 | parameters: { 32 | nextRouter: { 33 | asPath: "/page1/page2", 34 | }, 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /components/buttons/TextTransform.tsx: -------------------------------------------------------------------------------- 1 | import { processString } from "../../helpers/utils/string"; 2 | import React from "react"; 3 | 4 | /** 5 | * Change link like text to anchors, e.g https://google.com becomes a clickable link 6 | * used for data from an external source in the table component 7 | */ 8 | 9 | export type TextTransformerType = { 10 | regex: RegExp; 11 | fn: (key: number, result: RegExpExecArray) => React.ReactNode; 12 | }; 13 | 14 | export const TextTransform = ({ 15 | children, 16 | transformers = [], 17 | }: { 18 | children: React.ReactElement; 19 | transformers: TextTransformerType[]; 20 | }) => { 21 | let config = [ 22 | { 23 | regex: /(http|https):\/\/(\S+)\.([a-z]{2,}?)(.*?)( |\,|$|\.)/gim, 24 | fn: (key: number, result: RegExpExecArray) => ( 25 | 26 | 32 | {result[2]}.{result[3]} 33 | {result[4]} 34 | 35 | {result[5]} 36 | 37 | ), 38 | }, 39 | ...transformers, 40 | ]; 41 | 42 | let processed = processString(config)(children); 43 | return processed; 44 | }; 45 | -------------------------------------------------------------------------------- /components/buttons/button.options.ts: -------------------------------------------------------------------------------- 1 | export const BUTTON_ICON_POSITION_OPTIONS = { 2 | before: "Before", 3 | after: "After", 4 | }; 5 | export type ButtonIconPositionType = keyof typeof BUTTON_ICON_POSITION_OPTIONS; 6 | -------------------------------------------------------------------------------- /components/buttons/button.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, fireEvent, screen } from "../../jest.utils"; 2 | import { Button } from "./Button"; 3 | import "@testing-library/jest-dom"; 4 | import mockRouter from "next-router-mock"; 5 | import singletonRouter from "next/router"; 6 | 7 | jest.mock("next/dist/client/router", () => require("next-router-mock")); 8 | 9 | describe.skip("Button", () => { 10 | it("fires onClick", () => { 11 | mockRouter.setCurrentUrl("/"); 12 | 13 | render( 14 |