├── .circleci └── config.yml ├── .codeclimate.yml ├── .commitlintrc.json ├── .editorconfig ├── .env ├── .eslintcache ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .lintstagedrc ├── .prettierignore ├── .prettierrc ├── .releaserc.json ├── .vim └── coc-settings.json ├── .vscode └── settings.json ├── .watchmanconfig ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── components ├── CMS │ ├── PortableText │ │ ├── EmbedHTML.tsx │ │ ├── Figure.tsx │ │ ├── SimpleBlockContent.tsx │ │ └── serializers.ts │ ├── RenderSections.tsx │ └── sections │ │ ├── Hero.tsx │ │ ├── ImageSection.tsx │ │ ├── TextSection.tsx │ │ └── index.ts ├── HoC │ ├── Animation │ │ └── CustomAnimatePresence.tsx │ ├── Link │ │ ├── ButtonLink.tsx │ │ ├── MUILink.tsx │ │ ├── MenuLink.tsx │ │ ├── NakedLink.tsx │ │ └── NextComposed.tsx │ ├── PageSection.tsx │ ├── RenderPage.tsx │ ├── SEO │ │ ├── Default.tsx │ │ ├── Logo.tsx │ │ └── Page.tsx │ ├── TitleWithDivider.tsx │ └── index.ts ├── UI │ ├── Animation │ │ ├── PageAnimation.tsx │ │ └── TitleAnimation.tsx │ ├── Category │ │ ├── ListCategory.tsx │ │ └── SingleCategory.tsx │ ├── FAB │ │ ├── FAB.styles.ts │ │ └── FAB.tsx │ ├── Layout │ │ ├── AppBar │ │ │ ├── Bar.tsx │ │ │ ├── Header.tsx │ │ │ ├── MenuIcon.tsx │ │ │ └── index.ts │ │ ├── Drawer │ │ │ ├── AppDrawer.tsx │ │ │ ├── AppDrawerMenu.styles.ts │ │ │ ├── AppDrawerMenu.tsx │ │ │ ├── MenuItem.styles.ts │ │ │ ├── MenuItem.tsx │ │ │ ├── MenuItemsArr.tsx │ │ │ └── index.ts │ │ ├── Footer │ │ │ ├── Footer.styles.tsx │ │ │ ├── Footer.tsx │ │ │ ├── FooterGridRow.tsx │ │ │ └── FooterLinkGridItem.tsx │ │ ├── Layout.styles.ts │ │ ├── Layout.tsx │ │ └── index.ts │ ├── Pages │ │ ├── About │ │ │ ├── AboutPage.styles.ts │ │ │ └── AboutPage.tsx │ │ ├── Category │ │ │ ├── CategoriesListPage.styles.ts │ │ │ ├── CategoriesListPage.tsx │ │ │ └── SingleCategoryPage.tsx │ │ ├── Home │ │ │ ├── HomePage.styles.ts │ │ │ ├── HomePage.tsx │ │ │ └── Section │ │ │ │ ├── BasedOnPracticesIdeas.tsx │ │ │ │ ├── BasedOnThisTech.tsx │ │ │ │ ├── LearnMore.tsx │ │ │ │ ├── WhatIsTrue.tsx │ │ │ │ └── WhyUseTrue.tsx │ │ ├── Product │ │ │ ├── ProductPage.styles.tsx │ │ │ └── ProductPage.tsx │ │ └── page │ │ │ ├── DefaultPage.tsx │ │ │ └── PreviewPage.tsx │ ├── PreviewModeAlert.tsx │ ├── Product │ │ ├── ListProduct.tsx │ │ └── SingleProduct.tsx │ └── index.ts └── index.ts ├── jsconfig.json ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── 404.tsx ├── [page].tsx ├── _app.tsx ├── _document.tsx ├── _error.tsx ├── about.tsx ├── api │ ├── exit-preview.ts │ ├── preview.ts │ └── sitemap.ts ├── categories.tsx ├── category │ ├── [category].tsx │ └── [category] │ │ └── [product].tsx └── index.tsx ├── public ├── Lato-Black.ttf ├── Lato-Bold.ttf ├── Lato-Light.ttf ├── Lato-Regular.ttf ├── browserconfig.xml ├── i18n │ ├── da-DK │ │ ├── homePage.json │ │ └── test.json │ └── en-US │ │ ├── homePage.json │ │ └── test.json ├── images │ ├── TRUE-logo-16.svg │ ├── TRUE-logo-32.svg │ ├── TRUE-logo │ │ ├── TRUE-logo-16.svg │ │ ├── TRUE-logo-32.svg │ │ ├── TRUE-logo-social-blue.png │ │ ├── TRUE-logo-social-large-blue.png │ │ ├── TRUE-logo-social-large.png │ │ ├── TRUE-logo-social-small-blue.png │ │ ├── logo-120-white.png │ │ ├── logo-128-white.png │ │ ├── logo-128.png │ │ ├── logo-144-white.png │ │ ├── logo-144.png │ │ ├── logo-152-white.png │ │ ├── logo-152.png │ │ ├── logo-16-white.png │ │ ├── logo-180-white.png │ │ ├── logo-192-white.png │ │ ├── logo-192.png │ │ ├── logo-32-white.png │ │ ├── logo-384-white.png │ │ ├── logo-384.png │ │ ├── logo-512-white.png │ │ ├── logo-512.png │ │ ├── logo-72-white.png │ │ ├── logo-72.png │ │ ├── logo-96-white.png │ │ └── logo-96.png │ ├── header-icon.svg │ └── illustrations │ │ ├── design_components.svg │ │ ├── hacker_mindset.svg │ │ ├── new_ideas.svg │ │ ├── nextjs.svg │ │ └── questions.svg ├── manifest.json ├── mstile-150x150.png └── robots.txt ├── tsconfig.json ├── typescript ├── BlockContentToReact.d.ts ├── Manrope.ttf.d.ts ├── PageProps.d.ts ├── PortableText.d.ts ├── cms │ ├── APIRoute.d.ts │ ├── Category.d.ts │ ├── MetaAPIObject.d.ts │ ├── Page.d.ts │ ├── Product.d.ts │ └── SiteConfig.d.ts ├── global.d.ts ├── i18n │ └── Languages.ts ├── settings │ ├── AppRoute.d.ts │ └── UTILITY.d.ts └── state │ ├── NavigationModel.d.ts │ ├── StoreModel.d.ts │ └── UIModel.d.ts ├── util ├── Redirect.ts ├── animations │ ├── easing.ts │ └── variants.ts ├── api │ ├── calls │ │ ├── getAllCategories.ts │ │ ├── getAllCategoriesWithProducts.ts │ │ ├── getAllProducts.ts │ │ ├── getAllProductsByCategory.ts │ │ ├── getSanityConfig.ts │ │ ├── getSingleCategoryBySlug.ts │ │ ├── getSinglePageByRoute.ts │ │ └── getSingleProductFromSlug.ts │ ├── index.ts │ └── queries │ │ ├── allCategories.ts │ │ ├── allCategoriesAllProducts.ts │ │ ├── allProducts.ts │ │ ├── allProductsByCategory.ts │ │ ├── allRoutes.ts │ │ ├── allSitemapRoutes.ts │ │ ├── singleCategoryBySlug.ts │ │ ├── singlePageFromRoute.ts │ │ ├── singleProductBySlug.ts │ │ └── singleSiteConfig.ts ├── canonicalRoute.ts ├── capitalizeString.ts ├── characters │ └── index.ts ├── createRoutesForSitemap.ts ├── fixSw.js ├── generateFontFaces.ts ├── generateProductParamsArr.ts ├── generateRawTextFromTextBlock.ts ├── handleExitComplete.ts ├── i18n │ ├── generateLocale.ts │ └── resolveTranslationFiles.ts ├── ifPathnameIsIndex.ts ├── isIOS.ts ├── resolveProductionUrl.ts ├── resolveRoutes.ts ├── sanity.ts ├── settings │ ├── CONSTANTS.ts │ ├── common.settings.ts │ ├── index.ts │ ├── seo.settings.ts │ └── ui.settings.ts ├── shared │ ├── createStore.ts │ └── models │ │ ├── navigation.ts │ │ ├── settings.ts │ │ ├── storeModel.ts │ │ └── ui.ts ├── themes │ └── MainTheme.ts └── tsEasyPeasyHooks.ts └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # @format 2 | 3 | version: 2.1 4 | jobs: 5 | build: 6 | docker: 7 | - image: 'circleci/node:latest' 8 | steps: 9 | - checkout 10 | - run: 11 | name: install 12 | command: yarn 13 | - run: 14 | name: release 15 | command: yarn run semantic-release || true 16 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | # @format 2 | 3 | plugins: 4 | fixme: 5 | enabled: true 6 | editorconfig: 7 | enabled: true 8 | exclude_patterns: 9 | - '**/*.ttf' 10 | - '**/*.otf' 11 | - '**/*.svg' 12 | - '**/*.ico' 13 | - '**/public/**' 14 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@commitlint/config-conventional", 3 | "parserPreset": { 4 | "parserOpts": { 5 | "issuePrefixes": ["TRUE-"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # Matches multiple files with brace expansion notation 14 | # Set default charset 15 | [*.{js,ts,tsx,json,d.ts}] 16 | charset = utf-8 17 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ### Public variables 2 | NEXT_PUBLIC_PROJECT_APP_NAME="TRUE Framework" 3 | NEXT_PUBLIC_PROJECT_TITLE="TRUE Framework" 4 | NEXT_PUBLIC_PROJECT_DESCRIPTION="The TRUE Framework === The Really Unique and Exciting Framework" 5 | NEXT_PUBLIC_PROJECT_OG_TITLE="TRUE Framework" 6 | NEXT_PUBLIC_PROJECT_OG_DESCRIPTION="The TRUE Framework === The Really Unique and Exciting Framework" 7 | 8 | NEXT_PUBLIC_PROJECT_OG_AUTHOR_FIRST_NAME="Mathias" 9 | NEXT_PUBLIC_PROJECT_OG_AUTHOR_LAST_NAME="Kandelborg" 10 | NEXT_PUBLIC_PROJECT_OG_AUTHOR_USERNAME="MathiasKandelborg" 11 | NEXT_PUBLIC_PROJECT_OG_AUTHOR_HANDLE="kandelborg" 12 | NEXT_PUBLIC_PROJECT_OG_AUTHOR_AVATAR="https://s.gravatar.com/avatar/f4299efda225903c68fa649b9cb71d7d?s=80" 13 | NEXT_PUBLIC_PROJECT_OG_AUTHOR_GENDER="male" 14 | 15 | NEXT_PUBLIC_PROJECT_URL='https://true-framework.vercel.app' 16 | NEXT_PUBLIC_SANITY_PROJECT_ID="uyxuk36u" 17 | 18 | 19 | 20 | NEXT_PUBLIC_HOST='true-framework.vercel.app' 21 | NEXT_PUBLIC_PORT=3000 22 | NEXT_PUBLIC_HOSTNAME=https://$HOST:$PORT 23 | ### Server only variables 24 | 25 | SANITY_API_TOKEN 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | public/ 2 | public/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const typeNames = [ 2 | 'AppProps', 3 | 'AppRoute', 4 | 'BaseAPIObject', 5 | 'BaseRoute', 6 | 'BaseTextObject', 7 | 'BlockContent', 8 | 'Category', 9 | 'MetaAPIObject', 10 | 'Page', 11 | 'PageContent', 12 | 'PageProps', 13 | 'Product', 14 | 'StaticRoute', 15 | 'SMRoute', 16 | 'SpanTextObject', 17 | 'TextBlock', 18 | 'TextBlockMarkDefs' 19 | ] 20 | 21 | const typesToIgnore = typeNames.join('|') 22 | 23 | const rules = { 24 | 'no-underscore-dangle': [ 25 | 'error', 26 | { allow: ['_id', '_type', '_rev', '_key', '_createdAt', '_updatedAt'] } 27 | ], 28 | 'no-console': ['error', { allow: ['warn', 'error', 'info'] }], 29 | 'react/jsx-props-no-spreading': [ 30 | 2, 31 | { 32 | html: 'enforce', 33 | custom: 'enforce', 34 | explicitSpread: 'ignore', 35 | exceptions: ['App', 'Component', 'SectionComponent', 'SingleCategory'] 36 | } 37 | ], 38 | 'no-undef': 0, 39 | 'node/no-missing-import': 0, 40 | 'jsdoc/check-tag-names': 0, 41 | 'react/react-in-jsx-scope': 0, 42 | 'react/prop-types': 0, 43 | 'newline-before-return': 2, 44 | 'import/no-unresolved': [1, { ignore: ['^(all|part):'] }], 45 | 'comma-dangle': [ 46 | 2, 47 | { 48 | arrays: 'never', 49 | objects: 'never', 50 | imports: 'never', 51 | exports: 'never', 52 | functions: 'never' 53 | } 54 | ], 55 | 'arrow-body-style': [2, 'as-needed'], 56 | 'prefer-arrow/prefer-arrow-functions': [ 57 | 2, 58 | { 59 | allowStandaloneDeclarations: true, 60 | disallowPrototype: true, 61 | singleReturnOnly: true, 62 | classPropertiesAllowed: false 63 | } 64 | ] 65 | } 66 | 67 | module.exports = { 68 | root: true, 69 | env: { 70 | browser: true, 71 | es6: true, 72 | node: true 73 | }, 74 | parserOptions: { 75 | tsconfigRootDir: './', 76 | project: 'tsconfig.json', 77 | sourceType: 'module' 78 | }, 79 | globals: { 80 | JSX: true 81 | }, 82 | 83 | settings: { 84 | 'import/parsers': { 85 | 'babel-eslint': ['.*.js', '*.js'] 86 | } 87 | }, 88 | 89 | parser: 'babel-eslint', 90 | 91 | extends: [ 92 | 'plugin:import/errors', 93 | 'plugin:import/warnings', 94 | 'airbnb', 95 | 'airbnb/hooks', 96 | 'plugin:jsdoc/recommended', 97 | 'plugin:promise/recommended', 98 | 'plugin:node/recommended-module', 99 | 'plugin:@next/next/recommended', 100 | 'plugin:prettier/recommended', 101 | 'prettier/react' 102 | ], 103 | 104 | plugins: [ 105 | 'import', 106 | 'jsdoc', 107 | 'prefer-arrow', 108 | 'promise', 109 | '@next/next', 110 | 'prettier' 111 | ], 112 | 113 | rules, 114 | 115 | overrides: [ 116 | { 117 | files: ['./**/*.ts', './**/*.tsx', '.*.ts', '*.ts', '*.tsx', '.*.tsx'], 118 | parser: '@typescript-eslint/parser', 119 | parserOptions: { 120 | tsconfigRootDir: './', 121 | project: 'tsconfig.json', 122 | sourceType: 'module' 123 | }, 124 | extends: [ 125 | 'plugin:@typescript-eslint/recommended', 126 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 127 | 'plugin:import/errors', 128 | 'plugin:import/warnings', 129 | 'plugin:import/typescript', 130 | 'airbnb-typescript', 131 | 'airbnb/hooks', 132 | 'plugin:jsdoc/recommended', 133 | 'plugin:promise/recommended', 134 | 'plugin:node/recommended-module', 135 | 'plugin:@next/next/recommended', 136 | 'plugin:prettier/recommended', 137 | 'prettier/@typescript-eslint', 138 | 'prettier/react' 139 | ], 140 | 141 | plugins: [ 142 | 'import', 143 | '@typescript-eslint', 144 | 'jsdoc', 145 | 'prefer-arrow', 146 | '@next/next', 147 | 'promise', 148 | 'prettier' 149 | ], 150 | settings: { 151 | 'import/parsers': { 152 | '@typescript-eslint/parser': ['.ts', '.tsx'] 153 | }, 154 | 'import/resolver': { 155 | typescript: { 156 | alwaysTryTypes: false, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` 157 | project: 'tsconfig.json' 158 | } 159 | } 160 | }, 161 | rules: { 162 | ...rules, 163 | /* TS Specific rules */ 164 | 'no-undef': 0, 165 | 166 | '@typescript-eslint/naming-convention': [ 167 | 1, 168 | { 169 | selector: 'variable', 170 | format: ['camelCase', 'PascalCase', 'UPPER_CASE'] 171 | }, 172 | { 173 | selector: 'interface', 174 | format: ['PascalCase'], 175 | prefix: ['I', 'A'], 176 | filter: { 177 | regex: `^(${typesToIgnore})$`, 178 | match: false 179 | } 180 | }, 181 | { 182 | selector: 'typeParameter', 183 | format: ['PascalCase'], 184 | prefix: ['T'] 185 | }, 186 | { 187 | selector: ['variable'], 188 | format: ['camelCase'], 189 | leadingUnderscore: 'allow' 190 | } 191 | ] 192 | } 193 | } 194 | ] 195 | } 196 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | **/node_modules 4 | /.pnp 5 | .pnp.js 6 | 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # PWA 19 | public/service-worker.js 20 | public/workbox*.js 21 | 22 | # misc 23 | .DS_Store 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # local env files 31 | .env.local 32 | .env.development.local 33 | .env.test.local 34 | .env.production.local 35 | 36 | .vercel 37 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ['eslint -f codeframe --fix --cache', 'prettier --write'] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | .next 3 | # Ignore node_modules 4 | node_modules 5 | 6 | # Usually we don't want to format public files 7 | public 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "jsxBracketSameLine": true, 5 | 6 | "printWidth": 80, 7 | 8 | "quoteProps": "consistent", 9 | "semi": false, 10 | "singleQuote": true, 11 | "trailingComma": "none", 12 | 13 | "tabWidth": 2, 14 | "useTabs": false 15 | } 16 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "+([0-9])?(.{+([0-9]),x}).x", 4 | "master", 5 | "next", 6 | "next-major", 7 | { "name": "beta", "prerelease": true }, 8 | { "name": "alpha", "prerelease": true } 9 | ], 10 | 11 | "release": { 12 | "plugins": [ 13 | "@semantic-release/commit-analyzer", 14 | "@semantic-release/changelog", 15 | "@semantic-release/release-notes-generator", 16 | "@semantic-release/github", 17 | "@semantic-release/npm", 18 | "@semantic-release/git" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.run": "onSave", 3 | "eslint.packageManager": "yarn", 4 | "prettier.eslintIntegration": true, 5 | "prettier.formatterPriority": -1, 6 | "eslint.autoFixOnSave": true 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/.yarn": true, 4 | "**/.pnp.*": true 5 | }, 6 | "typescript.enablePromptUseWorkspaceTsdk": true, 7 | "editor.formatOnSaveMode": "file", 8 | "editor.formatOnSave": true, 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll": true 11 | }, 12 | "eslint.codeActionsOnSave.mode": "all", 13 | "eslint.lintTask.options": "--cache .", 14 | "eslint.packageManager": "yarn" 15 | } 16 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["node_modules", ".next"] 3 | } 4 | 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Code of Conduct 4 | 5 | ## Our Pledge 6 | 7 | In the interest of fostering an open and welcoming environment, we as 8 | contributors and maintainers pledge to making participation in our project and 9 | our community a harassment-free experience for everyone, regardless of age, body 10 | size, disability, ethnicity, sex characteristics, gender identity and expression, 11 | level of experience, education, socio-economic status, nationality, personal 12 | appearance, race, religion, or sexual identity and orientation. 13 | 14 | ## Our Standards 15 | 16 | Examples of behavior that contributes to maintaining a positive environment 17 | include: 18 | 19 | - Using welcoming and inclusive language 20 | - Being respectful of differing viewpoints and experiences 21 | - Gracefully accepting constructive criticism 22 | - Focusing on what is best for the community 23 | - Showing empathy towards other community members 24 | 25 | Examples of unacceptable behavior by participants include: 26 | 27 | - The use of sexualized language or imagery and unwelcome sexual attention or 28 | advances 29 | - Trolling, insulting/derogatory comments, and personal or political attacks 30 | - Public or private harassment 31 | - Publishing others' private information, such as a physical or electronic 32 | address, without explicit permission 33 | - Other conduct which could reasonably be considered inappropriate in a 34 | professional setting 35 | 36 | ## Our Responsibilities 37 | 38 | Project maintainers are responsible for clarifying the standards of acceptable 39 | behavior and are expected to take appropriate and fair corrective action in 40 | response to any instances of unacceptable behavior. 41 | 42 | Project maintainers have the right and responsibility to remove, edit, or 43 | reject comments, commits, code, wiki edits, issues, and other contributions 44 | that are not aligned to this Code of Conduct, or to ban temporarily or 45 | permanently any contributor for other behaviors that they deem inappropriate, 46 | threatening, offensive, or harmful. 47 | 48 | ## Scope 49 | 50 | This Code of Conduct applies both within project spaces and in public spaces 51 | when an individual is representing the project or its community. Examples of 52 | representing a project or community include using an official project e-mail 53 | address, posting via an official social media account, or acting as an appointed 54 | representative at an online or offline event. Representation of a project may be 55 | further defined and clarified by project maintainers. 56 | 57 | ## Enforcement 58 | 59 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 60 | reported by contacting the project team at mathias@kandelborg.dk. All 61 | complaints will be reviewed and investigated and will result in a response that 62 | is deemed necessary and appropriate to the circumstances. The project team is 63 | obligated to maintain confidentiality with regard to the reporter of an incident. 64 | Further details of specific enforcement policies may be posted separately. 65 | 66 | Project maintainers who do not follow or enforce the Code of Conduct in good 67 | faith may face temporary or permanent repercussions as determined by other 68 | members of the project's leadership. 69 | 70 | ## Attribution 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 73 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 74 | 75 | [homepage]: https://www.contributor-covenant.org 76 | 77 | For answers to common questions about this code of conduct, see 78 | https://www.contributor-covenant.org/faq 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Mathias Wøbbe | Mathias Kandelborg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # TRUE Framework 4 | 5 | [![Maintainability](https://img.shields.io/codeclimate/maintainability/MathiasKandelborg/TRUE-framework?style=for-the-badge)](https://codeclimate.com/github/MathiasKandelborg/TRUE-framework/maintainability) 6 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=for-the-badge)](http://commitizen.github.io/cz-cli/) 7 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=for-the-badge)](https://github.com/semantic-release/semantic-release) 8 | 9 | **THIS IS A DRAFT** - Come contribute and share your thoughts (make this a link) 10 | 11 | ## TRUE Framework === The Really Unique & Exciting Framework 12 | 13 | Built on the shoulders of giants. Made to Just Work™ and follow specifications 14 | 15 | TRUE is an opinionated starter kit based on technologies that seek to be: 16 | 17 | - Efficient 18 | - Have incredible user _and_ developer experience 19 | - Be feature-full (without all the bloat) 20 | 21 | ### Overview 22 | 23 | - Overview 24 | - Reasoning 25 | - Who would use this 26 | - Installation 27 | 28 | ## Reasoning 29 | 30 | A good starter kit reduces time spent solving problems, and improves the concept --> idea process. 31 | 32 | When you start a project, it can be daunting to get everything right. 33 | If one wants to have contributors, linting, versioning and other things, it would have to be implemented 'from the bottom'. 34 | 35 | It is rarely the case that a project would have requirements that differ so much from the 'default' most people (want to) set up; 36 | that's why it's often sensible to begin from a foundation instead of creating - and thereby re-thinking - what's essentially being created every time a new project is made. 37 | 38 | In order to _apply and test_ ideas instead of **building** them, one should start from a flexible place that isn't too restrictive, but still get a lot of utility going without further thought. 39 | 40 | For a starter kit with a lot of default utility and pre-made boilerplate, it's almost impossible to keep opinions away. This framework is opinionated and has to be in order to stay flexible, sleek and ready for large amounts of contributions. 41 | 42 | ## Who would use this 43 | 44 | Mathias (the creator) is going to use this for all his website/app based projects. 45 | 46 | Anyone who wants an easy time coding websites using NextJS, Sanity(CMS), Material-UI, Framer-motion and be able to do whatever they want in React. 47 | 48 | ## License 49 | 50 | Anyone can do anything they want; it's their responsibility, however. 51 | 52 | See LICENSE for reference 53 | 54 | This project is free (as in beer 🍻) and open-source. Always will be. 55 | -------------------------------------------------------------------------------- /components/CMS/PortableText/EmbedHTML.tsx: -------------------------------------------------------------------------------- 1 | interface IEmbedHTMLProps { 2 | node: { 3 | html: string 4 | } 5 | } 6 | 7 | const EmbedHTML: React.FC = (props) => { 8 | const { 9 | node: { html } 10 | } = props 11 | 12 | if (!html) { 13 | return <> 14 | } 15 | 16 | // eslint-disable-next-line react/no-danger 17 | return
18 | } 19 | 20 | export default EmbedHTML 21 | -------------------------------------------------------------------------------- /components/CMS/PortableText/Figure.tsx: -------------------------------------------------------------------------------- 1 | import imageUrlBuilder from '@sanity/image-url' 2 | import client from '@util/sanity' 3 | 4 | interface IFigureProps { 5 | node: { 6 | alt: string 7 | caption: string 8 | asset: { 9 | _ref: string 10 | } 11 | } 12 | } 13 | 14 | const builder = imageUrlBuilder(client) 15 | const Figure: React.FC = (props) => { 16 | const { 17 | node: { alt, caption, asset } 18 | } = props 19 | 20 | return ( 21 |
22 | {alt} 27 | {caption && ( 28 |
29 |
30 |

{caption}

31 |
32 |
33 | )} 34 |
35 | ) 36 | } 37 | 38 | export default Figure 39 | -------------------------------------------------------------------------------- /components/CMS/PortableText/SimpleBlockContent.tsx: -------------------------------------------------------------------------------- 1 | import BlockContent from '@sanity/block-content-to-react' 2 | import { TextBlock } from 'PortableText' 3 | import serializers from './serializers' 4 | 5 | interface ISimpleBlockContentProps { 6 | blocks: TextBlock | TextBlock[] 7 | } 8 | 9 | const SimpleBlockContent: React.FC = (props) => { 10 | const { blocks } = props 11 | 12 | return ( 13 | 20 | ) 21 | } 22 | 23 | export default SimpleBlockContent 24 | -------------------------------------------------------------------------------- /components/CMS/PortableText/serializers.ts: -------------------------------------------------------------------------------- 1 | import EmbedHTML from './EmbedHTML' 2 | import Figure from './Figure' 3 | 4 | const serializers = { 5 | types: { 6 | embedHTML: EmbedHTML, 7 | figure: Figure 8 | } 9 | } 10 | 11 | export default serializers 12 | -------------------------------------------------------------------------------- /components/CMS/RenderSections.tsx: -------------------------------------------------------------------------------- 1 | import capStr from '@util/capitalizeString' 2 | import { Page, PageContent } from 'cms/Page' 3 | import * as SectionComponents from './sections' 4 | 5 | /* 6 | * It's incredible how resolveSections does its thing. 7 | * I, however, haven't tried doing something of the likes before. 8 | * It is probably quite straight forward 🤣 9 | * - Mathias Wøbbe 14/08 10 | */ 11 | 12 | /* 13 | * ESLint and TypeScript hates when you do this to your comments: 14 | */ 15 | /** 16 | * @param {PageContent} section The section the resolve 17 | * @returns {React.FC | null} A section 18 | */ 19 | function resolveSections(section?: PageContent): React.FC | null { 20 | /** 21 | * // TODO: Section should be typed with possible Section components 22 | */ 23 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 24 | const Section: React.FC = 25 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 26 | // @ts-ignore 27 | SectionComponents[capStr(section._type)] 28 | 29 | if (Section) { 30 | return Section 31 | } 32 | 33 | console.error('Cant find section', section) // eslint-disable-line no-console 34 | 35 | return null 36 | } 37 | 38 | interface IRenderSectionsProps { 39 | sections: Page['content'] | Array | undefined 40 | } 41 | 42 | const RenderSections: React.FC = (props) => { 43 | const { sections } = props 44 | 45 | if (!sections) { 46 | console.error('Missing section') 47 | 48 | return
Missing sections
49 | } 50 | 51 | if (Array.isArray(sections)) { 52 | return ( 53 | <> 54 | {sections.map((section) => { 55 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 56 | const SectionComponent = resolveSections(section) 57 | if (!SectionComponent) { 58 | return
Missing section {section?._type}
59 | } 60 | 61 | return 62 | })} 63 | 64 | ) 65 | } 66 | 67 | const SectionComponent = resolveSections(sections) 68 | 69 | if (!SectionComponent) { 70 | return
Missing section {sections._type}
71 | } 72 | 73 | return 74 | } 75 | 76 | export default RenderSections 77 | -------------------------------------------------------------------------------- /components/CMS/sections/Hero.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import { BlockContent } from '@sanity/block-content-to-react' 3 | import imageUrlBuilder from '@sanity/image-url' 4 | import { SanityImageSource } from '@sanity/image-url/lib/types/types' 5 | import generateRawTextFromTextBlock from '@util/generateRawTextFromTextBlock' 6 | import client from '@util/sanity' 7 | import { TextBlock } from 'PortableText' 8 | import SimpleBlockContent from '../PortableText/SimpleBlockContent' 9 | 10 | interface IHeroSectionProps { 11 | heading: string 12 | tagline: TextBlock 13 | ctas: [string] 14 | backgroundImage: BlockContent['imageOptions'] 15 | } 16 | 17 | /** 18 | * @param {SanityImageSource} source The Sanity image source 19 | * @returns {string} Image url 20 | */ 21 | const urlFor = (source: SanityImageSource) => 22 | imageUrlBuilder(client).image(source) 23 | 24 | const HeroSection: React.FC = (props) => { 25 | const { heading, tagline, backgroundImage } = props 26 | 27 | const style = backgroundImage 28 | ? { 29 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 30 | backgroundImage: `url("${urlFor(backgroundImage) 31 | .width(2000) 32 | .auto('format') 33 | .url()!}")` 34 | } 35 | : {} 36 | 37 | return ( 38 |
39 | 40 | {generateRawTextFromTextBlock(Array(tagline))} 44 | {heading} 45 | {tagline && ( 46 | 47 | 48 | 49 | )} 50 | 51 |
52 | ) 53 | } 54 | 55 | export default HeroSection 56 | -------------------------------------------------------------------------------- /components/CMS/sections/ImageSection.tsx: -------------------------------------------------------------------------------- 1 | /* TODO: Implement ImageSection. 2 | * 'It is easy, I can always do it'. 3 | * Sharing the project is more important by now. 4 | */ 5 | export {} 6 | -------------------------------------------------------------------------------- /components/CMS/sections/TextSection.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import { PageContent } from 'cms/Page' 3 | import SimpleBlockContent from '../PortableText/SimpleBlockContent' 4 | 5 | interface ITextSectionProps extends Omit { 6 | heading: string 7 | label: string 8 | } 9 | 10 | const TextSection: React.FC = (props) => { 11 | const { heading, label, text } = props 12 | 13 | return ( 14 |
15 | {heading} 16 | {label} 17 | {text && ( 18 | 19 | 20 | 21 | )} 22 |
23 | ) 24 | } 25 | 26 | export default TextSection 27 | -------------------------------------------------------------------------------- /components/CMS/sections/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Hero } from './Hero' 2 | export { default as TextSection } from './TextSection' 3 | -------------------------------------------------------------------------------- /components/HoC/Animation/CustomAnimatePresence.tsx: -------------------------------------------------------------------------------- 1 | import handleExitComplete from '@util/handleExitComplete' 2 | import { AnimatePresence } from 'framer-motion' 3 | 4 | interface ICustomAnimatePresenceProps { 5 | layoutShift: boolean 6 | exitFirst?: boolean 7 | } 8 | 9 | const CustomAnimatePresence: React.FC = ( 10 | props 11 | ) => { 12 | const { layoutShift, exitFirst, children } = props 13 | 14 | return ( 15 | <> 16 | handleExitComplete()} 19 | presenceAffectsLayout={layoutShift}> 20 | {children} 21 | 22 | 23 | ) 24 | } 25 | 26 | export default CustomAnimatePresence 27 | -------------------------------------------------------------------------------- /components/HoC/Link/ButtonLink.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import ifPathnameIsIndex from '@util/ifPathnameIsIndex' 3 | import clsx from 'clsx' 4 | import { useRouter } from 'next/router' 5 | import * as React from 'react' 6 | import NextComposed, { NextComposedProps } from './NextComposed' 7 | 8 | interface ILinkPropsBase { 9 | activeClassName?: string 10 | innerRef?: React.Ref 11 | } 12 | 13 | export type LinkProps = ILinkPropsBase & 14 | NextComposedProps & 15 | Omit 16 | 17 | const ButtonLink = (props: LinkProps) => { 18 | const { 19 | href, 20 | activeClassName = 'active', 21 | className: classNameProps, 22 | innerRef, 23 | ...other 24 | } = props 25 | 26 | const router = useRouter() 27 | const pathname = ifPathnameIsIndex( 28 | typeof href === 'string' ? href : href.pathname 29 | ) 30 | 31 | const className = clsx(classNameProps, { 32 | [activeClassName]: router.pathname === pathname && activeClassName 33 | }) 34 | 35 | return ( 36 | // eslint-disable-next-line react/jsx-props-no-spreading 37 | 38 | ) 39 | } 40 | 41 | export default React.forwardRef((props, ref) => ( 42 | // eslint-disable-next-line react/jsx-props-no-spreading 43 | 44 | )) 45 | -------------------------------------------------------------------------------- /components/HoC/Link/MUILink.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading, jsx-a11y/anchor-has-content */ 2 | import * as MUI from '@material-ui/core' 3 | import * as React from 'react' 4 | import NextComposed, { NextComposedProps } from './NextComposed' 5 | 6 | interface ILinkPropsBase { 7 | innerRef?: React.Ref 8 | as?: string 9 | } 10 | 11 | export type TLinkProps = ILinkPropsBase & 12 | NextComposedProps & 13 | Omit 14 | 15 | // A styled version of the Next.js Link component: 16 | // https://nextjs.org/docs/#with-link 17 | const Link = (props: TLinkProps) => { 18 | const { href, as, className: classNameProps, innerRef, ...other } = props 19 | 20 | return ( 21 | 29 | ) 30 | } 31 | 32 | export default React.forwardRef((props, ref) => ( 33 | 34 | )) 35 | -------------------------------------------------------------------------------- /components/HoC/Link/MenuLink.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading, jsx-a11y/anchor-has-content */ 2 | 3 | import * as MUI from '@material-ui/core' 4 | import clsx from 'clsx' 5 | import { useRouter } from 'next/router' 6 | import * as React from 'react' 7 | import NextComposed, { NextComposedProps } from './NextComposed' 8 | 9 | interface ILinkPropsBase { 10 | activeClassName?: string 11 | innerRef?: React.Ref 12 | as: string 13 | } 14 | 15 | export type TLinkProps = ILinkPropsBase & 16 | NextComposedProps & 17 | Omit 18 | 19 | // A styled version of the Next.js Link component: 20 | // https://nextjs.org/docs/#with-link 21 | const MenuLink = (props: TLinkProps) => { 22 | const { 23 | activeClassName = 'active', 24 | className: classNameProps, 25 | innerRef, 26 | as, 27 | locale, 28 | href, 29 | ...other 30 | } = props 31 | 32 | const router = useRouter() 33 | 34 | const className = clsx(classNameProps, { 35 | [activeClassName]: router.asPath === as && activeClassName 36 | }) 37 | 38 | return ( 39 | 47 | ) 48 | } 49 | 50 | export default React.forwardRef((props, ref) => ( 51 | 52 | )) 53 | -------------------------------------------------------------------------------- /components/HoC/Link/NakedLink.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import ifPathnameIsIndex from '@util/ifPathnameIsIndex' 3 | import clsx from 'clsx' 4 | import { useRouter } from 'next/router' 5 | import * as React from 'react' 6 | import NextComposed, { NextComposedProps } from './NextComposed' 7 | 8 | interface ILinkPropsBase { 9 | activeClassName?: string 10 | innerRef?: React.Ref 11 | } 12 | 13 | export type LinkProps = ILinkPropsBase & 14 | NextComposedProps & 15 | Omit 16 | 17 | const NakedLink = (props: LinkProps) => { 18 | const { 19 | href, 20 | activeClassName = 'active', 21 | className: classNameProps, 22 | innerRef, 23 | ...other 24 | } = props 25 | 26 | const router = useRouter() 27 | const pathname = ifPathnameIsIndex( 28 | typeof href === 'string' ? href : href.pathname 29 | ) 30 | 31 | const className = clsx(classNameProps, { 32 | [activeClassName]: router.pathname === pathname && activeClassName 33 | }) 34 | 35 | return ( 36 | // eslint-disable-next-line react/jsx-props-no-spreading 37 | 38 | ) 39 | } 40 | 41 | export default React.forwardRef((props, ref) => ( 42 | // eslint-disable-next-line react/jsx-props-no-spreading 43 | 44 | )) 45 | -------------------------------------------------------------------------------- /components/HoC/Link/NextComposed.tsx: -------------------------------------------------------------------------------- 1 | import NextLink, { LinkProps as NextLinkProps } from 'next/link' 2 | import React from 'react' 3 | 4 | export type NextComposedProps = NextLinkProps & 5 | Omit, 'href'> 6 | 7 | const NextComposed = React.forwardRef( 8 | (props, ref) => { 9 | const { 10 | as, 11 | href, 12 | replace, 13 | scroll, 14 | passHref, 15 | shallow, 16 | prefetch, 17 | locale, 18 | ...other 19 | } = props 20 | 21 | return ( 22 | 31 | {/* eslint-disable-next-line jsx-a11y/anchor-has-content,react/jsx-props-no-spreading */} 32 | 33 | 34 | ) 35 | } 36 | ) 37 | 38 | export default NextComposed 39 | -------------------------------------------------------------------------------- /components/HoC/PageSection.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | 3 | interface IPageSectionComponentProps { 4 | id: string 5 | className: string 6 | direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse' 7 | } 8 | 9 | const PageSection: React.FC = (props) => { 10 | const { id, children, direction, className } = props 11 | 12 | return ( 13 | 21 | <>{children} 22 | 23 | ) 24 | } 25 | 26 | export default PageSection 27 | -------------------------------------------------------------------------------- /components/HoC/RenderPage.tsx: -------------------------------------------------------------------------------- 1 | import RenderSections from '@components/CMS/RenderSections' 2 | import { PageAnimation, TitleAnimation } from '@components/UI' 3 | import { APIRoute } from 'cms/APIRoute' 4 | import CustomAnimatePresence from './Animation/CustomAnimatePresence' 5 | 6 | const RenderPage: React.FC<{ 7 | loading: boolean 8 | preview: boolean 9 | pageProps: APIRoute['page'] | undefined 10 | }> = (props) => { 11 | const { preview, pageProps } = props 12 | 13 | const { title, content } = pageProps || { title: 'TITLE NOT FOUND' } 14 | 15 | if (preview) { 16 | /* getPreviewRouteBySlug(props.routeId!) 17 | getPreviewPageListenerBySlug(_id || 'id not present') */ 18 | 19 | return ( 20 | <> 21 |

PREVIEW MODE

22 |

{title}

23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | return ( 30 | <> 31 | 32 | 33 |

{title}

34 |
35 | 36 | {content && } 37 | 38 |
39 | 40 | ) 41 | } 42 | 43 | export default RenderPage 44 | -------------------------------------------------------------------------------- /components/HoC/SEO/Default.tsx: -------------------------------------------------------------------------------- 1 | import { DefaultSeo } from 'next-seo' 2 | 3 | interface IDefaultSEOProps { 4 | canonicalRoute: string 5 | } 6 | 7 | /** 8 | * [Documentation for next-seo](https://github.com/garmeeh/next-seo) 9 | * 10 | * @param {IDefaultSEOProps} props Default SEO props 11 | * @returns {JSX.Element} The default SEO component 12 | */ 13 | const DefaultSEO: React.FC = (props) => { 14 | const { canonicalRoute } = props 15 | 16 | return ( 17 | 28 | ) 29 | } 30 | 31 | export default DefaultSEO 32 | -------------------------------------------------------------------------------- /components/HoC/SEO/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { LogoJsonLd } from 'next-seo' 2 | 3 | const LogoSEO: React.FC<{ canonicalRoute: string }> = ({ canonicalRoute }) => ( 4 | 5 | ) 6 | 7 | export default LogoSEO 8 | -------------------------------------------------------------------------------- /components/HoC/SEO/Page.tsx: -------------------------------------------------------------------------------- 1 | import { common, CONSTANTS, seo } from '@util/settings' 2 | import { NextSeo } from 'next-seo' 3 | import { OpenGraphImages, OpenGraphProfile, Twitter } from 'next-seo/lib/types' 4 | 5 | interface IPageSEOProps { 6 | title?: string 7 | description?: string 8 | images?: OpenGraphImages[] 9 | defaultImageHeight?: number 10 | defaultImageWidth?: number 11 | ogUrl?: string 12 | ogSiteName?: string 13 | ogTitle?: string 14 | ogDescription?: string 15 | ogProfile?: OpenGraphProfile 16 | ogLocale?: string 17 | ogType?: string 18 | twitter?: Twitter 19 | } 20 | 21 | const PageSEO: React.FC = (props) => { 22 | const seoImgUrl = CONSTANTS.DEV ? 'http://localhost:3000' : seo.url 23 | 24 | const { 25 | title = seo.title, 26 | description = seo.description, 27 | defaultImageHeight = 1250, 28 | defaultImageWidth = 2500, 29 | images = [ 30 | { 31 | url: `${seoImgUrl}/images/TRUE-logo-social-large-blue.png`, 32 | height: defaultImageHeight, 33 | width: defaultImageWidth 34 | } 35 | ], 36 | ogTitle = seo.ogTitle, 37 | ogDescription = seo.ogDescription, 38 | ogSiteName = seo.title, 39 | ogUrl = seo.url, 40 | ogLocale = common.language, 41 | ogProfile = { 42 | firstName: seo.author.firstName, 43 | lastName: seo.author.lastName, 44 | username: seo.author.username, 45 | gender: seo.author.gender 46 | }, 47 | ogType = ' website', 48 | twitter = { 49 | cardType: 'summary_large_image', 50 | handle: seo.author.twitterHandle, 51 | site: seo.url 52 | } 53 | } = props 54 | 55 | return ( 56 | 72 | ) 73 | } 74 | 75 | export default PageSEO 76 | -------------------------------------------------------------------------------- /components/HoC/TitleWithDivider.tsx: -------------------------------------------------------------------------------- 1 | import TitleAnimation from '@components/UI/Animation/TitleAnimation' 2 | import * as MUI from '@material-ui/core' 3 | 4 | interface ITitleDividerProps extends MUI.TypographyProps { 5 | text: string 6 | } 7 | 8 | const TitleWithDivider: React.FC = (props) => { 9 | const { text, color, ...rest } = props 10 | 11 | return ( 12 | 13 | 14 | 18 | {text} 19 | 20 | 21 | 22 |
23 |
24 | ) 25 | } 26 | 27 | export default TitleWithDivider 28 | -------------------------------------------------------------------------------- /components/HoC/index.ts: -------------------------------------------------------------------------------- 1 | import MUILink from './Link/MUILink' 2 | import NakedLink from './Link/NakedLink' 3 | 4 | export { MUILink, NakedLink } 5 | -------------------------------------------------------------------------------- /components/UI/Animation/PageAnimation.tsx: -------------------------------------------------------------------------------- 1 | import { pageVariants } from '@util/animations/variants' 2 | import { motion } from 'framer-motion' 3 | 4 | interface IPageAnimation { 5 | layoutID?: string 6 | } 7 | 8 | const PageAnimation: React.FC = ({ children }) => ( 9 | 16 | {children} 17 | 18 | ) 19 | 20 | export default PageAnimation 21 | -------------------------------------------------------------------------------- /components/UI/Animation/TitleAnimation.tsx: -------------------------------------------------------------------------------- 1 | import { titleVariants } from '@util/animations/variants' 2 | import { motion } from 'framer-motion' 3 | 4 | /** 5 | * Animates the page entering and on un-mount leaving again 6 | * 7 | * @param {object} options The options object 8 | * @param {JSX.Element|JSX.Element[]} options.children React children 9 | * @returns {JSX.Element} Title animation div 10 | */ 11 | const TitleAnimation: React.FC = ({ children }) => ( 12 | 18 | {children} 19 | 20 | ) 21 | 22 | export default TitleAnimation 23 | -------------------------------------------------------------------------------- /components/UI/Category/ListCategory.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import { Category } from 'cms/Category' 3 | import SingleCategory from './SingleCategory' 4 | 5 | const ListCategory: React.FC<{ categories: Category[] }> = (props) => { 6 | const { categories } = props 7 | 8 | return ( 9 | <> 10 | {categories.map((category) => ( 11 | 12 | 13 | 14 | ))} 15 | 16 | ) 17 | } 18 | 19 | export default ListCategory 20 | -------------------------------------------------------------------------------- /components/UI/Category/SingleCategory.tsx: -------------------------------------------------------------------------------- 1 | import SimpleBlockContent from '@components/CMS/PortableText/SimpleBlockContent' 2 | import { NakedLink } from '@components/HoC' 3 | import * as MUI from '@material-ui/core' 4 | import { Category } from 'cms/Category' 5 | 6 | const SingleCategory: React.FC = (props) => { 7 | const { title, description, url } = props 8 | 9 | return ( 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | export default SingleCategory 25 | -------------------------------------------------------------------------------- /components/UI/FAB/FAB.styles.ts: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | 3 | const makeFABStyles = MUI.makeStyles((theme: MUI.Theme) => 4 | MUI.createStyles({ 5 | fab: { 6 | right: theme.spacing(2), 7 | bottom: theme.spacing(2), 8 | position: 'fixed' 9 | } 10 | }) 11 | ) 12 | 13 | export default makeFABStyles 14 | -------------------------------------------------------------------------------- /components/UI/FAB/FAB.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import { useState } from 'react' 3 | import makeFABStyles from './FAB.styles' 4 | 5 | /* interface IFABProps { 6 | placeholder: string 7 | } */ 8 | 9 | const FAB: React.FC = () => { 10 | const [modalIsOpen, setModalOpen] = useState(false) 11 | 12 | const classes = makeFABStyles() 13 | 14 | return ( 15 | <> 16 | setModalOpen(true)}> 21 |

X

22 |
23 | setModalOpen(false)}> 28 | 29 | Contact Options 30 | 31 | 32 | 33 | Contact options 34 | 35 | 36 | 37 | 38 | ) 39 | } 40 | 41 | export default FAB 42 | -------------------------------------------------------------------------------- /components/UI/Layout/AppBar/Bar.tsx: -------------------------------------------------------------------------------- 1 | import PreviewModeAlert from '@components/UI/PreviewModeAlert' 2 | import * as MUI from '@material-ui/core' 3 | import AppHeader from './Header' 4 | import MenuIconSvg from './MenuIcon' 5 | 6 | interface IAppBarProps { 7 | appBarClassName: string 8 | iconButtonClassName: string 9 | preview?: boolean 10 | } 11 | 12 | const AppBar: React.FC = (props) => { 13 | const { preview, appBarClassName, iconButtonClassName } = props 14 | 15 | return ( 16 | 17 | 18 | 19 |
20 | {preview ? : } 21 |
22 | 23 | 24 | ) 25 | } 26 | 27 | export default AppBar 28 | -------------------------------------------------------------------------------- /components/UI/Layout/AppBar/Header.tsx: -------------------------------------------------------------------------------- 1 | const AppHeader: React.FC = () => ( 2 | Application logo 8 | ) 9 | 10 | export default AppHeader 11 | -------------------------------------------------------------------------------- /components/UI/Layout/AppBar/MenuIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import { useStoreActions } from '@util/tsEasyPeasyHooks' 3 | import { Menu } from 'mdi-material-ui' 4 | 5 | const MenuIcon: React.FC<{ iconButtonClassName: string }> = (props) => { 6 | const { iconButtonClassName } = props 7 | const drawerToggle = useStoreActions((a) => a.toggleDrawer) 8 | 9 | return ( 10 | drawerToggle(true)}> 15 | 16 | 17 | ) 18 | } 19 | export default MenuIcon 20 | -------------------------------------------------------------------------------- /components/UI/Layout/AppBar/index.ts: -------------------------------------------------------------------------------- 1 | import AppBar from './Bar' 2 | import MenuIcon from './MenuIcon' 3 | 4 | export { AppBar, MenuIcon } 5 | -------------------------------------------------------------------------------- /components/UI/Layout/Drawer/AppDrawer.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import iOS from '@util/isIOS' 3 | import { useStoreActions, useStoreState } from '@util/tsEasyPeasyHooks' 4 | 5 | interface IDrawerProps { 6 | toggleDrawerClose: () => void 7 | toggleDrawerOpen: () => void 8 | drawerPaperClassName: string 9 | drawerOpen: boolean 10 | } 11 | const DefaultDrawer: React.FC> = ( 12 | props 13 | ) => { 14 | const { drawerPaperClassName, children } = props 15 | 16 | return ( 17 | 18 | 24 | {children} 25 | 26 | 27 | ) 28 | } 29 | 30 | const MobileDrawer: React.FC = (props) => { 31 | const { 32 | toggleDrawerClose, 33 | toggleDrawerOpen, 34 | drawerPaperClassName, 35 | drawerOpen, 36 | children 37 | } = props 38 | 39 | return ( 40 | 41 | 52 | {children} 53 | 54 | 55 | ) 56 | } 57 | 58 | const AppDrawer: React.FC> = ( 59 | props 60 | ) => { 61 | const { children, drawerPaperClassName } = props 62 | const drawerOpen = useStoreState((s) => s.drawerOpen) 63 | const drawerToggled = useStoreActions((a) => a.toggleDrawer) 64 | 65 | const toggleDrawerOpen = () => drawerToggled(true) 66 | 67 | const toggleDrawerClose = () => drawerToggled(false) 68 | 69 | return ( 70 | <> 71 | 72 | {children} 73 | 74 | 79 | {children} 80 | 81 | 82 | ) 83 | } 84 | 85 | export default AppDrawer 86 | -------------------------------------------------------------------------------- /components/UI/Layout/Drawer/AppDrawerMenu.styles.ts: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | 3 | const appDrawerMenuStyles = MUI.makeStyles((theme: MUI.Theme) => 4 | MUI.createStyles({ 5 | toolbar: theme.mixins.toolbar 6 | }) 7 | ) 8 | 9 | export default appDrawerMenuStyles 10 | -------------------------------------------------------------------------------- /components/UI/Layout/Drawer/AppDrawerMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import appDrawerMenuStyles from './AppDrawerMenu.styles' 3 | 4 | interface IDrawerMenuProps { 5 | routes: JSX.Element 6 | } 7 | 8 | const DrawerMenu: React.FC = (props) => { 9 | const { routes } = props 10 | const styles = appDrawerMenuStyles() 11 | 12 | return ( 13 | <> 14 |
15 | {routes} 16 | 17 | ) 18 | } 19 | 20 | export default DrawerMenu 21 | -------------------------------------------------------------------------------- /components/UI/Layout/Drawer/MenuItem.styles.ts: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | 3 | const menuItemStyles = MUI.makeStyles((theme: MUI.Theme) => 4 | MUI.createStyles({ 5 | menuItem: { 6 | color: 'inherit', 7 | fontSize: theme.typography.subtitle1.fontSize 8 | /* fontWeight: theme.typography.fontWeightMedium */ 9 | }, 10 | menuItemActive: { 11 | color: theme.palette.primary.light, 12 | fontWeight: theme.typography.fontWeightBold, 13 | paddingLeft: theme.spacing(3) 14 | } 15 | }) 16 | ) 17 | 18 | export default menuItemStyles 19 | -------------------------------------------------------------------------------- /components/UI/Layout/Drawer/MenuItem.tsx: -------------------------------------------------------------------------------- 1 | import MenuLink from '@components/HoC/Link/MenuLink' 2 | import * as MUI from '@material-ui/core' 3 | import { useStoreActions } from '@util/tsEasyPeasyHooks' 4 | import menuItemStyles from './MenuItem.styles' 5 | 6 | interface IMenuItemProps { 7 | text: string 8 | route: string 9 | as: string 10 | } 11 | 12 | const MenuItem: React.FC = (props) => { 13 | const { text, route, as } = props 14 | 15 | const drawerToggle = useStoreActions((a) => a.toggleDrawer) 16 | 17 | const classes = menuItemStyles() 18 | 19 | return ( 20 |
  • 21 | drawerToggle(false)} 25 | component={MenuLink} 26 | scroll 27 | href={route} 28 | as={as} 29 | className={classes.menuItem} 30 | activeClassName={classes.menuItemActive}> 31 | 32 | 33 |
  • 34 | ) 35 | } 36 | 37 | export default MenuItem 38 | -------------------------------------------------------------------------------- /components/UI/Layout/Drawer/MenuItemsArr.tsx: -------------------------------------------------------------------------------- 1 | import { AppRoute } from 'settings/AppRoute' 2 | import MenuItem from './MenuItem' 3 | 4 | interface IListMenuItemsProps { 5 | allRoutes?: AppRoute[] 6 | } 7 | 8 | const ListMenuItems: React.FC = (props) => { 9 | const { allRoutes } = props 10 | 11 | const MenuItems = allRoutes?.map((r, i) => ( 12 | 18 | )) 19 | 20 | return <>{MenuItems} 21 | } 22 | 23 | export default ListMenuItems 24 | -------------------------------------------------------------------------------- /components/UI/Layout/Drawer/index.ts: -------------------------------------------------------------------------------- 1 | import Drawer from './AppDrawer' 2 | import DrawerMenu from './AppDrawerMenu' 3 | 4 | export { Drawer as AppDrawer, DrawerMenu } 5 | -------------------------------------------------------------------------------- /components/UI/Layout/Footer/Footer.styles.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | 3 | const footerStyles = MUI.makeStyles((theme: MUI.Theme) => 4 | MUI.createStyles({ 5 | footerLinkItem: { 6 | padding: theme.spacing(1) 7 | }, 8 | link: { 9 | margin: theme.spacing(1), 10 | width: '100%', 11 | color: theme.palette.primary.light 12 | } 13 | }) 14 | ) 15 | 16 | export default footerStyles 17 | -------------------------------------------------------------------------------- /components/UI/Layout/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import FooterGridRow from './FooterGridRow' 3 | import FooterLinkGridItem from './FooterLinkGridItem' 4 | 5 | const Footer: React.FC = () => ( 6 |
    7 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | Made with TRUE Framework - Mathias Kandelborg © 2020 26 | 27 | 28 | 29 |
    30 | ) 31 | 32 | export default Footer 33 | -------------------------------------------------------------------------------- /components/UI/Layout/Footer/FooterGridRow.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import footerStyles from './Footer.styles' 3 | 4 | interface IFooterGridRowProps { 5 | placeholder: string 6 | } 7 | 8 | const FooterGridRow: React.FC = (props) => { 9 | const { children } = props 10 | 11 | const classes = footerStyles() 12 | 13 | return ( 14 | 23 | {children} 24 | 25 | ) 26 | } 27 | 28 | export default FooterGridRow 29 | -------------------------------------------------------------------------------- /components/UI/Layout/Footer/FooterLinkGridItem.tsx: -------------------------------------------------------------------------------- 1 | import { MUILink } from '@components/HoC' 2 | import footerStyles from './Footer.styles' 3 | 4 | interface IFooterLinkGridItem { 5 | text: string 6 | href: string 7 | } 8 | 9 | const FooterLinkGridItem: React.FC = (props) => { 10 | const { text, href } = props 11 | 12 | const classes = footerStyles() 13 | 14 | return ( 15 | 20 | {text} 21 | 22 | ) 23 | } 24 | 25 | export default FooterLinkGridItem 26 | -------------------------------------------------------------------------------- /components/UI/Layout/Layout.styles.ts: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import { ui } from '@util/settings' 3 | 4 | const layoutStyles = MUI.makeStyles((theme: MUI.Theme) => 5 | MUI.createStyles({ 6 | drawer: {}, 7 | drawerPaper: { 8 | width: ui.CONSTANTS.DRAWER_WIDTH 9 | }, 10 | appBar: { 11 | backgroundColor: theme.palette.background.default, 12 | zIndex: theme.zIndex.drawer + 1, 13 | [theme.breakpoints.up('md')]: { 14 | marginLeft: ui.CONSTANTS.DRAWER_WIDTH 15 | } 16 | }, 17 | menuButton: { 18 | position: 'fixed', // Don't mess with other elements 19 | marginLeft: -theme.spacing(1), 20 | marginRight: theme.spacing(2), 21 | [theme.breakpoints.up('md')]: { 22 | display: 'none' 23 | } 24 | }, 25 | toolbar: { ...theme.mixins.toolbar }, 26 | 27 | mainSpacer: { 28 | height: theme.spacing(20) 29 | }, 30 | 31 | main: { 32 | /* 33 | * Remove scrollbars on transitions, where content would be scrolled, 34 | * if initial container height was in the resulting container. 35 | */ 36 | overflow: 'hidden' 37 | }, 38 | 39 | content: { 40 | /* Don't show horizontal scroll-bar on horizontal/x position animations 41 | * ########## WARNING WARNING WARNING WARNING ################ 42 | * This settings disables a visual queue that content is too wide 43 | * If overflowX equals hidden, one need to 44 | * ### Manually check content width consistency. ### 45 | */ 46 | overflowX: 'hidden', 47 | paddingTop: theme.spacing(3), 48 | paddingBottom: theme.spacing(3), 49 | 50 | [theme.breakpoints.up('md')]: { 51 | paddingLeft: `calc(${theme.spacing(3)}px + ${ 52 | ui.CONSTANTS.DRAWER_WIDTH 53 | }px)` 54 | } 55 | } 56 | }) 57 | ) 58 | 59 | export default layoutStyles 60 | -------------------------------------------------------------------------------- /components/UI/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import * as MUI from '@material-ui/core' 2 | import FAB from '../FAB/FAB' 3 | import { AppBar } from './AppBar' 4 | import { AppDrawer, DrawerMenu } from './Drawer' 5 | import Footer from './Footer/Footer' 6 | import layoutStyles from './Layout.styles' 7 | 8 | interface ILayoutProps { 9 | MenuItems: JSX.Element 10 | preview?: boolean 11 | } 12 | 13 | const Layout: React.FC = (props) => { 14 | const { children, MenuItems, preview } = props 15 | 16 | const classes = layoutStyles() 17 | 18 | return ( 19 | <> 20 | 25 | 26 | 31 | 32 |
    33 | 34 | {/* Wrap page components in a 'Root Grid' (https://material-ui.com/components/Grid/) */} 35 | 40 | <>{children} 41 | 42 |
    43 |