├── .editorconfig ├── .env.sample ├── .eslintignore ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── assertions.yml │ ├── chromatic.yml │ ├── publish.yml │ └── security.yml ├── .gitignore ├── .markdownlint.json ├── .markdownlintignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── .storybook ├── main.js ├── manager.js ├── preview.js └── theme.js ├── .stylelintignore ├── .stylelintrc.js ├── .vscode ├── extensions.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── __tests__ └── jest │ └── components │ ├── atoms │ ├── Breadcrumbs.test.js │ ├── Button.test.js │ ├── Code.test.js │ ├── Container.test.js │ ├── Heading.test.js │ ├── Icon.test.js │ ├── Inputs │ │ ├── Checkbox.test.js │ │ └── CheckboxGroup.test.js │ ├── Logo.test.js │ ├── PullQuote.test.js │ ├── Quote.test.js │ ├── RichText.test.js │ ├── Separator.test.js │ ├── Spacer.test.js │ └── Table.test.js │ └── molecules │ ├── Alert.test.js │ ├── ButtonGroup.test.js │ ├── Card.test.js │ ├── Comments.test.js │ ├── Form.test.js │ ├── ImageGallery.test.js │ └── Navigation.test.js ├── backend ├── README.md └── composer.json ├── components ├── atoms │ ├── Breadcrumbs │ │ ├── Breadcrumbs.js │ │ ├── Breadcrumbs.module.css │ │ ├── Breadcrumbs.stories.mdx │ │ └── index.js │ ├── Button │ │ ├── Button.js │ │ ├── Button.module.css │ │ ├── Button.stories.mdx │ │ └── index.js │ ├── Code │ │ ├── Code.js │ │ ├── Code.module.css │ │ ├── Code.stories.mdx │ │ └── index.js │ ├── Columns │ │ ├── Columns.js │ │ ├── Columns.module.css │ │ └── index.js │ ├── Container │ │ ├── Container.js │ │ ├── Container.module.css │ │ ├── Container.stories.mdx │ │ └── index.js │ ├── ExitPreview │ │ ├── ExitPreview.js │ │ └── index.js │ ├── Heading │ │ ├── Heading.js │ │ ├── Heading.stories.mdx │ │ └── index.js │ ├── Icon │ │ ├── Icon.js │ │ ├── Icon.stories.mdx │ │ ├── icons.js │ │ └── index.js │ ├── Image │ │ ├── Image.js │ │ ├── Image.module.css │ │ ├── Image.stories.mdx │ │ └── index.js │ ├── Inputs │ │ ├── Checkbox │ │ │ ├── Checkbox.js │ │ │ └── index.js │ │ ├── CheckboxGroup │ │ │ ├── CheckboxGroup.js │ │ │ └── index.js │ │ ├── InputError │ │ │ ├── InputError.js │ │ │ ├── InputError.module.css │ │ │ └── index.js │ │ ├── Select │ │ │ ├── Select.js │ │ │ ├── Select.module.css │ │ │ └── index.js │ │ ├── Text │ │ │ ├── Text.js │ │ │ ├── Text.module.css │ │ │ └── index.js │ │ └── index.js │ ├── Logo │ │ ├── Logo.js │ │ ├── Logo.stories.mdx │ │ └── index.js │ ├── PullQuote │ │ ├── PullQuote.js │ │ ├── PullQuote.module.css │ │ ├── PullQuote.stories.mdx │ │ └── index.js │ ├── Quote │ │ ├── Quote.js │ │ ├── Quote.module.css │ │ ├── Quote.stories.mdx │ │ └── index.js │ ├── RichText │ │ ├── RichText.js │ │ ├── RichText.module.css │ │ ├── RichText.stories.mdx │ │ └── index.js │ ├── Separator │ │ ├── Separator.js │ │ ├── Separator.module.css │ │ ├── Separator.stories.mdx │ │ └── index.js │ ├── Spacer │ │ ├── Spacer.js │ │ ├── Spacer.stories.mdx │ │ └── index.js │ ├── Table │ │ ├── Table.js │ │ ├── Table.module.css │ │ ├── Table.stories.mdx │ │ └── index.js │ ├── TwitterEmbed │ │ ├── TwitterEmbed.js │ │ ├── TwitterEmbed.module.css │ │ ├── TwitterEmbed.stories.mdx │ │ └── index.js │ └── VideoEmbed │ │ ├── VideoEmbed.js │ │ ├── VideoEmbed.module.css │ │ ├── VideoEmbed.stories.mdx │ │ └── index.js ├── blocks │ ├── core │ │ ├── BlockButton │ │ │ ├── BlockButton.js │ │ │ └── index.js │ │ ├── BlockButtons │ │ │ ├── BlockButtons.js │ │ │ └── index.js │ │ ├── BlockCode │ │ │ ├── BlockCode.js │ │ │ └── index.js │ │ ├── BlockColumns │ │ │ ├── BlockColumns.js │ │ │ └── index.js │ │ ├── BlockCover │ │ │ ├── BlockCover.js │ │ │ └── index.js │ │ ├── BlockEmbed │ │ │ ├── BlockEmbed.js │ │ │ └── index.js │ │ ├── BlockGravityForm │ │ │ ├── BlockGravityForm.js │ │ │ └── index.js │ │ ├── BlockHeadings │ │ │ ├── BlockHeadings.js │ │ │ └── index.js │ │ ├── BlockImage │ │ │ ├── BlockImage.js │ │ │ └── index.js │ │ ├── BlockImageGallery │ │ │ ├── BlockImageGallery.js │ │ │ └── index.js │ │ ├── BlockList │ │ │ ├── BlockList.js │ │ │ └── index.js │ │ ├── BlockMediaText │ │ │ ├── BlockMediaText.js │ │ │ └── index.js │ │ ├── BlockParagraph │ │ │ ├── BlockParagraph.js │ │ │ └── index.js │ │ ├── BlockPullQuote │ │ │ ├── BlockPullQuote.js │ │ │ └── index.js │ │ ├── BlockQuote │ │ │ ├── BlockQuote.js │ │ │ └── index.js │ │ ├── BlockSeparator │ │ │ ├── BlockSeparator.js │ │ │ └── index.js │ │ ├── BlockShortcode │ │ │ ├── BlockShortcode.js │ │ │ └── index.js │ │ ├── BlockSpacer │ │ │ ├── BlockSpacer.js │ │ │ └── index.js │ │ └── BlockTable │ │ │ ├── BlockTable.js │ │ │ └── index.js │ └── index.js ├── common │ ├── Layout.js │ ├── Meta.js │ └── WordPressProvider.js ├── documentation │ ├── DocTable │ │ ├── DocTable.js │ │ ├── DocTable.module.css │ │ └── index.js │ ├── Field.stories.mdx │ ├── Icons.stories.mdx │ └── README.md ├── molecules │ ├── Alert │ │ ├── Alert.js │ │ ├── Alert.module.css │ │ ├── Alert.stories.mdx │ │ └── index.js │ ├── AlgoliaResults │ │ ├── AlgoliaResults.js │ │ ├── AlgoliaResults.module.css │ │ ├── facets │ │ │ ├── Authors.js │ │ │ ├── PostType.js │ │ │ └── Sort.js │ │ ├── index.js │ │ ├── refinements │ │ │ ├── CustomClearRefinements.js │ │ │ ├── CustomMenu.js │ │ │ ├── CustomRefinementList.js │ │ │ └── CustomToggleRefinement.js │ │ └── templates │ │ │ ├── Hit.js │ │ │ ├── NoResults.js │ │ │ └── SearchResults.js │ ├── AlgoliaSearch │ │ ├── AlgoliaSearch.js │ │ ├── AlgoliaSearch.module.css │ │ ├── components │ │ │ ├── History.js │ │ │ ├── Hit.js │ │ │ ├── Results.js │ │ │ ├── Search.js │ │ │ ├── SearchIcon.js │ │ │ └── SearchPlaceholder.js │ │ ├── functions │ │ │ ├── buildSearchUrl.js │ │ │ ├── localStorage.js │ │ │ ├── searchClick.js │ │ │ └── searchSubmit.js │ │ └── index.js │ ├── Blocks │ │ ├── Blocks.js │ │ ├── Blocks.stories.mdx │ │ └── index.js │ ├── ButtonGroup │ │ ├── ButtonGroup.js │ │ ├── ButtonGroup.module.css │ │ ├── ButtonGroup.stories.mdx │ │ └── index.js │ ├── Card │ │ ├── Card.js │ │ ├── Card.module.css │ │ ├── Card.stories.mdx │ │ └── index.js │ ├── Comments │ │ ├── Comments.js │ │ └── index.js │ ├── Form │ │ ├── Form.js │ │ ├── Form.module.css │ │ ├── Form.stories.mdx │ │ └── index.js │ ├── GravityForm │ │ ├── Fields │ │ │ ├── Checkbox.js │ │ │ ├── Fields.js │ │ │ ├── File.js │ │ │ ├── Select.js │ │ │ ├── Text.js │ │ │ ├── Textarea.js │ │ │ └── index.js │ │ ├── GravityForm.js │ │ ├── GravityForm.module.css │ │ ├── GravityForm.stories.mdx │ │ └── index.js │ ├── ImageGallery │ │ ├── ImageGallery.js │ │ ├── ImageGallery.module.css │ │ ├── ImageGallery.stories.mdx │ │ └── index.js │ └── Navigation │ │ ├── Navigation.js │ │ ├── Navigation.module.css │ │ └── index.js └── organisms │ ├── Archive │ ├── Archive.js │ ├── Archive.stories.mdx │ └── index.js │ ├── Footer │ ├── Footer.js │ ├── Footer.module.css │ ├── Footer.stories.mdx │ └── index.js │ ├── Header │ ├── Header.js │ ├── Header.module.css │ ├── Header.stories.mdx │ └── index.js │ ├── Hero │ ├── Hero.js │ ├── Hero.module.css │ ├── Hero.stories.mdx │ └── index.js │ └── MediaText │ ├── MediaText.js │ ├── MediaText.module.css │ ├── MediaText.stories.mdx │ └── index.js ├── docs ├── .markdownlint.json ├── .markdownlintignore ├── .prettierignore ├── .prettierrc.js ├── README.md ├── babel.config.js ├── docs │ ├── backend │ │ ├── _category_.json │ │ ├── algolia.md │ │ ├── comments.md │ │ ├── gravity-forms.md │ │ ├── index.md │ │ ├── menus.md │ │ └── wp-config.md │ ├── contributing.md │ ├── frontend │ │ ├── _category_.json │ │ ├── coding-standards.md │ │ ├── component-block-handler.md │ │ ├── component-css-module.md │ │ ├── component-js.md │ │ ├── components-overview.md │ │ ├── env-variables-and-vercel.md │ │ ├── env-variables.md │ │ ├── folder-structure.md │ │ ├── index.md │ │ ├── menus.md │ │ ├── overview.md │ │ └── previews.md │ ├── index.md │ ├── learn │ │ ├── _category_.json │ │ ├── builds.md │ │ ├── comments.md │ │ ├── creating-content.md │ │ ├── custom-post-types.md │ │ ├── settings-page.md │ │ ├── wordpress-theme.md │ │ └── wp-graphql.md │ ├── other │ │ ├── 3rd-party-services.md │ │ ├── _category_.json │ │ ├── docusaurus.md │ │ ├── internal-documentation-for-wds.md │ │ ├── npm-workflows.md │ │ └── recommended-extensions.md │ └── storybook │ │ ├── _category_.json │ │ └── index.md ├── docusaurus.config.js ├── lefthook-docs.yml ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ ├── components │ │ ├── Features.js │ │ ├── Features.module.css │ │ ├── GetStarted.js │ │ ├── GetStarted.module.css │ │ ├── Hero.js │ │ ├── Hero.module.css │ │ ├── HowItWorks.js │ │ ├── HowItWorks.module.css │ │ ├── Plugins.js │ │ └── Plugins.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ ├── index.module.css │ │ └── old-index.md ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ ├── nextjs-wordpress-starter-frontend-backend-graphic-vertical.webp │ │ ├── nextjs-wordpress-starter-frontend-backend-graphic.webp │ │ ├── screenshot-activate-all-plugins.png │ │ ├── screenshot-activate-graphql-gutenberg.png │ │ ├── screenshot-env-vars-vercel.png │ │ ├── screenshot-frontend.png │ │ ├── screenshot-github-actions.png │ │ ├── screenshot-headless-theme.png │ │ ├── screenshot-local-by-flywheel.png │ │ ├── screenshot-npm-run-dev.png │ │ ├── screenshot-set-404-page-2.png │ │ ├── screenshot-set-404-page.png │ │ ├── screenshot-set-algolia-creds.png │ │ ├── screenshot-set-application-password.png │ │ ├── screenshot-set-menus.png │ │ ├── screenshot-set-page-options.png │ │ ├── screenshot-set-permalinks.png │ │ ├── screenshot-setup-algolia-account.png │ │ ├── screenshot-setup-gravity-forms.png │ │ ├── screenshot-tgm-theme.png │ │ ├── screenshot-wpe-prod-release.png │ │ ├── wds-logo-60x60.png │ │ └── wds-logo-60x60.webp └── tailwind.config.js ├── functions ├── convertHextoRgb.js ├── createMarkup.js ├── extractRgbValues.js ├── formatFocalPoint.js ├── getEnvVar.js ├── getPagePropTypes.js ├── isLinkActive.js ├── middleware │ └── gfMultipartFormParser.js ├── next-api │ ├── README.md │ └── wordpress │ │ ├── README.md │ │ ├── archive │ │ └── getArchivePosts.js │ │ ├── comments │ │ └── processPostComment.js │ │ └── gravityForms │ │ ├── processGfFieldValues.js │ │ └── processGfFormSubmission.js ├── parseQuerystring.js └── wordpress │ ├── README.md │ ├── auth │ ├── loginUser.js │ ├── refreshAuthToken.js │ └── registerUser.js │ ├── blocks │ ├── displayBlock.js │ ├── formatBlockData.js │ └── getBlockStyles.js │ ├── comments │ └── insertPostComment.js │ ├── gravityForms │ ├── encodeGfFormData.js │ ├── getGfFieldId.js │ ├── getGfFormById.js │ ├── getGfFormDefaults.js │ ├── getGfFormValidationSchema.js │ ├── getHiddenClassName.js │ ├── index.js │ ├── insertGfFormEntry.js │ └── yupSchema │ │ ├── ArraySchemaFactory.js │ │ └── StringSchemaFactory.js │ ├── media │ └── getMediaByID.js │ ├── menus │ ├── filterMenusByLocation.js │ ├── formatHeirarchialMenu.js │ └── getMenus.js │ ├── postTypes │ ├── getFrontendPage.js │ ├── getHeadlessConfigPage.js │ ├── getPostTypeArchive.js │ ├── getPostTypeById.js │ ├── getPostTypeStaticPaths.js │ ├── getPostTypeStaticProps.js │ ├── getPostTypeTaxonomyArchive.js │ ├── isHierarchicalPostType.js │ ├── isValidPostType.js │ └── processPostTypeQuery.js │ ├── posts │ └── getPostsDateArchive.js │ ├── seo │ ├── formatArchiveSeoData.js │ ├── formatDefaultSeoData.js │ └── formatManualSeoMeta.js │ └── taxonomies │ ├── getTaxonomyStaticPaths.js │ ├── getTaxonomyStaticProps.js │ └── isValidTaxonomy.js ├── jest.config.js ├── jest.setup.js ├── jsconfig.json ├── lefthook-frontend.yml ├── lefthook.yml ├── lib ├── algolia │ ├── README.md │ └── connector.js ├── apolloConfig.js ├── next-api │ ├── README.md │ ├── connector.js │ └── wordpress │ │ ├── README.md │ │ ├── _config │ │ └── schema.js │ │ ├── archive │ │ └── queryArchivePosts.js │ │ ├── comments │ │ └── mutationAddComment.js │ │ └── gravityForms │ │ └── mutationSubmitForm.js └── wordpress │ ├── README.md │ ├── _config │ ├── archiveQuerySeo.js │ ├── frontendPageSeo.js │ ├── headlessConfigPageQuerySeo.js │ ├── menuLocations.js │ ├── postTypes.js │ └── taxonomies.js │ ├── _query-partials │ ├── allMenus.js │ ├── archiveData.js │ ├── archivePageInfo.js │ ├── archiveSeo.js │ ├── authorPostFields.js │ ├── categoriesPostFields.js │ ├── commentsFields.js │ ├── commentsPostFields.js │ ├── defaultPageData.js │ ├── defaultSeoFields.js │ ├── featuredImagePostFields.js │ ├── globalPostFields.js │ ├── seoPostFields.js │ └── tagsPostFields.js │ ├── auth │ ├── mutationLoginUser.js │ ├── mutationRefreshAuthToken.js │ └── mutationRegisterUser.js │ ├── categories │ └── queryPostsByCategory.js │ ├── comments │ ├── mutationInsertComment.js │ └── queryCommentsByPostId.js │ ├── connector.js │ ├── gravityForms │ ├── fieldProps.js │ ├── mutationInsertFormEntry.js │ └── queryFormById.js │ ├── media │ └── queryMediaAttributes.js │ ├── pages │ ├── queryDefaultPageData.js │ ├── queryError404Page.js │ └── queryPageById.js │ ├── posts │ ├── queryPostById.js │ ├── queryPostsArchive.js │ └── queryPostsDateArchive.js │ └── tags │ └── queryPostsByTag.js ├── next-sitemap.js ├── next.config.js ├── package-lock.json ├── package.json ├── packages └── README.md ├── pages ├── 404.js ├── 500.js ├── [...slug].js ├── [year] │ └── [month] │ │ └── [day] │ │ └── [slug].js ├── _app.js ├── _document.js ├── api │ ├── auth │ │ └── [...nextauth].js │ ├── exit-preview.js │ ├── preview.js │ └── wordpress │ │ ├── archive.js │ │ ├── comments.js │ │ ├── gravityForms.js │ │ └── revalidate.js ├── category │ └── [...slug].js ├── index.js ├── login.js ├── profile │ └── index.js ├── register.js ├── search.js └── tags │ └── [...slug].js ├── postcss.config.js ├── public ├── favicon │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-512x512.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── maskable_icon.png │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── ms-icon-70x70.png │ └── site.webmanifest ├── images │ ├── wds-logo-inverse.svg │ └── wds-logo.svg ├── robots.txt ├── sitemap-0.xml └── sitemap.xml ├── scripts └── .gitkeep ├── styles ├── demo.css └── index.css └── tailwind.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !/.*.js 2 | *.min.js 3 | .*cache 4 | .next/ 5 | backend/ 6 | build/ 7 | dist/ 8 | docs/ 9 | node_modules/ 10 | public/ 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring/ 2 | module.exports = { 3 | env: { 4 | browser: true, 5 | node: true, 6 | es6: true 7 | }, 8 | extends: [ 9 | 'eslint:recommended', 10 | 'plugin:jsdoc/recommended', 11 | 'plugin:jest/recommended', 12 | 'next', 13 | 'prettier' 14 | ], 15 | settings: { 16 | jsdoc: { 17 | tagNamePreference: { 18 | returns: 'return' 19 | } 20 | } 21 | }, 22 | plugins: ['prettier', 'jsdoc'], 23 | rules: { 24 | '@next/next/no-img-element': 'off', 25 | 'func-style': ['error', 'declaration'], 26 | 'jsdoc/check-indentation': 'warn', 27 | 'jsdoc/check-line-alignment': [ 28 | 'warn', 29 | 'always', 30 | { 31 | tags: ['param', 'return'] 32 | } 33 | ], 34 | 'jsdoc/require-param': [ 35 | 'warn', 36 | { 37 | checkRestProperty: true, 38 | unnamedRootBase: ['props'] 39 | } 40 | ], 41 | 'jsdoc/check-values': [ 42 | 'warn', 43 | { 44 | allowedAuthors: ['WebDevStudios'] 45 | } 46 | ], 47 | 'jsx-a11y/anchor-is-valid': 'off', 48 | 'no-console': ['error', {allow: ['warn', 'error']}], 49 | 'prettier/prettier': 'error' 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code Owners 2 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | Thanks for taking the time to file a bug report! Please fill out this form as completely as possible. 10 | 11 | If you leave out sections there is a high likelihood it will be moved to our [GitHub Discussions](https://github.com/WebDevStudios/nextjs-wordpress-starter/discussions). 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Desktop (please complete the following information):** 31 | 32 | - OS: [e.g. iOS] 33 | - Browser [e.g. chrome, safari] 34 | - Version [e.g. 22] 35 | 36 | **Smartphone (please complete the following information):** 37 | 38 | - Device: [e.g. iPhone6] 39 | - OS: [e.g. iOS8.1] 40 | - Browser [e.g. stock browser, safari] 41 | - Version [e.g. 22] 42 | 43 | **Additional context** 44 | Add any other context about the problem here. 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Please create your feature request as a [GitHub Discussion](https://github.com/WebDevStudios/nextjs-wordpress-starter/discussions).** 10 | 11 | Thank you! 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Closes # 2 | 3 | ### Description 4 | 5 | What does your Pull Request do? Give some context... 6 | 7 | ### Screenshot 8 | 9 | If possible, add some screenshots of your feature. 10 | 11 | ### Verification 12 | 13 | How will a stakeholder test this? 14 | 15 | 1. 16 | 2. 17 | 3. 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | 5 | - package-ecosystem: 'npm' 6 | directory: '/' 7 | schedule: 8 | interval: 'weekly' 9 | day: 'monday' 10 | 11 | - package-ecosystem: 'npm' 12 | directory: '/docs' 13 | schedule: 14 | interval: 'weekly' 15 | day: 'monday' 16 | -------------------------------------------------------------------------------- /.github/workflows/assertions.yml: -------------------------------------------------------------------------------- 1 | name: Assertions 2 | 3 | on: 4 | pull_request: 5 | branches: main 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v3 16 | with: 17 | token: ${{ github.token }} 18 | 19 | - name: Setup Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 'lts/*' 23 | cache: 'npm' 24 | 25 | - name: Install Dependencies 26 | run: | 27 | npm i --legacy-peer-deps 28 | cd docs 29 | npm i --legacy-peer-deps 30 | 31 | - name: Lint Docs 32 | run: | 33 | cd docs 34 | npm run lint 35 | 36 | - name: Lint Frontend 37 | run: | 38 | npm run lint 39 | 40 | - name: Run Frontend Tests 41 | run: | 42 | npm run test:jest 43 | -------------------------------------------------------------------------------- /.github/workflows/chromatic.yml: -------------------------------------------------------------------------------- 1 | name: Chromatic 2 | 3 | on: 4 | pull_request: 5 | branches: main 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | chromatic: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@v3 16 | with: 17 | token: ${{ github.token }} 18 | fetch-depth: 0 19 | 20 | - name: Setup Node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 'lts/*' 24 | cache: 'npm' 25 | 26 | - name: Install Dependencies 27 | run: npm i --legacy-peer-deps 28 | 29 | - name: Publish to Chromatic 30 | uses: chromaui/action@v1 31 | with: 32 | token: ${{ github.token }} 33 | projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} 34 | workingDir: / 35 | exitZeroOnChanges: true 36 | exitOnceUploaded: true 37 | skip: 'dependabot/**' 38 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | name: 'Security' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: ['javascript'] 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v3 26 | 27 | - name: Initialize CodeQL 28 | uses: github/codeql-action/init@v2 29 | with: 30 | languages: ${{ matrix.language }} 31 | queries: security-extended 32 | 33 | - name: Perform CodeQL Analysis 34 | uses: github/codeql-action/analyze@v2 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | /overage 10 | 11 | # next.js 12 | .next 13 | out 14 | .vercel 15 | 16 | # production 17 | build 18 | 19 | # misc 20 | .DS_Store 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | wpe.json 34 | 35 | # storybook 36 | build-storybook.log 37 | storybook-static/* 38 | 39 | # docusaurus 40 | .docusaurus 41 | 42 | # composer 43 | vendor 44 | 45 | # vercel 46 | .vercel 47 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD003": {"style": "atx"}, 3 | "MD007": {"indent": 2}, 4 | "MD026": {"punctuation": ".,;!。,;:!"}, 5 | "default": true, 6 | "line-length": false, 7 | "no-duplicate-heading": false, 8 | "no-hard-tabs": false, 9 | "no-inline-html": false, 10 | "ol-prefix": false, 11 | "whitespace": false 12 | } 13 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | !/.*.js 2 | *.min.js 3 | .*cache 4 | .next/ 5 | __tests__/ 6 | backend/ 7 | build/ 8 | dist/ 9 | docs/ 10 | node_modules/ 11 | public/ 12 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | !/.*.js 2 | *.min.js 3 | .*cache 4 | .next/ 5 | build/ 6 | dist/ 7 | docs/ 8 | node_modules/ 9 | public/ 10 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // https://prettier.io/docs/en/configuration.html 2 | module.exports = { 3 | tabWidth: 2, 4 | useTabs: false, 5 | singleQuote: true, 6 | bracketSpacing: false, 7 | semi: false, 8 | trailingComma: 'none' 9 | } 10 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import {addons} from '@storybook/addons' 2 | import theme from './theme' 3 | 4 | /** 5 | * Configure Storybook features and behavior. 6 | * 7 | * @see https://storybook.js.org/docs/react/configure/features-and-behavior 8 | */ 9 | addons.setConfig({ 10 | isFullscreen: false, 11 | showNav: true, 12 | showPanel: true, 13 | panelPosition: 'bottom', 14 | sidebarAnimations: true, 15 | enableShortcuts: true, 16 | isToolshown: true, 17 | theme: theme, 18 | selectedPanel: undefined, 19 | initialActive: 'sidebar', 20 | showRoots: false 21 | }) 22 | -------------------------------------------------------------------------------- /.storybook/theme.js: -------------------------------------------------------------------------------- 1 | import {create} from '@storybook/theming/create' 2 | 3 | /** 4 | * Configure Storybook theme. 5 | * 6 | * @see https://storybook.js.org/docs/react/configure/theming#create-a-theme-quickstart 7 | */ 8 | export default create({ 9 | base: 'light', 10 | brandTitle: 'Next.js WordPress Starter', 11 | brandUrl: 'https://nextjs-wordpress-starter.vercel.app' 12 | }) 13 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | !/.*.js 2 | *.min.js 3 | .*cache 4 | .next/ 5 | __tests__/ 6 | backend/ 7 | build/ 8 | dist/ 9 | docs/ 10 | node_modules/ 11 | public/ 12 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | // https://stylelint.io/user-guide/configure 2 | module.exports = { 3 | extends: ['stylelint-config-standard'], 4 | rules: { 5 | 'at-rule-no-unknown': [ 6 | true, 7 | { 8 | ignoreAtRules: [ 9 | 'tailwind', 10 | 'layer', 11 | 'apply', 12 | 'variants', 13 | 'responsive', 14 | 'screen' 15 | ] 16 | } 17 | ], 18 | 'declaration-block-trailing-semicolon': null, 19 | 'max-line-length': null, 20 | 'no-descending-specificity': null, 21 | 'selector-class-pattern': null, 22 | 'string-quotes': 'single' 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "DavidAnson.vscode-markdownlint", 4 | "bradlc.vscode-tailwindcss", 5 | "christian-kohler.npm-intellisense", 6 | "christian-kohler.path-intellisense", 7 | "clinyong.vscode-css-modules", 8 | "coenraads.bracket-pair-colorizer-2", 9 | "csstools.postcss", 10 | "dbaeumer.vscode-eslint", 11 | "editorconfig.editorconfig", 12 | "esbenp.prettier-vscode", 13 | "graphql.vscode-graphql", 14 | "gruntfuggly.todo-tree", 15 | "mikestead.dotenv", 16 | "naumovs.color-highlight", 17 | "nucllear.vscode-extension-auto-import", 18 | "silvenon.mdx", 19 | "stylelint.vscode-stylelint", 20 | "zignd.html-css-class-completion" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Enable format with prettier on save, configure import organizing. 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": true 6 | }, 7 | "[javascriptreact]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | 11 | // This will get emmet working in JSX. 12 | "emmet.includeLanguages": { 13 | "javascript": "javascriptreact" 14 | }, 15 | "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false 16 | } 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please see for more information. 4 | -------------------------------------------------------------------------------- /__tests__/jest/components/atoms/Breadcrumbs.test.js: -------------------------------------------------------------------------------- 1 | import Breadcrumbs from '@/components/atoms/Breadcrumbs' 2 | import {render} from '@testing-library/react' 3 | 4 | test('render Breadcrumbs', () => { 5 | const props = { 6 | breadcrumbs: [ 7 | { 8 | text: 'Home', 9 | url: 'http://localhost:3000/' 10 | }, 11 | { 12 | text: 'Blog', 13 | url: 'http://localhost:3000/blog' 14 | }, 15 | { 16 | text: 'Lorem Ipsum', 17 | url: 'http://localhost:3000/2020/07/01/lorem-ipsum' 18 | } 19 | ] 20 | } 21 | 22 | const {container} = render() 23 | 24 | expect(container.firstElementChild).toHaveClass('breadcrumbs') 25 | 26 | const lists = container.firstElementChild.querySelectorAll('li') 27 | 28 | expect(lists).toHaveLength(3) 29 | 30 | // Breadcrumbs should be in order 31 | // Home -> Blog -> Lorem Ipsum 32 | 33 | expect(lists[0].querySelector('a')).toHaveAttribute( 34 | 'href', 35 | 'http://localhost:3000/' 36 | ) 37 | expect(lists[0]).toHaveTextContent('Home') 38 | 39 | expect(lists[1].querySelector('a')).toHaveAttribute( 40 | 'href', 41 | 'http://localhost:3000/blog' 42 | ) 43 | expect(lists[1]).toHaveTextContent('Blog') 44 | 45 | expect(lists[2].querySelector('a')).toHaveAttribute( 46 | 'href', 47 | 'http://localhost:3000/2020/07/01/lorem-ipsum' 48 | ) 49 | expect(lists[2]).toHaveTextContent('Lorem Ipsum') 50 | }) 51 | -------------------------------------------------------------------------------- /__tests__/jest/components/atoms/Code.test.js: -------------------------------------------------------------------------------- 1 | import Code from '@/components/atoms/Code' 2 | import {render} from '@testing-library/react' 3 | 4 | test('render Code with id, className, content, and style props', () => { 5 | const props = { 6 | id: 'test-code-id', 7 | className: 'test-class-code', 8 | content: '

this is a code block!

', 9 | style: { 10 | backgroundColor: 'blue' 11 | } 12 | } 13 | 14 | const {container} = render() 15 | 16 | const parentDiv = container.querySelector('#test-code-id') 17 | 18 | expect(parentDiv).not.toBeNull() 19 | expect(parentDiv).toHaveStyle({ 20 | backgroundColor: 'blue' 21 | }) 22 | 23 | const codeDiv = container.querySelector('code') 24 | expect(codeDiv).toHaveClass('language-test-class-code') 25 | expect(codeDiv).toHaveTextContent('

this is a code block!

') 26 | }) 27 | -------------------------------------------------------------------------------- /__tests__/jest/components/atoms/Inputs/Checkbox.test.js: -------------------------------------------------------------------------------- 1 | import Checkbox from '@/components/atoms/Inputs/Checkbox' 2 | import Form from '@/components/molecules/Form' 3 | import {render} from '@testing-library/react' 4 | 5 | test('render Checkbox with className, id, label, name, and value', () => { 6 | const props = { 7 | className: 'checkbox-test-cls', 8 | id: 'awesome-id', 9 | label: 'Are you awesome', 10 | name: 'awesome', 11 | value: 'am-awesome' 12 | } 13 | 14 | const form = ( 15 |
16 |

Checkbox

17 | 18 | 19 | ) 20 | 21 | const {container} = render(form) 22 | 23 | const checkboxContainer = container.querySelector('.checkbox-test-cls') 24 | 25 | expect(checkboxContainer).not.toBeNull() 26 | expect(checkboxContainer.querySelector('label')).toHaveAttribute( 27 | 'for', 28 | 'awesome-id' 29 | ) 30 | expect(checkboxContainer).toHaveTextContent('Are you awesome') 31 | 32 | const checkboxInput = checkboxContainer.querySelector('input') 33 | 34 | expect(checkboxInput).toHaveAttribute('name', 'awesome') 35 | expect(checkboxInput).toHaveAttribute('type', 'checkbox') 36 | expect(checkboxInput).toHaveAttribute('value', 'am-awesome') 37 | }) 38 | -------------------------------------------------------------------------------- /__tests__/jest/components/atoms/Logo.test.js: -------------------------------------------------------------------------------- 1 | import Logo from '@/components/atoms/Logo' 2 | import {render} from '@testing-library/react' 3 | 4 | test('render Logo with className, and type-dark props', () => { 5 | const props = { 6 | className: 'logo-ctm-cls', 7 | type: 'dark' 8 | } 9 | 10 | const {container} = render() 11 | 12 | const logo = container.querySelector('svg') 13 | 14 | expect(logo).toHaveClass('logo-ctm-cls') 15 | expect(logo).toHaveAttribute('fill', '#414141') 16 | }) 17 | 18 | test('render Logo with type-light prop', () => { 19 | const props = { 20 | type: 'light' 21 | } 22 | 23 | const {container} = render() 24 | 25 | const logo = container.querySelector('svg') 26 | 27 | expect(logo).toHaveAttribute('fill', '#f9fbfd') 28 | }) 29 | -------------------------------------------------------------------------------- /__tests__/jest/components/atoms/Quote.test.js: -------------------------------------------------------------------------------- 1 | import Quote from '@/components/atoms/Quote' 2 | import {render} from '@testing-library/react' 3 | 4 | test('render Quote with className, id, style and value props', () => { 5 | const props = { 6 | className: 'ctm-cls-q', 7 | id: 'ctm-id-q', 8 | value: 9 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sed molestie lorem.' 10 | } 11 | 12 | const {container} = render() 13 | 14 | expect(container.firstElementChild).toHaveClass('ctm-cls-q') 15 | expect(container.firstElementChild).toHaveAttribute('id', 'ctm-id-q') 16 | expect(container).toHaveTextContent( 17 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sed molestie lorem.' 18 | ) 19 | }) 20 | 21 | test('render Quote with citation and value props', () => { 22 | const props = { 23 | citation: 'Lorem', 24 | value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' 25 | } 26 | 27 | const {container} = render() 28 | 29 | expect(container).toHaveTextContent( 30 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' 31 | ) 32 | expect(container.querySelector('.cite')).toHaveTextContent('Lorem') 33 | }) 34 | -------------------------------------------------------------------------------- /__tests__/jest/components/atoms/RichText.test.js: -------------------------------------------------------------------------------- 1 | import RichText from '@/components/atoms/RichText' 2 | import {render} from '@testing-library/react' 3 | 4 | test('render RichText with children, className, dropCap and style props', () => { 5 | const props = { 6 | className: 'custom-rt-cls', 7 | dropCap: true, 8 | style: { 9 | backgroundColor: 'red' 10 | } 11 | } 12 | 13 | const {container} = render( 14 | This is a rich text example. 15 | ) 16 | 17 | expect(container.firstElementChild).toHaveClass('dropcap custom-rt-cls') 18 | expect(container.firstElementChild).toHaveStyle({ 19 | backgroundColor: 'red' 20 | }) 21 | expect(container).toHaveTextContent('This is a rich text example.') 22 | }) 23 | 24 | test('render RichText with attributes, children, id and tag props', () => { 25 | const props = { 26 | attributes: { 27 | 'data-att': true 28 | }, 29 | id: 'rt-ctm-id', 30 | tag: 'span' 31 | } 32 | 33 | const {container} = render( 34 | This is a span example. 35 | ) 36 | 37 | const richTextSpan = container.querySelector('span') 38 | 39 | expect(richTextSpan).not.toHaveClass('dropcap') 40 | expect(richTextSpan).toHaveAttribute('id', 'rt-ctm-id') 41 | expect(richTextSpan).toHaveAttribute('data-att', 'true') 42 | expect(richTextSpan).toHaveTextContent('This is a span example.') 43 | }) 44 | -------------------------------------------------------------------------------- /__tests__/jest/components/atoms/Separator.test.js: -------------------------------------------------------------------------------- 1 | import Separator from '@/components/atoms/Separator' 2 | import {render} from '@testing-library/react' 3 | 4 | test('render Separator with anchor, and className props', () => { 5 | const props = { 6 | anchor: 'test-anchor', 7 | className: 'test-cls' 8 | } 9 | 10 | const {container} = render() 11 | 12 | expect(container.firstElementChild).toHaveClass('test-cls') 13 | expect(container.firstElementChild).toHaveAttribute('id', 'test-anchor') 14 | }) 15 | -------------------------------------------------------------------------------- /__tests__/jest/components/atoms/Spacer.test.js: -------------------------------------------------------------------------------- 1 | import Spacer from '@/components/atoms/Spacer' 2 | import {render} from '@testing-library/react' 3 | 4 | test('render Spacer with height prop', () => { 5 | const props = { 6 | height: 12 7 | } 8 | 9 | const {container} = render() 10 | 11 | const computedHeight = 12 / 16 12 | 13 | expect(container.firstElementChild).toHaveStyle({ 14 | height: `${computedHeight}rem` 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /__tests__/jest/components/molecules/ButtonGroup.test.js: -------------------------------------------------------------------------------- 1 | import ButtonGroup from '@/components/molecules/ButtonGroup' 2 | import {render} from '@testing-library/react' 3 | 4 | test('render ButtonGroup with id, orientation=horizontal, contentJustification=left, and children props', () => { 5 | const props = { 6 | id: 'test-id-btngrp', 7 | orientation: 'horizontal', 8 | contentJustification: 'left' 9 | } 10 | 11 | const {container} = render( 12 | 13 | 14 | 15 | ) 16 | 17 | expect(container.firstElementChild).toHaveClass('horizontal left') 18 | expect(container.firstElementChild).toHaveAttribute('id', 'test-id-btngrp') 19 | expect(container.firstElementChild.innerHTML).toBe( 20 | '' 21 | ) 22 | }) 23 | 24 | test('render ButtonGroup with id, orientation=vertical, contentJustification=right, and children props', () => { 25 | const props = { 26 | orientation: 'vertical', 27 | contentJustification: 'right' 28 | } 29 | 30 | const {container} = render( 31 | 32 |
Lorem ipsum
33 |
34 | ) 35 | 36 | expect(container.firstElementChild).toHaveClass('vertical right') 37 | expect(container.firstElementChild.innerHTML).toBe('
Lorem ipsum
') 38 | }) 39 | -------------------------------------------------------------------------------- /__tests__/jest/components/molecules/Form.test.js: -------------------------------------------------------------------------------- 1 | import Text from '@/components/atoms/Inputs/Text' 2 | import Form from '@/components/molecules/Form' 3 | import {render} from '@testing-library/react' 4 | 5 | test('render Form with children, className, formDefaults id, title, and onSubmit props', () => { 6 | const props = { 7 | className: 'test-form-cls', 8 | formDefaults: { 9 | username: '', 10 | password: '' 11 | }, 12 | id: 'test-form-id', 13 | title: 'Test form', 14 | onSubmit: () => {} 15 | } 16 | 17 | const {container} = render( 18 |
19 |

Login Form

20 | 21 | 22 | 23 | ) 24 | 25 | const form = container.querySelector('#test-form-id') 26 | 27 | expect(form).toHaveClass('test-form-cls') 28 | 29 | expect(form.querySelector('h1')).toHaveTextContent('Login Form') 30 | 31 | expect(form.querySelector('input#username')).not.toBeNull() 32 | expect(form.querySelector('input#password')).not.toBeNull() 33 | expect(form.querySelector('button[type="submit"]')).not.toBeNull() 34 | }) 35 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Backend Setup 2 | 3 | To learn more about setting up the backend, please [read the documentation](https://webdevstudios.github.io/nextjs-wordpress-starter/docs/backend). 4 | -------------------------------------------------------------------------------- /components/atoms/Breadcrumbs/Breadcrumbs.js: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import Link from 'next/link' 3 | import PropTypes from 'prop-types' 4 | import styles from './Breadcrumbs.module.css' 5 | 6 | /** 7 | * Render the Breadcrumbs component. 8 | * 9 | * @author WebDevStudios 10 | * @param {object} props The component attributes as props. 11 | * @param {Array} props.breadcrumbs The breadcrumb array. 12 | * @return {Element} The Breadcrumbs component. 13 | */ 14 | export default function Breadcrumbs({breadcrumbs}) { 15 | return ( 16 | <> 17 | {!!breadcrumbs?.length && ( 18 |
    19 | {breadcrumbs.map((breadcrumb, index) => ( 20 |
  • 21 | 22 | {breadcrumb?.text} 23 | 24 | {index < breadcrumbs.length - 1 && ( 25 | » 26 | )} 27 |
  • 28 | ))} 29 |
30 | )} 31 | 32 | ) 33 | } 34 | 35 | Breadcrumbs.propTypes = { 36 | breadcrumbs: PropTypes.array.isRequired 37 | } 38 | -------------------------------------------------------------------------------- /components/atoms/Breadcrumbs/Breadcrumbs.module.css: -------------------------------------------------------------------------------- 1 | ul.breadcrumbs { 2 | @apply mb-8 flex; 3 | 4 | & li { 5 | @apply uppercase text-xs font-semibold p-0; 6 | 7 | & .sep { 8 | @apply px-2 opacity-50 font-normal; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /components/atoms/Breadcrumbs/Breadcrumbs.stories.mdx: -------------------------------------------------------------------------------- 1 | import {Meta, Story, Canvas} from '@storybook/addon-docs/blocks' 2 | 3 | import Breadcrumbs from '.' 4 | 5 | export const crumbs = [ 6 | { 7 | text: 'Home', 8 | url: '/' 9 | }, 10 | { 11 | text: 'About', 12 | url: '/about' 13 | }, 14 | { 15 | text: 'Our Team', 16 | url: '/about/team' 17 | } 18 | ] 19 | 20 | 21 | 22 | # Breadcrumbs 23 | 24 | Use this component to display site breadcrumbs. 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /components/atoms/Breadcrumbs/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Breadcrumbs' 2 | -------------------------------------------------------------------------------- /components/atoms/Button/Button.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | @apply inline-flex items-center justify-center rounded border cursor-pointer font-semibold mr-2 mb-2; 3 | 4 | & svg { 5 | @apply ml-4; 6 | } 7 | 8 | &.iconLeft { 9 | @apply flex-row-reverse; 10 | 11 | & svg { 12 | @apply mr-12 ml-0; 13 | } 14 | } 15 | 16 | &:hover, 17 | &:focus, 18 | &:active:not([disabled]) { 19 | @apply underline; 20 | } 21 | 22 | /* SIZES */ 23 | &.lg { 24 | @apply px-4 py-3 text-lg; 25 | } 26 | 27 | &.md { 28 | @apply px-3 py-2; 29 | } 30 | 31 | &.sm { 32 | @apply px-1.5 py-0.5; 33 | } 34 | 35 | &.fluid { 36 | @apply block w-full text-center; 37 | } 38 | 39 | /* TYPES */ 40 | &.primary { 41 | @apply bg-white; 42 | 43 | &.disabled { 44 | @apply opacity-50; 45 | } 46 | } 47 | 48 | &.secondary { 49 | @apply bg-black text-white; 50 | 51 | &.disabled { 52 | @apply opacity-50; 53 | } 54 | } 55 | 56 | &.disabled { 57 | @apply cursor-not-allowed; 58 | } 59 | 60 | &.iconOnly { 61 | & svg { 62 | @apply m-0; 63 | } 64 | } 65 | 66 | /* STYLES */ 67 | &.styleOutline { 68 | border: 2px solid; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /components/atoms/Button/index.js: -------------------------------------------------------------------------------- 1 | export {ButtonInner, default} from './Button' 2 | -------------------------------------------------------------------------------- /components/atoms/Code/Code.module.css: -------------------------------------------------------------------------------- 1 | .code { 2 | @apply mb-8; 3 | 4 | & > pre { 5 | @apply rounded; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /components/atoms/Code/Code.stories.mdx: -------------------------------------------------------------------------------- 1 | import {Meta, Story, Canvas} from '@storybook/addon-docs/blocks' 2 | 3 | import Code from '.' 4 | 5 | 6 | 7 | # Code 8 | 9 | Use this component to display a code block. 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | ## Controls 21 | 22 | Play around with `` props in the [Canvas tab of the Controls story](?path=/story/design-system-atoms-code--controls). 23 | 24 | export const Template = (args) => 25 | 26 | 27 | this is a code block!

', 31 | className: 'html custom-class' 32 | }} 33 | > 34 | {Template.bind({})} 35 |
36 |
37 | -------------------------------------------------------------------------------- /components/atoms/Code/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Code' 2 | -------------------------------------------------------------------------------- /components/atoms/Columns/Columns.module.css: -------------------------------------------------------------------------------- 1 | .columns { 2 | @apply flex gap-8; 3 | 4 | &.columnStacked { 5 | @apply flex-col md:flex-row; 6 | } 7 | 8 | &.alignCenter { 9 | & .column { 10 | @apply justify-center; 11 | } 12 | } 13 | 14 | &.alignBottom { 15 | & .column { 16 | @apply justify-end; 17 | } 18 | } 19 | 20 | &.hasBackground { 21 | @apply p-8; 22 | } 23 | 24 | & .column { 25 | @apply flex flex-col w-full; 26 | 27 | &.hasBackground { 28 | @apply p-4; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /components/atoms/Columns/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Columns' 2 | -------------------------------------------------------------------------------- /components/atoms/Container/Container.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import styles from './Container.module.css' 4 | import cn from 'classnames' 5 | 6 | /** 7 | * Render the Container component. 8 | * 9 | * @param {object} props Container component props. 10 | * @param {object} props.children Container children. 11 | * @param {boolean} props.paddingTop Should container render top padding. 12 | * @param {boolean} props.paddingBtm Should container render bottom padding. 13 | * @return {Element} The Container component. 14 | */ 15 | export default function Container({children, paddingTop, paddingBtm}) { 16 | return ( 17 |
24 | {children && children} 25 |
26 | ) 27 | } 28 | 29 | Container.propTypes = { 30 | children: PropTypes.node, 31 | paddingTop: PropTypes.bool, 32 | paddingBtm: PropTypes.bool 33 | } 34 | 35 | Container.defaultProps = { 36 | paddingTop: true, 37 | paddingBtm: true 38 | } 39 | -------------------------------------------------------------------------------- /components/atoms/Container/Container.module.css: -------------------------------------------------------------------------------- 1 | .containerW { 2 | @apply container; 3 | 4 | &.paddingTop { 5 | @apply pt-12; 6 | } 7 | 8 | &.paddingBtm { 9 | @apply pb-12; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /components/atoms/Container/Container.stories.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/addon-docs/blocks' 2 | import Container from './' 3 | 4 | 5 | 6 | # Component 7 | 8 | Use this to wrap another component in a container. This will limit the max width of everything inside of it. See [the tailwind container docs](https://tailwindcss.com/docs/container) for details. 9 | 10 | 11 | 12 | 13 |
I am contained.
14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /components/atoms/Container/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Container' 2 | -------------------------------------------------------------------------------- /components/atoms/ExitPreview/ExitPreview.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | /** 4 | * Renders an anchor to exit Preview Mode. 5 | * 6 | * @param {object} props The component as props. 7 | * @param {object} props.preview Checks if a preview exists. 8 | * @return {Element | null} The ExitPreview component. 9 | */ 10 | export default function ExitPreview({preview}) { 11 | if (preview) { 12 | return ( 13 |

14 | This page is a preview. 15 | 16 | Exit preview mode 17 | 18 |

19 | ) 20 | } 21 | 22 | return null 23 | } 24 | -------------------------------------------------------------------------------- /components/atoms/ExitPreview/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './ExitPreview.js' 2 | -------------------------------------------------------------------------------- /components/atoms/Heading/Heading.js: -------------------------------------------------------------------------------- 1 | import createMarkup from '@/functions/createMarkup' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | 5 | /** 6 | * Render the Heading component. 7 | * 8 | * @param {object} props The props object. 9 | * @param {string} props.children The elements or text you'd like to render inside the heading. 10 | * @param {string} props.className The optional classname. 11 | * @param {string} props.id The optional ID. 12 | * @param {object} props.style The style attributes. 13 | * @param {string} props.tag The tag name you'd like the heading to render as. 14 | * @return {Element} The Heading element. 15 | */ 16 | export default function Heading({children, className, id, style, tag}) { 17 | if (typeof children === 'string') { 18 | return React.createElement(tag, { 19 | className, 20 | id, 21 | style, 22 | dangerouslySetInnerHTML: createMarkup(children) 23 | }) 24 | } else { 25 | return React.createElement( 26 | tag, 27 | { 28 | className, 29 | id, 30 | style 31 | }, 32 | children 33 | ) 34 | } 35 | } 36 | 37 | Heading.propTypes = { 38 | children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), 39 | className: PropTypes.string, 40 | id: PropTypes.string, 41 | style: PropTypes.object, 42 | tag: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) 43 | } 44 | 45 | Heading.defaultProps = { 46 | tag: 'h1' 47 | } 48 | -------------------------------------------------------------------------------- /components/atoms/Heading/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Heading' 2 | -------------------------------------------------------------------------------- /components/atoms/Icon/index.js: -------------------------------------------------------------------------------- 1 | export {default, sizeToPx} from './Icon' 2 | -------------------------------------------------------------------------------- /components/atoms/Image/Image.module.css: -------------------------------------------------------------------------------- 1 | .image { 2 | @apply mb-8; 3 | 4 | & img { 5 | @apply block rounded; 6 | } 7 | 8 | & .caption { 9 | @apply text-center text-xs pt-4; 10 | } 11 | 12 | &.hasImageFill { 13 | @apply mb-0; 14 | } 15 | 16 | & .imageFill { 17 | @apply object-cover rounded; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/atoms/Image/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Image' 2 | -------------------------------------------------------------------------------- /components/atoms/Inputs/Checkbox/Checkbox.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import {Field} from 'formik' 3 | 4 | /** 5 | * Render Checkbox component. 6 | * 7 | * @param {object} props The component attributes as props. 8 | * @param {string} props.className Input className. 9 | * @param {string|number} props.id Input id. 10 | * @param {string} props.label Input label. 11 | * @param {string} props.name Input name. 12 | * @param {string} props.value Input value. 13 | * @return {Element} The Checkbox component. 14 | */ 15 | export default function Checkbox({className, id, label, name, value}) { 16 | return ( 17 |
18 | {label && ( 19 | 23 | )} 24 |
25 | ) 26 | } 27 | 28 | Checkbox.propTypes = { 29 | className: PropTypes.string, 30 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 31 | label: PropTypes.string.isRequired, 32 | name: PropTypes.string.isRequired, 33 | value: PropTypes.string 34 | } 35 | -------------------------------------------------------------------------------- /components/atoms/Inputs/Checkbox/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Checkbox' 2 | -------------------------------------------------------------------------------- /components/atoms/Inputs/CheckboxGroup/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './CheckboxGroup' 2 | -------------------------------------------------------------------------------- /components/atoms/Inputs/InputError/InputError.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import {ErrorMessage} from 'formik' 3 | import styles from './InputError.module.css' 4 | 5 | /** 6 | * Render the InputError component. 7 | * 8 | * @param {object} props The component attributes as props. 9 | * @param {string|number} props.name Input id. 10 | * @return {Element} The InputError component. 11 | */ 12 | export default function InputError({name}) { 13 | return ( 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | InputError.propTypes = { 21 | name: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) 22 | } 23 | -------------------------------------------------------------------------------- /components/atoms/Inputs/InputError/InputError.module.css: -------------------------------------------------------------------------------- 1 | .inputError { 2 | color: #df1642; 3 | } 4 | -------------------------------------------------------------------------------- /components/atoms/Inputs/InputError/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './InputError' 2 | -------------------------------------------------------------------------------- /components/atoms/Inputs/Select/Select.module.css: -------------------------------------------------------------------------------- 1 | .select { 2 | & select { 3 | @apply block border; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /components/atoms/Inputs/Select/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Select' 2 | -------------------------------------------------------------------------------- /components/atoms/Inputs/Text/Text.module.css: -------------------------------------------------------------------------------- 1 | .text { 2 | & input { 3 | @apply rounded; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /components/atoms/Inputs/Text/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Text' 2 | -------------------------------------------------------------------------------- /components/atoms/Inputs/index.js: -------------------------------------------------------------------------------- 1 | export {default as Checkbox} from './Checkbox' 2 | export {default as CheckboxGroup} from './CheckboxGroup' 3 | export {default as Select} from './Select' 4 | export {default as Text} from './Text' 5 | -------------------------------------------------------------------------------- /components/atoms/Logo/Logo.stories.mdx: -------------------------------------------------------------------------------- 1 | import {Meta, Story, Canvas} from '@storybook/addon-docs/blocks' 2 | 3 | import Logo from '.' 4 | 5 | 6 | 7 | # Logo 8 | 9 | Use this component to display a button. Use it anywhere you would use an `` or ` 27 | )} 28 | 29 | ) 30 | } 31 | 32 | ClearRefinements.propTypes = { 33 | items: PropTypes.any.isRequired, 34 | refine: PropTypes.func 35 | } 36 | 37 | const CustomClearRefinements = connectCurrentRefinements(ClearRefinements) 38 | export default CustomClearRefinements 39 | -------------------------------------------------------------------------------- /components/molecules/AlgoliaResults/templates/Hit.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import styles from '../AlgoliaResults.module.css' 3 | 4 | /** 5 | * Render the Hit component. 6 | * 7 | * @author WebDevStudios 8 | * @param {object} props The component attributes as props. 9 | * @param {object} props.hit The hit data. 10 | * @return {Element} The Hit component. 11 | */ 12 | export default function Hit({hit}) { 13 | return ( 14 | 19 | ) 20 | } 21 | 22 | Hit.propTypes = { 23 | hit: PropTypes.any 24 | } 25 | -------------------------------------------------------------------------------- /components/molecules/AlgoliaResults/templates/NoResults.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import styles from '../AlgoliaResults.module.css' 3 | 4 | /** 5 | * Render the NoResults component. 6 | * 7 | * @author WebDevStudios 8 | * @param {object} props The component attributes as props. 9 | * @param {object} props.query The no results data. 10 | * @return {Element} The NoResults component. 11 | */ 12 | export default function NoResults({query}) { 13 | return ( 14 | <> 15 |

Search Results

16 | {query !== '' && ( 17 |

18 | 0 Results for {query} 19 |

20 | )} 21 |
22 | Sorry, there are no results found for your search criteria. 23 |
24 | Try searching again with a different term. 25 |
26 | 27 | ) 28 | } 29 | 30 | NoResults.propTypes = { 31 | query: PropTypes.string 32 | } 33 | 34 | NoResults.defaultProps = { 35 | query: '' 36 | } 37 | -------------------------------------------------------------------------------- /components/molecules/AlgoliaSearch/components/Hit.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import {Highlight} from 'react-instantsearch-dom' 3 | import searchClick from '../functions/searchClick' 4 | 5 | /** 6 | * Render the Hit component. 7 | * 8 | * @author WebDevStudios 9 | * @see https://www.algolia.com/doc/api-reference/widgets/hits/react/ 10 | * @param {object} props The component attributes as props. 11 | * @param {object} props.hit Renders each hit from the results. 12 | * @return {Element} The Hit component. 13 | */ 14 | export default function Hit({hit}) { 15 | return ( 16 | 24 | ) 25 | } 26 | 27 | Hit.propTypes = { 28 | hit: PropTypes.object.isRequired 29 | } 30 | -------------------------------------------------------------------------------- /components/molecules/AlgoliaSearch/components/SearchIcon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Render the SearchIcon component. 3 | * 4 | * @author WebDevStudios 5 | * @return {Element} The SearchIcon component. 6 | */ 7 | export default function SearchIcon() { 8 | return ( 9 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /components/molecules/AlgoliaSearch/functions/buildSearchUrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Construct Search URL and navigate user to results. 3 | * 4 | * @author WebDevStudios 5 | * @param {string} query The search query. 6 | * @return {string} The search URL with search query. 7 | */ 8 | export default function buildSearchUrl(query) { 9 | if (!query) { 10 | return false 11 | } 12 | return `/search?q=${query}` 13 | } 14 | -------------------------------------------------------------------------------- /components/molecules/AlgoliaSearch/functions/searchClick.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Click event for search results 3 | * 4 | * @author WebDevStudios 5 | * @param {object} e The click event. 6 | * @return {object} The routed URL. 7 | */ 8 | export default function searchClick(e) { 9 | const target = e.currentTarget 10 | if (!target) { 11 | return false 12 | } 13 | 14 | const url = target.dataset.url 15 | if (url && window) { 16 | // router.push(url) // Does not work, does not rerender and causes `InfiniteHits` component to get out of sync. 17 | window.location = url 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/molecules/AlgoliaSearch/functions/searchSubmit.js: -------------------------------------------------------------------------------- 1 | import buildSearchUrl from './buildSearchUrl' 2 | import {setLocalStorage} from './localStorage' 3 | 4 | /** 5 | * Search Form Submit Handler 6 | * 7 | * @author WebDevStudios 8 | * @param {object} event Form submit. 9 | * @param {Function} setSearchState Callback function to set search state. 10 | * @param {Array} searchState Search state array. 11 | * @param {string} storageName Local storage name. 12 | * @param {number} maxLength Maximum history items to store. 13 | * @return {string} The search term. 14 | */ 15 | export default function searchSubmit( 16 | event, 17 | setSearchState, 18 | searchState, 19 | storageName, 20 | maxLength 21 | ) { 22 | event.preventDefault() 23 | 24 | const target = event.target 25 | if (!target) { 26 | return false 27 | } 28 | 29 | const term = target.querySelector('input').value.trim() // Search term. 30 | 31 | if (searchState !== '' && target.querySelector('input').value.trim() !== '') { 32 | // Save search term to local storage. 33 | setLocalStorage(storageName, term, maxLength) 34 | document.location = buildSearchUrl(term) 35 | } else { 36 | // Empty search, set focus back on input. 37 | target.querySelector('input').focus() 38 | setSearchState('') 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /components/molecules/AlgoliaSearch/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './AlgoliaSearch' 2 | -------------------------------------------------------------------------------- /components/molecules/Blocks/Blocks.js: -------------------------------------------------------------------------------- 1 | import displayBlock from '@/functions/wordpress/blocks/displayBlock' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | 5 | /** 6 | * Render the Blocks component. 7 | * 8 | * @author WebDevStudios 9 | * @param {object} props The component attributes as props. 10 | * @param {Array} props.blocks The array of blocks. 11 | * @return {Element} The Blocks component. 12 | */ 13 | export default function Blocks({blocks}) { 14 | return ( 15 | <> 16 | { 17 | // If there are blocks, loop over and display. 18 | !!blocks?.length && 19 | blocks.map((block, index) => { 20 | return displayBlock(block, index) 21 | }) 22 | } 23 | 24 | ) 25 | } 26 | 27 | Blocks.propTypes = { 28 | blocks: PropTypes.array.isRequired 29 | } 30 | 31 | Blocks.defaultProps = { 32 | blocks: [] 33 | } 34 | -------------------------------------------------------------------------------- /components/molecules/Blocks/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './Blocks' 2 | -------------------------------------------------------------------------------- /components/molecules/ButtonGroup/ButtonGroup.js: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import styles from './ButtonGroup.module.css' 5 | 6 | /** 7 | * Render the ButtonGroup component. 8 | * 9 | * @author WebDevStudios 10 | * @param {object} props The component properties. 11 | * @param {Element} props.children The children props to render. 12 | * @param {string} props.contentJustification The justification of the buttons. 13 | * @param {string} props.id The id of the block. 14 | * @param {string} props.orientation The orientation of buttons. 15 | * @return {Element} The ButtonGroup component. 16 | */ 17 | export default function ButtonGroup({ 18 | children, 19 | contentJustification, 20 | id, 21 | orientation 22 | }) { 23 | return ( 24 | <> 25 |
33 | {children} 34 |
35 | 36 | ) 37 | } 38 | 39 | ButtonGroup.propTypes = { 40 | children: PropTypes.element, 41 | contentJustification: PropTypes.string, 42 | id: PropTypes.string, 43 | orientation: PropTypes.string 44 | } 45 | -------------------------------------------------------------------------------- /components/molecules/ButtonGroup/ButtonGroup.module.css: -------------------------------------------------------------------------------- 1 | .buttonGroup { 2 | @apply mb-8; 3 | 4 | &.horizontal { 5 | @apply flex flex-wrap; 6 | } 7 | 8 | &.vertical { 9 | @apply flex flex-col; 10 | } 11 | 12 | &.center { 13 | @apply justify-center items-center; 14 | } 15 | 16 | &.right { 17 | @apply justify-end items-end; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/molecules/ButtonGroup/ButtonGroup.stories.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/addon-docs/blocks' 2 | import ButtonGroup from './' 3 | import Button from '@/components/atoms/Button' 4 | 5 | 6 | 7 | # ButtonGroup 8 | 9 | Use this to show a group of buttons. 10 | 11 | 12 | 13 | 14 | <> 15 |