├── .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 |
16 | );
17 |
18 | export const BleedSmall = () => (
19 |
24 | );
25 |
26 | export const BleedDefault = () => (
27 |
32 | );
33 |
34 | export const BleedLarge = () => (
35 |
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 |