├── .eslintignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── renovate.json └── workflows │ ├── ci.yml │ ├── release.yml │ └── security.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── config ├── webpack.config.base.js ├── webpack.config.core.js └── webpack.config.lib.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── AbstractCoreComponent.test.tsx ├── AbstractCoreComponent.tsx ├── authoring │ ├── button │ │ └── v1 │ │ │ ├── ButtonV1.test.tsx │ │ │ ├── ButtonV1.tsx │ │ │ └── ButtonV1IsEmptyFn.ts │ ├── download │ │ └── v1 │ │ │ ├── DownloadV1.test.tsx │ │ │ ├── DownloadV1.tsx │ │ │ └── DownloadV1IsEmptyFn.ts │ ├── image │ │ └── v2 │ │ │ ├── ImageV2.test.tsx │ │ │ ├── ImageV2.tsx │ │ │ └── ImageV2IsEmptyFn.ts │ ├── list │ │ └── v2 │ │ │ ├── ListV2.test.tsx │ │ │ ├── ListV2.tsx │ │ │ ├── ListV2IsEmptyFn.ts │ │ │ ├── ListV2TestMockItems.ts │ │ │ ├── expected-list.html │ │ │ └── expected-routed-list.html │ ├── separator │ │ └── v1 │ │ │ ├── SeparatorV1.test.tsx │ │ │ ├── SeparatorV1.tsx │ │ │ └── SeparatorV1IsEmptyFn.ts │ ├── teaser │ │ └── v1 │ │ │ ├── TeaserV1.test.tsx │ │ │ ├── TeaserV1.tsx │ │ │ └── TeaserV1IsEmptyFn.ts │ ├── text │ │ └── v2 │ │ │ ├── TextV2.test.tsx │ │ │ ├── TextV2.tsx │ │ │ └── TextV2IsEmptyFn.ts │ └── title │ │ └── v2 │ │ ├── TitleV2.test.tsx │ │ ├── TitleV2.tsx │ │ └── TitleV2IsEmptyFn.ts ├── common │ └── placeholder.tsx ├── default │ └── v1 │ │ ├── DefaultV1Component.test.tsx │ │ ├── DefaultV1Component.tsx │ │ └── DefaultV1ComponentIsEmptyFn.ts ├── index.test.ts ├── index.ts ├── isEmptyFunctions.ts ├── layout │ ├── breadcrumb │ │ └── v2 │ │ │ ├── BreadCrumbV2.test.tsx │ │ │ ├── BreadCrumbV2.tsx │ │ │ └── BreadCrumbV2IsEmptyFn.ts │ ├── language-navigation │ │ └── v1 │ │ │ ├── LanguageNavigationV1.test.tsx │ │ │ ├── LanguageNavigationV1.tsx │ │ │ ├── LanguageNavigationV1IsEmptyFn.ts │ │ │ └── LanguageNavigationV1TestMockItems.ts │ └── navigation │ │ └── v1 │ │ ├── NavigationV1.test.tsx │ │ ├── NavigationV1.tsx │ │ ├── NavigationV1IsEmptyFn.ts │ │ └── NavigationV1TestMockItems.ts ├── routing │ ├── RoutedCoreComponent.tsx │ ├── RoutedLink.test.tsx │ └── RoutedLink.tsx ├── setupTests.ts ├── tsconfig.base.json ├── tsconfig.types.json └── types.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | config/ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[bug]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Package version** 14 | Provide a package version where the bug occurs. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[feature] " 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "timezone": "Europe/Zurich", 3 | "masterIssue": true, 4 | "packageRules": [ 5 | { 6 | "groupName": "@adobe fixes", 7 | "updateTypes": ["patch", "pin", "digest", "minor"], 8 | "automerge": true, 9 | "packagePatterns": ["^@adobe/"], 10 | "schedule": ["at any time"] 11 | }, 12 | { 13 | "groupName": "@adobe major", 14 | "updateTypes": ["major"], 15 | "packagePatterns": ["^@adobe/"], 16 | "automerge": false, 17 | "schedule": ["at any time"] 18 | }, 19 | { 20 | "groupName": "external fixes", 21 | "updateTypes": ["patch", "pin", "digest", "minor"], 22 | "automerge": false, 23 | "schedule": ["after 1pm on Monday"], 24 | "packagePatterns": ["^.+"], 25 | "excludePackagePatterns": ["^@adobe/"] 26 | }, 27 | { 28 | "groupName": "external major", 29 | "updateTypes": ["major"], 30 | "automerge": false, 31 | "packagePatterns": ["^.+"], 32 | "excludePackagePatterns": ["^@adobe/"], 33 | "schedule": ["after 1pm on Monday"] 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: pull_request_target 3 | 4 | jobs: 5 | test-react-base-components: 6 | name: test react-base-components 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout source code 10 | uses: actions/checkout@v2 11 | - name: Setup Node.js 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: '12' 15 | - name: Install dependencies 16 | run: npm ci 17 | - name: Build the project 18 | run: npm run build:production 19 | - name: Run tests and do code coverage check 20 | run: npm run test:coverage 21 | - name: Run code linter 22 | uses: hallee/eslint-action@1.0.3 23 | if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }} 24 | with: 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | - name: Upload code coverage report to workflow as an artifact 27 | uses: actions/upload-artifact@v2 28 | with: 29 | name: base-coverage.zip 30 | path: coverage 31 | - name: Upload code coverage report to codecov.io and comment in pull request 32 | uses: codecov/codecov-action@v1 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | release: 8 | name: Release and publish module 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout source code 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: 12 19 | - name: Install dependencies 20 | run: npm ci 21 | - name: Build the project 22 | run: npm run build:production 23 | - name: Run tests and do code coverage check 24 | run: npm run test:coverage 25 | - name: Release module and publish it in github.com and npmjs.com 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | NPM_TOKEN: ${{ secrets.ADOBE_BOT_NPM_TOKEN }} 29 | run: npm run semantic-release 30 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | name: Vulnerability check 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request_target: 7 | 8 | jobs: 9 | security: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout source code 13 | uses: actions/checkout@master 14 | - name: Run Snyk to check for vulnerabilities 15 | uses: snyk/actions/node@master 16 | env: 17 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 18 | with: 19 | command: monitor --all-projects --exclude=examples,pom.xml 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | .classpath 4 | .metadata 5 | .project 6 | .settings 7 | .externalToolBuilders 8 | maven-eclipse.xml 9 | *.swp 10 | *.iml 11 | *.ipr 12 | *.iws 13 | *.bak 14 | .vlt 15 | .DS_Store 16 | jcr.log 17 | atlassian-ide-plugin.xml 18 | .vlt-sync.log 19 | .vlt-sync-config.properties 20 | node 21 | node_modules 22 | lib 23 | dist 24 | coverage 25 | 26 | src/tsconfig.types.tsbuildinfo 27 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | config 2 | node 3 | node_modules 4 | .eslintignore 5 | .gitignore 6 | *.iml 7 | jest.config.js 8 | pom.xml 9 | tsconfig.json 10 | src -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AEM WCM Components - React Core implementation 2 | 3 | This module provides a React implementation for the [AEM core components](https://www.aemcomponents.dev/). 4 | This enables you to use the core components: 5 | -In the [AEM SPA editor](https://docs.adobe.com/content/help/en/experience-manager-64/developing/headless/spas/spa-overview.html) with React 6 | -In [React web components](https://www.npmjs.com/package/@adobe/react-webcomponent) 7 | -Or in any other React context, provided you have the input needed to instantiate the components. 8 | 9 | [Introduction Video and Demo](https://www.youtube.com/watch?v=9759AhM7fAc) 10 | 11 | Current supported / exported components: 12 | 13 | ### Page Authoring 14 | - Button (V1) 15 | - Download (V1) 16 | - Image (V2) 17 | - List (V2) 18 | - Separator (V1) 19 | - Teaser (V1) 20 | - Text (V2) 21 | - Title (V2) 22 | 23 | ### Layout 24 | - BreadCrumb (V2) 25 | - Language Navigation (V1) 26 | - Navigation (V1) 27 | 28 | ### Abstraction 29 | - AbstractCoreComponent 30 | - CoreComponentModel (interface) 31 | 32 | ### Containers 33 | For the containers (Accordion,Tabs,Carousel,Container) we do not provide any implementation in this project. 34 | It does not make sense to provide it for web-components as you can leverage the normal Core Components implementation such as a Tab Container, and drag your web components in there. 35 | Instead we provide them for the SPA editor only, introducing a dependency, and therefore we moved it into a [separate project](https://www.npmjs.com/package/@adobe/aem-core-components-react-spa). 36 | 37 | 38 | ## Usage 39 | 40 | You can choose to import the entire library at once OR import components individually. 41 | The latter is useful if you want to only enable a few components and you want to save your javascript footprint. 42 | Also, if you want to load all core components, but you want to lazyload them with react suspense, you will need to import them individually. 43 | 44 | ### Importing the whole library: 45 | 46 | ``` 47 | import * as BaseCoreComponents from "@adobe/aem-core-components-react-base"; 48 | const {ButtonV1, ButtonV1Model, ButtonV1IsEmptyFn} = BaseCoreComponents; 49 | ``` 50 | 51 | ### Importing the button component individually: 52 | 53 | ``` 54 | import ButtonV1, {ButtonV1Model, ButtonV1IsEmptyFn} from "@adobe/aem-core-components-react-base/dist/authoring/button/v1/ButtonV1"; 55 | ``` 56 | 57 | ### Using the imported code 58 | 59 | Now that you have the Button and ButtonV1IsEmptyFn imported, you can use them in your project. 60 | The properties of the Button 1 on 1 correspond to the Sling Model Exporter (.model.json) output. 61 | 62 | Note: There are some exceptions where some extra properties are added (mainly i18n labels) that are currently not present in the OOTB sling model exports. 63 | These can be added by the project itself with delegation. If they are not present, the default (English) values will be used. 64 | 65 | #### Button - Direct instantiation with TypeScript: 66 | ``` 67 | const modelProps:ButtonV1Model = { 68 | text: 'Example Button', 69 | link: '/content/my/awesome/page.html', 70 | icon: 'iconCssCLass' 71 | }; 72 | 73 | const html:JSX.Element = (); 74 | ``` 75 | 76 | 77 | #### Button - Example with the spa editor: 78 | 79 | ``` 80 | MapTo('my-project/wcm/components/button')(ButtonV1, {isEmpty: ButtonV1IsEmptyFn}); 81 | ``` 82 | 83 | For a complete project with examples, visit the [github page](https://github.com/adobe/aem-react-core-wcm-components/tree/master/examples). -------------------------------------------------------------------------------- /config/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | const path = require('path'); 17 | 18 | const isEnvironmentTest = process.env.NODE_ENV === 'test'; 19 | const isEnvironmentProd = process.env.NODE_ENV === 'production'; 20 | const nodeExternals = require('webpack-node-externals'); 21 | const mode = (isEnvironmentProd) ? 'production' : 'development'; 22 | const ManifestPlugin = require('webpack-manifest-plugin'); 23 | 24 | module.exports = { 25 | entry: {}, 26 | mode: mode, 27 | devtool: 'source-map', 28 | output: { 29 | globalObject: `typeof self !== 'undefined' ? self : this`, 30 | path: path.resolve(__dirname, '../dist'), 31 | filename: '[name].js', 32 | libraryTarget: 'umd' 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.(js|mjs|jsx|ts|tsx)$/, 38 | use: ['source-map-loader'], 39 | enforce: 'pre', 40 | }, 41 | { 42 | test: /\.ts$|\.tsx$/, 43 | exclude: /(node_modules|dist)/, 44 | use: 'ts-loader', 45 | enforce: 'post', 46 | }].concat(isEnvironmentTest ? 47 | { 48 | test: /\.ts$|\.tsx$/, 49 | include: path.resolve(__dirname, 'src'), 50 | use: { 51 | loader: 'istanbul-instrumenter-loader', 52 | options: { 53 | esModules: true, 54 | presets: ["env", "react", "stage-2"] 55 | } 56 | }, 57 | enforce: 'post' 58 | } : []) 59 | }, 60 | externals: [!isEnvironmentTest ? nodeExternals({ 61 | modulesFromFile: { 62 | exclude: ['dependencies'] 63 | } 64 | }) : ''], 65 | resolve: { 66 | extensions: ['.ts', '.tsx'] 67 | }, 68 | plugins: [ 69 | 70 | new ManifestPlugin() 71 | ] 72 | }; 73 | -------------------------------------------------------------------------------- /config/webpack.config.core.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const DllPlugin = require("webpack").DllPlugin; 18 | const config = require('./webpack.config.base'); 19 | const path = require('path'); 20 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 21 | 22 | config.output.library = 'AbstractCoreComponent'; 23 | 24 | config.entry = { 25 | 'AbstractCoreComponent': ['./src/AbstractCoreComponent.tsx'], 26 | }; 27 | 28 | config.plugins.push(new CleanWebpackPlugin()); 29 | config.plugins.push( 30 | new DllPlugin({ 31 | context: path.join(__dirname, '..'), 32 | name: "[name]", 33 | path: path.resolve(__dirname, `./../dist/manifest/[name].json`), 34 | }) 35 | ); 36 | 37 | module.exports = config; -------------------------------------------------------------------------------- /config/webpack.config.lib.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const config = require('./webpack.config.base'); 18 | 19 | config.output.library = '@adobe/aem-core-components-react-base'; 20 | 21 | config.entry = { 22 | 'index': ['./src/index.ts'], 23 | 'isEmptyFunctions': ['./src/isEmptyFunctions.ts'], 24 | 'authoring/list/v2/ListV2': ['./src/authoring/list/v2/ListV2'], 25 | 'authoring/button/v1/ButtonV1': ['./src/authoring/button/v1/ButtonV1'], 26 | 'authoring/text/v2/TextV2': ['./src/authoring/text/v2/TextV2'], 27 | 'authoring/title/v2/TitleV2': ['./src/authoring/title/v2/TitleV2'], 28 | 'authoring/image/v2/ImageV2': ['./src/authoring/image/v2/ImageV2'], 29 | 'authoring/teaser/v1/TeaserV1': ['./src/authoring/teaser/v1/TeaserV1'], 30 | 'authoring/download/v1/DownloadV1': ['./src/authoring/download/v1/DownloadV1'], 31 | 'authoring/separator/v1/SeparatorV1': ['./src/authoring/separator/v1/SeparatorV1'], 32 | 'layout/breadcrumb/v2/BreadCrumbV2': ['./src/layout/breadcrumb/v2/BreadCrumbV2'], 33 | 'layout/navigation/v1/NavigationV1': ['./src/layout/navigation/v1/NavigationV1'], 34 | 'layout/language-navigation/v1/LanguageNavigationV1': ['./src/layout/language-navigation/v1/LanguageNavigationV1'] 35 | }; 36 | 37 | module.exports = config; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | module.exports = { 20 | preset: "ts-jest", 21 | setupFilesAfterEnv: ['/src/setupTests.ts'], 22 | testEnvironment: 'jsdom', 23 | transform: { 24 | "^.+\\.tsx?$": "ts-jest" 25 | }, 26 | testMatch: ['/**/*.test.ts','/**/*.test.tsx'], 27 | testPathIgnorePatterns: ['node_modules','lib', 'dist', 'node'], 28 | collectCoverageFrom: [ 29 | '**/*.{ts,tsx}' 30 | ], 31 | coveragePathIgnorePatterns: [ 32 | "/node_modules/", 33 | "/lib/", 34 | "/dist/", 35 | "/node/" 36 | ], 37 | moduleFileExtensions: [ 38 | "ts", 39 | "tsx", 40 | "js", 41 | "jsx", 42 | "json", 43 | "node" 44 | ], 45 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@adobe/aem-core-components-react-base", 3 | "version": "1.1.8", 4 | "license": "Apache-2.0", 5 | "keywords": [ 6 | "adobe", 7 | "aem", 8 | "cq5", 9 | "react", 10 | "core", 11 | "components" 12 | ], 13 | "description": "AEM - React Implementation for the AEM Core Components", 14 | "author": { 15 | "name": "Adobe Systems Inc." 16 | }, 17 | "main": "dist/index.js", 18 | "types": "dist/index.d.ts", 19 | "homepage": "https://github.com/adobe/aem-react-core-wcm-components-base/blob/master/README.md", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/adobe/aem-react-core-wcm-components-base" 23 | }, 24 | "scripts": { 25 | "clean": "shx rm -rf coverage/ dist/ src/tsconfig.types.tsbuildinfo", 26 | "linter": "eslint --ext .ts,.tsx .", 27 | "linter:fix": "eslint --ext .ts,.tsx . --fix", 28 | "test-clear": "jest --clearCache", 29 | "test": "jest", 30 | "test:coverage": "jest --coverage", 31 | "test:debug": "jest --coverage --watchAll", 32 | "build": "npm run clean && webpack --config config/webpack.config.core.js && webpack --config config/webpack.config.lib.js && tsc -p src/tsconfig.types.json", 33 | "build:production": "npm run clean && cross-env NODE_ENV=production webpack --config config/webpack.config.core.js --mode=production && webpack --config config/webpack.config.lib.js --mode=production && tsc -p src/tsconfig.types.json", 34 | "build:types": "npm run clean && tsc -p src/tsconfig.types.json", 35 | "docs": "jsdoc -c ./jsdoc_conf.json && doxdox \"src/**/*.+(js|jsx)\" --layout templates/DOCUMENTATION.hbs --output DOCUMENTATION.md", 36 | "readme": "node node_modules/markdown-include/bin/cli.js markdown-include.config.json", 37 | "docs-readme": "npm run docs && npm run readme", 38 | "semantic-release": "semantic-release" 39 | }, 40 | "dependencies": {}, 41 | "peerDependencies": { 42 | "react": "^16.14.0", 43 | "react-router-dom": "^5.2.0" 44 | }, 45 | "devDependencies": { 46 | "shx": "^0.3.2", 47 | "@babel/cli": "^7.10.5", 48 | "@babel/core": "^7.11.4", 49 | "@babel/preset-env": "^7.11.0", 50 | "@babel/preset-react": "^7.10.4", 51 | "@testing-library/jest-dom": "^4.2.4", 52 | "@testing-library/react": "^9.5.0", 53 | "@testing-library/user-event": "^7.2.1", 54 | "@types/enzyme": "^3.10.5", 55 | "@types/enzyme-adapter-react-16": "^1.0.6", 56 | "@types/jest": "^26.0.14", 57 | "@types/node": "^12.20.10", 58 | "@types/react": "^16.14.5", 59 | "@types/react-dom": "^16.9.8", 60 | "@types/react-router-dom": "^5.1.5", 61 | "@typescript-eslint/eslint-plugin": "^3.9.0", 62 | "@typescript-eslint/parser": "^3.9.0", 63 | "babel-loader": "^8.1.0", 64 | "babel-plugin-istanbul": "^5.2.0", 65 | "source-map-loader": "^1.1.0", 66 | "chai": "^4.2.0", 67 | "clean-webpack-plugin": "^3.0.0", 68 | "cross-env": "^6.0.3", 69 | "doxdox": "^3.0.0", 70 | "enzyme": "^3.11.0", 71 | "enzyme-adapter-react-16": "^1.15.3", 72 | "eslint": "^6.8.0", 73 | "istanbul": "^0.4.5", 74 | "istanbul-instrumenter-loader": "^3.0.1", 75 | "jest": "^26.4.2", 76 | "jest-environment-jsdom-fourteen": "1.0.1", 77 | "jest-resolve": "26.6.2", 78 | "jest-watch-typeahead": "0.6.3", 79 | "jsdoc": "^3.6.5", 80 | "markdown-include": "^0.4.3", 81 | "prop-types": "^15.7.2", 82 | "react": "^16.14.0", 83 | "react-dom": "^16.14.0", 84 | "react-router-dom": "^5.2.0", 85 | "ts-jest": "26.5.6", 86 | "ts-loader": "^6.2.2", 87 | "typescript": "^4.0.2", 88 | "webpack": "^4.46.0", 89 | "webpack-cli": "^3.3.10", 90 | "webpack-manifest-plugin": "^2.2.0", 91 | "webpack-node-externals": "^1.7.2", 92 | "commitizen": "^4.2.3", 93 | "cz-conventional-changelog": "^3.3.0", 94 | "@semantic-release/changelog": "^5.0.1", 95 | "@semantic-release/git": "^9.0.0", 96 | "@semantic-release/github": "^7.2.0", 97 | "semantic-release": "^17.4.1" 98 | }, 99 | "eslintConfig": { 100 | "extends": "plugin:@typescript-eslint/recommended", 101 | "parser": "@typescript-eslint/parser", 102 | "parserOptions": { 103 | "ecmaVersion": 2020, 104 | "sourceType": "module" 105 | }, 106 | "rules": { 107 | "@typescript-eslint/no-empty-interface": 0 108 | } 109 | }, 110 | "browserslist": { 111 | "production": [ 112 | ">0.2%", 113 | "not dead", 114 | "not op_mini all" 115 | ], 116 | "development": [ 117 | "last 1 chrome version", 118 | "last 1 firefox version", 119 | "last 1 safari version" 120 | ] 121 | }, 122 | "config": { 123 | "commitizen": { 124 | "path": "./node_modules/cz-conventional-changelog" 125 | } 126 | }, 127 | "release": { 128 | "plugins": [ 129 | "@semantic-release/commit-analyzer", 130 | "@semantic-release/release-notes-generator", 131 | "@semantic-release/npm", 132 | [ 133 | "@semantic-release/git", 134 | { 135 | "assets": [ 136 | "package.json" 137 | ] 138 | } 139 | ] 140 | ] 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/AbstractCoreComponent.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React, {Component} from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | 22 | import {CoreComponentModel,withStandardBaseCssClass, withConditionalPlaceHolder} from './AbstractCoreComponent'; 23 | 24 | interface TestType extends CoreComponentModel{ 25 | forceEmptyFlag:boolean 26 | } 27 | 28 | class ImplementingClass extends Component{ 29 | 30 | render(): JSX.Element { 31 | return
My awesome component
; 32 | } 33 | 34 | } 35 | 36 | const WrappedClass = withConditionalPlaceHolder(withStandardBaseCssClass(ImplementingClass,"base-class"), (props) => props.forceEmptyFlag, "AwesomeComponent"); 37 | 38 | it('Renders without crashing', () => { 39 | const div = document.createElement('div'); 40 | ReactDOM.render( 41 | , 42 | div 43 | ); 44 | ReactDOM.unmountComponentAtNode(div); 45 | expect(1).toBe(1); 46 | }); 47 | 48 | it('Should show our awesome text if the component is not empty', () => { 49 | const wrapper = mount(); 50 | expect(wrapper.html()).toEqual("
My awesome component
"); 51 | }); 52 | 53 | it('Should not show anything if wcmmode is disabled and component is empty', () => { 54 | const wrapper = mount(); 55 | expect(wrapper.html()).toBeNull(); 56 | }); 57 | 58 | it('Should show the proper placeholder with a custom text if wcmmode is edit and component is empty', () => { 59 | 60 | const WrappedOverride = withConditionalPlaceHolder(ImplementingClass, (props) => props.forceEmptyFlag, "AwesomeComponent", "Custom Configure Text"); 61 | 62 | const wrapper = mount(); 63 | expect(wrapper.html()).toEqual("
AwesomeComponent - Custom Configure Text
"); 64 | }); 65 | 66 | it('Should NOT show the proper placeholder if wcmmode is edit and component is empty, and hidePlaceHolder is set to true.', () => { 67 | const wrapper = mount(); 68 | expect(wrapper.html()).toBeNull(); 69 | }); 70 | 71 | it('Should change the baseClass if we specify it with properties', () => { 72 | const wrapper = mount(); 73 | expect(wrapper.html()).toEqual("
My awesome component
"); 74 | }); -------------------------------------------------------------------------------- /src/AbstractCoreComponent.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | import {ComponentType} from 'react'; 19 | 20 | import {EditorPlaceHolder} from "./common/placeholder"; 21 | 22 | export interface HasBaseCssClass { 23 | baseCssClass?: string 24 | } 25 | 26 | export interface CoreComponentModel extends HasBaseCssClass{ 27 | hidePlaceHolder?: boolean 28 | isInEditor?:boolean 29 | } 30 | 31 | export interface CoreComponentState { 32 | 33 | } 34 | 35 | export const withStandardBaseCssClass = 36 | ( 37 | Component:ComponentType, 38 | defaultBaseCssClass:string 39 | ):React.ComponentType => { 40 | return (props:M) => { 41 | 42 | const baseCssClass = props.baseCssClass; 43 | const toBeUsedCssClass = baseCssClass && baseCssClass.trim().length > 0 ? baseCssClass : defaultBaseCssClass; 44 | 45 | const mergedProps: M= { 46 | ...props, 47 | baseCssClass: toBeUsedCssClass 48 | }; 49 | 50 | return ; 51 | } 52 | }; 53 | 54 | export const withConditionalPlaceHolder = 55 | ( 56 | Component:ComponentType, 57 | isEmpty:(props:M) => boolean, 58 | componentTitle?:string, emptyText?:string 59 | ):React.ComponentType => { 60 | return (props:M) => { 61 | 62 | const isEmptyResult:boolean = isEmpty(props); 63 | const {hidePlaceHolder = false, isInEditor = false} = props; 64 | 65 | return ( 66 | <> 67 | { !isEmptyResult && 68 | 69 | } 70 | { 71 | (isEmptyResult && isInEditor && !hidePlaceHolder) && 72 | 76 | } 77 | 78 | ); 79 | } 80 | }; -------------------------------------------------------------------------------- /src/authoring/button/v1/ButtonV1.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | import ButtonV1, {ButtonV1Model} from './ButtonV1'; 22 | import {MemoryRouter} from 'react-router-dom'; 23 | 24 | 25 | it('Renders without crashing', () => { 26 | const div = document.createElement('div'); 27 | ReactDOM.render( 28 | , 29 | div 30 | ); 31 | ReactDOM.unmountComponentAtNode(div); 32 | expect(1).toBe(1); 33 | }); 34 | 35 | 36 | it('Renders a proper button with link', () => { 37 | 38 | let captured = false; 39 | 40 | const properties:ButtonV1Model = { 41 | ariaLabel: "ThisIsAButton", 42 | icon: "iconCSSCls", 43 | link: "/content/some/link.html", 44 | text: "SomeText", 45 | routed:true, 46 | handleOnClick(event): void { 47 | captured = true; 48 | } 49 | }; 50 | 51 | const wrapper = mount(); 52 | 53 | const button = wrapper.find('a.cmp-button'); 54 | expect(button).toHaveLength(1); 55 | button.simulate('click'); 56 | expect(captured).toEqual(true); 57 | 58 | const anchor = button.find("a"); 59 | expect(anchor).toHaveLength(1); 60 | expect(anchor.prop("aria-label")).toEqual("ThisIsAButton"); 61 | expect(anchor.prop("href")).toEqual("/content/some/link.html"); 62 | 63 | const iconSpan = anchor.find("span.cmp-button__icon.cmp-button__icon--iconCSSCls"); 64 | expect(iconSpan).toHaveLength(1); 65 | 66 | const textSpan = anchor.find("span.cmp-button__text"); 67 | expect(textSpan.text()).toEqual("SomeText"); 68 | }); 69 | 70 | 71 | it('Renders a proper button with link', () => { 72 | 73 | let captured = false; 74 | 75 | const properties:ButtonV1Model = { 76 | ariaLabel: "ThisIsAButton", 77 | icon: "iconCSSCls", 78 | text: "SomeText", 79 | handleOnClick(event): void { 80 | captured = true; 81 | } 82 | }; 83 | 84 | const wrapper = mount(); 85 | 86 | const button = wrapper.find('.cmp-button'); 87 | expect(button).toHaveLength(1); 88 | button.simulate('click'); 89 | expect(captured).toEqual(true); 90 | 91 | const anchor = button.find("a"); 92 | expect(anchor).toHaveLength(0); 93 | 94 | 95 | const iconSpan = button.find("span.cmp-button__icon.cmp-button__icon--iconCSSCls"); 96 | expect(iconSpan).toHaveLength(1); 97 | 98 | const textSpan = button.find("span.cmp-button__text"); 99 | expect(textSpan.text()).toEqual("SomeText"); 100 | }); -------------------------------------------------------------------------------- /src/authoring/button/v1/ButtonV1.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React, {MouseEvent} from 'react'; 18 | import {withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 19 | import {RoutedCoreComponentModel} from "../../../routing/RoutedCoreComponent"; 20 | import {RoutedLink} from "../../../routing/RoutedLink"; 21 | import {ButtonV1IsEmptyFn} from "./ButtonV1IsEmptyFn"; 22 | 23 | 24 | export interface ButtonV1Model extends RoutedCoreComponentModel{ 25 | text?: string; 26 | link?: string; 27 | icon?: string; 28 | ariaLabel?: string; 29 | handleOnClick?(event: MouseEvent): void 30 | } 31 | 32 | export const ButtonV1Content = (props:ButtonV1Model) => { 33 | return ( 34 | <> 35 | { props.icon && } 36 | {props.text} 37 | 38 | ); 39 | }; 40 | 41 | const ButtonV1Impl = (props:ButtonV1Model) => { 42 | 43 | const handleOnClick = (event:MouseEvent) =>{ 44 | if(props.handleOnClick){ 45 | props.handleOnClick(event); 46 | } 47 | }; 48 | 49 | const generateAttributes = (isLink: boolean) => { 50 | const computedAttrs: any = { 51 | className: props.baseCssClass, 52 | onClick: handleOnClick 53 | }; 54 | 55 | if (isLink) { 56 | computedAttrs['aria-label'] = props.ariaLabel; 57 | computedAttrs['href'] = props.link; 58 | } 59 | return computedAttrs; 60 | }; 61 | 62 | const isLink = (!!props.link); 63 | const attrs = generateAttributes(isLink); 64 | 65 | if(isLink){ 66 | return 67 | }else{ 68 | return 69 | } 70 | }; 71 | 72 | const ButtonV1 = (props:ButtonV1Model) => { 73 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(ButtonV1Impl,"cmp-button"), ButtonV1IsEmptyFn, "Button V1"); 74 | return 75 | }; 76 | 77 | export default ButtonV1; -------------------------------------------------------------------------------- /src/authoring/button/v1/ButtonV1IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {ButtonV1Model} from "./ButtonV1"; 2 | 3 | export function ButtonV1IsEmptyFn(props:ButtonV1Model): boolean{ 4 | return props.text == null || props.text.length === 0; 5 | } -------------------------------------------------------------------------------- /src/authoring/download/v1/DownloadV1.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | 22 | import DownloadV1, {DownloadV1Model} from "./DownloadV1"; 23 | 24 | 25 | it('Renders without crashing', () => { 26 | const div = document.createElement('div'); 27 | ReactDOM.render( 28 | , 29 | div 30 | ); 31 | ReactDOM.unmountComponentAtNode(div); 32 | expect(1).toBe(1); 33 | }); 34 | 35 | 36 | it('Renders out properly', () => { 37 | 38 | let captured = false; 39 | const props:DownloadV1Model = { 40 | actionText: "Download now!!!", 41 | description: "

Asset uploaded directly from a local file system

\\r\\n", 42 | displayFilename: true, 43 | displayFormat: true, 44 | displaySize: true, 45 | extension: "jpg", 46 | filename: "lava-into-ocean.jpg", 47 | format: "image/jpeg", 48 | hidePlaceHolder: false, 49 | isInEditor: false, 50 | size: "81 KB", 51 | title: "Uploaded Asset", 52 | titleType: "h2", 53 | handleOnClick: ()=> { 54 | captured = true; 55 | }, 56 | url: "/content/core-components-examples/library/page-authoring/download/jcr:content/root/responsivegrid/demo_68071479/component/download/file.coredownload.jpeg/lava-into-ocean.jpg" 57 | }; 58 | 59 | const wrapper = mount(); 60 | 61 | const properties = wrapper.find('.cmp-download__property'); 62 | 63 | expect(properties).toHaveLength(3); 64 | 65 | const button = wrapper.find('.cmp-download__action'); 66 | expect(button).toHaveLength(1); 67 | button.simulate('click'); 68 | expect(captured).toEqual(true); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /src/authoring/download/v1/DownloadV1.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import React, {MouseEvent} from 'react'; 17 | import {CoreComponentModel, withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 18 | import {DownloadV1IsEmptyFn} from "./DownloadV1IsEmptyFn"; 19 | 20 | 21 | export interface DownloadV1Model extends CoreComponentModel{ 22 | url?: string 23 | title: string 24 | titleType: string 25 | description?: string 26 | actionText?: string 27 | filename?: string 28 | displaySize: boolean 29 | displayFilename: boolean 30 | displayFormat: boolean 31 | format?: string 32 | size?: string 33 | extension?: string 34 | handleOnClick?(event: MouseEvent): void 35 | } 36 | 37 | export const getDownloadV1Href = (props:DownloadV1Model) => (!!props.url && props.url.length > 0) ? props.url : '#'; 38 | 39 | export const DownloadV1HeadingContent = (props:DownloadV1Model) => { 40 | 41 | const handleOnClick = (event:MouseEvent) => { 42 | props.handleOnClick && props.handleOnClick(event); 43 | }; 44 | 45 | return ( 46 | <> 47 | {!!props.url || !!props.handleOnClick && ( 48 | 51 | {props.title} 52 | 53 | )} 54 | {!props.url && ( <> {props.title} )} 55 | 56 | ) 57 | }; 58 | 59 | 60 | export const DownloadV1HeadingElement = (props:DownloadV1Model) => { 61 | 62 | const {titleType = 'h3'} = props; 63 | 64 | return ( 65 | React.createElement( 66 | `${titleType}`, 67 | { 68 | className: props.baseCssClass + '__title"', 69 | }, 70 | 71 | ) 72 | ) 73 | }; 74 | 75 | export const renderProperty = ( label: string, content: string|undefined, cssClassModifier: string,baseCssClass?: string) => { 76 | const cssClass = `${baseCssClass}__property ${baseCssClass}__property--' + ${cssClassModifier}`; 77 | return ( 78 |
79 |
{label}
80 |
{content}
81 |
82 | ); 83 | }; 84 | 85 | export const DownloadV1Details = (props:DownloadV1Model) => { 86 | const { displayFilename = false,displaySize = false, displayFormat = false} = props; 87 | 88 | return ( 89 |
90 | {displayFilename && renderProperty('Filename', props.filename, 'filename',props.baseCssClass)} 91 | {displaySize && renderProperty('Size', props.size, 'size',props.baseCssClass)} 92 | {displayFormat && renderProperty('Format', props.format, 'format',props.baseCssClass)} 93 |
94 | ) 95 | }; 96 | 97 | export const DownloadV1Description = (props:DownloadV1Model) => { 98 | const html:string = String(props.description) || ''; 99 | return
100 | }; 101 | 102 | export const DownloadV1Link = (props:DownloadV1Model) => { 103 | 104 | const handleOnClick = (event:MouseEvent) => { 105 | props.handleOnClick && props.handleOnClick(event); 106 | }; 107 | 108 | return ( 109 | 110 | {props.actionText} 111 | 112 | ) 113 | }; 114 | 115 | export const DownloadV1Impl = (props:DownloadV1Model) => { 116 | 117 | const { displayFilename = false ,displaySize = false, displayFormat = false} = props; 118 | 119 | const cssClass = props.baseCssClass + ( props.isInEditor ? ' cq-dd-file' : ''); 120 | const displayDownloadDetails = displayFilename || displaySize || displayFormat; 121 | return ( 122 |
123 | {!!props.title && } 124 | {!!props.description && } 125 | {displayDownloadDetails && } 126 | 127 |
128 | ); 129 | 130 | }; 131 | 132 | 133 | 134 | const DownloadV1 = (props:DownloadV1Model) => { 135 | 136 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(DownloadV1Impl, "cmp-download"), DownloadV1IsEmptyFn, "Download V1"); 137 | return 138 | }; 139 | 140 | export default DownloadV1; -------------------------------------------------------------------------------- /src/authoring/download/v1/DownloadV1IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {DownloadV1Model} from "./DownloadV1"; 2 | 3 | 4 | export function DownloadV1IsEmptyFn(props:DownloadV1Model): boolean{ 5 | return (props.url == null || props.url.length === 0) && props.handleOnClick == null; 6 | } -------------------------------------------------------------------------------- /src/authoring/image/v2/ImageV2.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | import ImageV2, {ImageV2Model} from "./ImageV2"; 22 | import {ImageV2IsEmptyFn} from "./ImageV2IsEmptyFn"; 23 | 24 | it('Has a proper isEmpty function', () => { 25 | 26 | const props1:ImageV2Model = { 27 | src: "/content/dam/image.jpg", 28 | alt: "Some Image" 29 | }; 30 | 31 | expect(ImageV2IsEmptyFn(props1)).toEqual(false); 32 | 33 | const props2:ImageV2Model = { 34 | src: " ", 35 | alt: "Some Image" 36 | }; 37 | 38 | expect(ImageV2IsEmptyFn(props2)).toEqual(true); 39 | 40 | }); 41 | 42 | it('Renders without crashing', () => { 43 | const div = document.createElement('div'); 44 | 45 | ReactDOM.render( 46 | , 47 | div 48 | ); 49 | ReactDOM.unmountComponentAtNode(div); 50 | expect(1).toBe(1); 51 | }); 52 | 53 | it('Renders with a cq-dd-image in edit mode', () => { 54 | 55 | //let captured = false; 56 | const props:ImageV2Model = { 57 | src: "/content/dam/image.jpg", 58 | alt: "Some Image", 59 | isInEditor: true 60 | }; 61 | 62 | const image = mount(); 63 | 64 | expect(image.find(".cq-dd-image")).toHaveLength(1); 65 | 66 | expect(image).toBeDefined(); 67 | }); 68 | 69 | 70 | it('Renders without link', () => { 71 | 72 | //let captured = false; 73 | const props:ImageV2Model = { 74 | src: "/content/dam/image.jpg", 75 | alt: "Some Image" 76 | }; 77 | 78 | const image = mount(); 79 | 80 | expect(image).toBeDefined(); 81 | 82 | const anchor = image.find("a"); 83 | 84 | expect(anchor).toHaveLength(0); 85 | 86 | const title = image.find(".cmp-image__title"); 87 | 88 | expect(title).toHaveLength(0); 89 | 90 | const img = image.find("img"); 91 | 92 | 93 | expect(img.prop("alt")).toEqual(props.alt); 94 | expect(img.prop("src")).toEqual(props.src); 95 | 96 | 97 | }); 98 | 99 | it('Renders with title', () => { 100 | 101 | //let captured = false; 102 | const props:ImageV2Model = { 103 | src: "/content/dam/image.jpg", 104 | alt: "Some Image", 105 | title: "Awesome Title!" 106 | }; 107 | 108 | const image = mount(); 109 | 110 | const title = image.find(".cmp-image__title"); 111 | 112 | expect(title).toHaveLength(1); 113 | expect(title.text()).toEqual(props.title); 114 | 115 | 116 | }); 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/authoring/image/v2/ImageV2.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React, {Component} from 'react'; 18 | import {withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 19 | import {RoutedCoreComponentModel} from "../../../routing/RoutedCoreComponent"; 20 | import {RoutedLink} from "../../../routing/RoutedLink"; 21 | import {ImageV2IsEmptyFn} from "./ImageV2IsEmptyFn"; 22 | 23 | 24 | export interface ImageV2Model extends RoutedCoreComponentModel{ 25 | src: string 26 | alt: string 27 | displayPopupTitle?: boolean 28 | title?: string 29 | link?: string 30 | } 31 | 32 | const ImageV2InnerContents = (props:ImageV2Model) => { 33 | return ( 34 | <> 35 | {props.alt}/ 38 | { 39 | !!(props.title) && {props.title} 40 | } 41 | { 42 | props.displayPopupTitle && (!!props.title) && 43 | } 44 | 45 | ); 46 | }; 47 | 48 | const ImageV2Contents = (props:ImageV2Model) => { 49 | if( props.link && props.link.trim().length > 0){ 50 | return ( 51 | 52 | 53 | 54 | ) 55 | } 56 | return 57 | }; 58 | 59 | const ImageV2Impl = (props:ImageV2Model) => { 60 | 61 | const {isInEditor = false} = props; 62 | const cssClassName = (isInEditor) ? props.baseCssClass + ' cq-dd-image' : props.baseCssClass; 63 | 64 | return ( 65 |
66 | 67 |
68 | ) 69 | 70 | }; 71 | 72 | const ImageV2 = (props:ImageV2Model) => { 73 | 74 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(ImageV2Impl, "cmp-image"), ImageV2IsEmptyFn, "Image V2"); 75 | return 76 | }; 77 | 78 | export default ImageV2; -------------------------------------------------------------------------------- /src/authoring/image/v2/ImageV2IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {ImageV2Model} from "./ImageV2"; 2 | 3 | export function ImageV2IsEmptyFn(props:ImageV2Model) { 4 | return (!props.src) || props.src.trim().length === 0; 5 | } 6 | -------------------------------------------------------------------------------- /src/authoring/list/v2/ListV2.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | import {dummyProps, dummyPropsWithDefaultRouting, dummyPropsWithRoutedItems} from "./ListV2TestMockItems"; 22 | import ListV2 from "./ListV2"; 23 | import {MemoryRouter} from 'react-router-dom'; 24 | 25 | import fs from 'fs'; 26 | import path from 'path'; 27 | 28 | const loadFormattedHtml = (relativePath:string) => { 29 | const expectedRoutedList = fs.readFileSync(path.resolve(__dirname, relativePath), 'utf8'); 30 | return expectedRoutedList.replace(/(\r\n|\n|\r)/gm,"").replace(/>\s+|\s+ { 42 | const div = document.createElement('div'); 43 | ReactDOM.render( 44 | , 45 | div 46 | ); 47 | ReactDOM.unmountComponentAtNode(div); 48 | expect(1).toBe(1); 49 | }); 50 | 51 | 52 | it('Renders a basic list properly', () => { 53 | 54 | const wrapper = mount(); 55 | expect(wrapper.html()).toEqual(expectedList); 56 | 57 | }); 58 | 59 | 60 | 61 | it('Renders with routing from the items', () => { 62 | 63 | const wrapper = mount(); 64 | expect(wrapper.html()).toEqual(expectedRoutedList); 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /src/authoring/list/v2/ListV2.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import React from 'react'; 19 | import {HasBaseCssClass, withConditionalPlaceHolder,withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 20 | import { RoutedCoreComponentModel, RoutedModel} from "../../../routing/RoutedCoreComponent"; 21 | import {RoutedLink} from "../../../routing/RoutedLink"; 22 | import {ListV2IsEmptyFn} from "./ListV2IsEmptyFn"; 23 | 24 | 25 | export interface ListV2Item extends RoutedModel,HasBaseCssClass{ 26 | index?: number 27 | url?:string 28 | lastModified?:number 29 | lastModifiedFormatted?:string 30 | description?:string 31 | path:string 32 | title:string, 33 | showModificationDate?: boolean 34 | } 35 | 36 | 37 | export interface ListV2Model extends RoutedCoreComponentModel{ 38 | items:ListV2Item[] 39 | dateFormatString: string 40 | showDescription: boolean 41 | showModificationDate: boolean 42 | linkItems: boolean 43 | } 44 | 45 | export const ListV2ItemModificationDate = (item:ListV2Item) => { 46 | const dateStringToDisplay = item.lastModifiedFormatted ? item.lastModifiedFormatted : ""; 47 | return ( 48 | {dateStringToDisplay} 49 | ) 50 | }; 51 | 52 | export const ListV2ItemContent = (item:ListV2Item) => { 53 | return ( 54 | <> 55 | {item.title} 56 | {item.showModificationDate && } 57 | 58 | ) 59 | }; 60 | 61 | export const ListV2Anchor = (item:ListV2Item) => { 62 | 63 | return ( 64 | 65 | 66 | 67 | ) 68 | }; 69 | 70 | export const ListV2ItemDescription = (item: ListV2Item) => { 71 | return ( 72 | ${item.description} 73 | ) 74 | }; 75 | 76 | export const ListV2Impl = (props:ListV2Model) => { 77 | 78 | const ListV2Item = (item:ListV2Item) => { 79 | return ( 80 |
  • 81 |
    82 | {props.linkItems && !!item.url && } 83 | {!props.linkItems && } 84 | {props.showDescription && } 85 |
    86 |
  • 87 | ) 88 | }; 89 | 90 | return ( 91 |
      92 | {props.items.map((item, index) => 93 | )} 97 |
    98 | ) 99 | }; 100 | 101 | 102 | const ListV2 = (props:ListV2Model) => { 103 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(ListV2Impl, "cmp-list"), ListV2IsEmptyFn, "List V2") 104 | return 105 | }; 106 | 107 | export default ListV2; -------------------------------------------------------------------------------- /src/authoring/list/v2/ListV2IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {ListV2Model} from "./ListV2"; 2 | 3 | export function ListV2IsEmptyFn(props:ListV2Model): boolean{ 4 | return props.items == null || props.items.length === 0; 5 | } -------------------------------------------------------------------------------- /src/authoring/list/v2/ListV2TestMockItems.ts: -------------------------------------------------------------------------------- 1 | import {ListV2Item, ListV2Model} from './ListV2'; 2 | 3 | 4 | export const mockItems:ListV2Item[] = [ 5 | { 6 | "url": "/content/core-components-examples/library/page-authoring/title.html", 7 | "lastModified": 1547642198741, 8 | "description": "Display a page heading", 9 | "title": "Title", 10 | "path": "/content/core-components-examples/library/page-authoring/title" 11 | }, 12 | { 13 | "url": "/content/core-components-examples/library/page-authoring/text.html", 14 | "lastModified": 1548159422163, 15 | "description": "Display a rich text paragraph", 16 | "title": "Text", 17 | "path": "/content/core-components-examples/library/page-authoring/text" 18 | }, 19 | { 20 | "url": "/content/core-components-examples/library/page-authoring/image.html", 21 | "lastModified": 1550255022224, 22 | "description": "Display an image asset", 23 | "title": "Image", 24 | "path": "/content/core-components-examples/library/page-authoring/image" 25 | }, 26 | { 27 | "url": "/content/core-components-examples/library/page-authoring/button.html", 28 | "lastModified": 1547062227177, 29 | "description": "Display a button or anchor button", 30 | "title": "Button", 31 | "path": "/content/core-components-examples/library/page-authoring/button" 32 | }, 33 | { 34 | "url": "/content/core-components-examples/library/page-authoring/teaser.html", 35 | "lastModified": 1575799718587, 36 | "description": "Link an image and text", 37 | "title": "Teaser", 38 | "path": "/content/core-components-examples/library/page-authoring/teaser" 39 | }, 40 | { 41 | "url": "/content/core-components-examples/library/page-authoring/download.html", 42 | "lastModified": 1558992253683, 43 | "description": "Display an asset for download", 44 | "title": "Download", 45 | "path": "/content/core-components-examples/library/page-authoring/download" 46 | }, 47 | { 48 | "url": "/content/core-components-examples/library/page-authoring/list.html", 49 | "lastModified": 1547642282466, 50 | "description": "Display a list of pages", 51 | "title": "List", 52 | "path": "/content/core-components-examples/library/page-authoring/list" 53 | }, 54 | { 55 | "url": "/content/core-components-examples/library/page-authoring/experience-fragment.html", 56 | "lastModified": 1566294323252, 57 | "description": "Display an experience fragment", 58 | "title": "Experience Fragment", 59 | "path": "/content/core-components-examples/library/page-authoring/experience-fragment" 60 | }, 61 | { 62 | "url": "/content/core-components-examples/library/page-authoring/content-fragment.html", 63 | "lastModified": 1547644839952, 64 | "description": "Display a content fragment asset", 65 | "title": "Content Fragment", 66 | "path": "/content/core-components-examples/library/page-authoring/content-fragment" 67 | }, 68 | { 69 | "url": "/content/core-components-examples/library/page-authoring/content-fragment-list.html", 70 | "lastModified": 1554130037469, 71 | "description": "Display a list of content fragments", 72 | "title": "Content Fragment List", 73 | "path": "/content/core-components-examples/library/page-authoring/content-fragment-list" 74 | }, 75 | { 76 | "url": "/content/core-components-examples/library/page-authoring/embed.html", 77 | "lastModified": 1567092519658, 78 | "description": "Embed a third-party widget", 79 | "title": "Embed", 80 | "path": "/content/core-components-examples/library/page-authoring/embed" 81 | }, 82 | { 83 | "url": "/content/core-components-examples/library/page-authoring/social-sharing.html", 84 | "lastModified": 1547062206375, 85 | "description": "Add social sharing links", 86 | "title": "Social Sharing", 87 | "path": "/content/core-components-examples/library/page-authoring/social-sharing" 88 | }, 89 | { 90 | "url": "/content/core-components-examples/library/page-authoring/separator.html", 91 | "lastModified": 1547062195738, 92 | "description": "Display a section divider", 93 | "title": "Separator", 94 | "path": "/content/core-components-examples/library/page-authoring/separator" 95 | } 96 | ]; 97 | 98 | 99 | export const mockItemsWithRouting:ListV2Item[] = [ 100 | { 101 | "url": "/content/core-components-examples/library/page-authoring/title.html", 102 | "lastModified": 1547642198741, 103 | "description": "Display a page heading", 104 | "title": "Title", 105 | "path": "/content/core-components-examples/library/page-authoring/title", 106 | "routed": true 107 | }, 108 | { 109 | "url": "/content/core-components-examples/library/page-authoring/text.html", 110 | "lastModified": 1548159422163, 111 | "description": "Display a rich text paragraph", 112 | "title": "Text", 113 | "path": "/content/core-components-examples/library/page-authoring/text", 114 | "routed": true 115 | }, 116 | { 117 | "url": "/content/core-components-examples/library/page-authoring/image.html", 118 | "lastModified": 1550255022224, 119 | "description": "Display an image asset", 120 | "title": "Image", 121 | "path": "/content/core-components-examples/library/page-authoring/image", 122 | "routed": true 123 | }, 124 | { 125 | "url": "/content/core-components-examples/library/page-authoring/button.html", 126 | "lastModified": 1547062227177, 127 | "description": "Display a button or anchor button", 128 | "title": "Button", 129 | "path": "/content/core-components-examples/library/page-authoring/button", 130 | "routed": true 131 | }, 132 | { 133 | "url": "/content/core-components-examples/library/page-authoring/teaser.html", 134 | "lastModified": 1575799718587, 135 | "description": "Link an image and text", 136 | "title": "Teaser", 137 | "path": "/content/core-components-examples/library/page-authoring/teaser", 138 | "routed": true 139 | }, 140 | { 141 | "url": "/content/core-components-examples/library/page-authoring/download.html", 142 | "lastModified": 1558992253683, 143 | "description": "Display an asset for download", 144 | "title": "Download", 145 | "path": "/content/core-components-examples/library/page-authoring/download", 146 | "routed": true 147 | }, 148 | { 149 | "url": "/content/core-components-examples/library/page-authoring/list.html", 150 | "lastModified": 1547642282466, 151 | "description": "Display a list of pages", 152 | "title": "List", 153 | "path": "/content/core-components-examples/library/page-authoring/list", 154 | "routed": true 155 | }, 156 | { 157 | "url": "/content/core-components-examples/library/page-authoring/experience-fragment.html", 158 | "lastModified": 1566294323252, 159 | "description": "Display an experience fragment", 160 | "title": "Experience Fragment", 161 | "path": "/content/core-components-examples/library/page-authoring/experience-fragment", 162 | "routed": true 163 | }, 164 | { 165 | "url": "/content/core-components-examples/library/page-authoring/content-fragment.html", 166 | "lastModified": 1547644839952, 167 | "description": "Display a content fragment asset", 168 | "title": "Content Fragment", 169 | "path": "/content/core-components-examples/library/page-authoring/content-fragment", 170 | "routed": true 171 | }, 172 | { 173 | "url": "/content/core-components-examples/library/page-authoring/content-fragment-list.html", 174 | "lastModified": 1554130037469, 175 | "description": "Display a list of content fragments", 176 | "title": "Content Fragment List", 177 | "path": "/content/core-components-examples/library/page-authoring/content-fragment-list", 178 | "routed": true 179 | }, 180 | { 181 | "url": "/content/core-components-examples/library/page-authoring/embed.html", 182 | "lastModified": 1567092519658, 183 | "description": "Embed a third-party widget", 184 | "title": "Embed", 185 | "path": "/content/core-components-examples/library/page-authoring/embed", 186 | "routed": true 187 | }, 188 | { 189 | "url": "/content/core-components-examples/library/page-authoring/social-sharing.html", 190 | "lastModified": 1547062206375, 191 | "description": "Add social sharing links", 192 | "title": "Social Sharing", 193 | "path": "/content/core-components-examples/library/page-authoring/social-sharing", 194 | "routed": true 195 | }, 196 | { 197 | "url": "/content/core-components-examples/library/page-authoring/separator.html", 198 | "lastModified": 1547062195738, 199 | "description": "Display a section divider", 200 | "title": "Separator", 201 | "path": "/content/core-components-examples/library/page-authoring/separator", 202 | "routed": true 203 | } 204 | ]; 205 | 206 | export const dummyProps:ListV2Model = { 207 | hidePlaceHolder: false, 208 | isInEditor: false, 209 | dateFormatString: "yyyy-MM-dd", 210 | showDescription: true, 211 | showModificationDate: true, 212 | linkItems: true, 213 | routed: false, 214 | items: mockItems 215 | }; 216 | 217 | export const dummyPropsWithDefaultRouting:ListV2Model = { 218 | hidePlaceHolder: false, 219 | isInEditor: false, 220 | dateFormatString: "yyyy-MM-dd", 221 | showDescription: true, 222 | showModificationDate: true, 223 | linkItems: true, 224 | routed: true, 225 | items: mockItems 226 | }; 227 | 228 | export const dummyPropsWithRoutedItems:ListV2Model = { 229 | hidePlaceHolder: false, 230 | isInEditor: false, 231 | dateFormatString: "yyyy-MM-dd", 232 | showDescription: true, 233 | showModificationDate: true, 234 | linkItems: true, 235 | items: mockItemsWithRouting 236 | }; 237 | -------------------------------------------------------------------------------- /src/authoring/list/v2/expected-list.html: -------------------------------------------------------------------------------- 1 |
      2 |
    • 3 |
      Title$Display a page heading
      4 |
    • 5 |
    • 6 |
      Text$Display a rich text paragraph
      7 |
    • 8 |
    • 9 |
      Image$Display an image asset
      10 |
    • 11 |
    • 12 |
      Button$Display a button or anchor button
      13 |
    • 14 |
    • 15 | 16 |
    • 17 |
    • 18 | 19 |
    • 20 |
    • 21 |
      List$Display a list of pages
      22 |
    • 23 |
    • 24 | 25 |
    • 26 |
    • 27 | 28 |
    • 29 |
    • 30 | 31 |
    • 32 |
    • 33 |
      Embed$Embed a third-party widget
      34 |
    • 35 |
    • 36 | 37 |
    • 38 |
    • 39 | 40 |
    • 41 |
    -------------------------------------------------------------------------------- /src/authoring/list/v2/expected-routed-list.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/authoring/separator/v1/SeparatorV1.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import ReactDOM from "react-dom"; 18 | 19 | import React from "react"; 20 | import SeparatorV1 from "./SeparatorV1"; 21 | import {mount} from "enzyme"; 22 | 23 | it('Renders without crashing', () => { 24 | const div = document.createElement('div'); 25 | ReactDOM.render( 26 | , 27 | div 28 | ); 29 | ReactDOM.unmountComponentAtNode(div); 30 | expect(1).toBe(1); 31 | }); 32 | 33 | it('Renders as expected', ()=> { 34 | const element = mount(); 35 | 36 | const html = "

    "; 37 | expect(element.html()).toEqual(html); 38 | }) 39 | -------------------------------------------------------------------------------- /src/authoring/separator/v1/SeparatorV1.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React, {Component} from "react"; 18 | import {CoreComponentModel, withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 19 | import {SeparatorV1IsEmptyFn} from "./SeparatorV1IsEmptyFn"; 20 | 21 | const SeparatorV1Impl = (props:CoreComponentModel) => { 22 | 23 | return ( 24 |
    25 |
    26 |
    27 | ) 28 | 29 | }; 30 | 31 | const SeparatorV1 = (props:CoreComponentModel) => { 32 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(SeparatorV1Impl, "cmp-separator"), SeparatorV1IsEmptyFn, "Separator V1") 33 | return 34 | }; 35 | 36 | export default SeparatorV1; -------------------------------------------------------------------------------- /src/authoring/separator/v1/SeparatorV1IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {CoreComponentModel} from "../../../AbstractCoreComponent"; 2 | 3 | export function SeparatorV1IsEmptyFn(props:CoreComponentModel): boolean{ 4 | return false 5 | } -------------------------------------------------------------------------------- /src/authoring/teaser/v1/TeaserV1.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import ReactDOM from "react-dom"; 18 | 19 | import React from "react"; 20 | import TeaserV1, {TeaserV1Model} from "./TeaserV1"; 21 | import {mount} from "enzyme"; 22 | 23 | const defaultProps:TeaserV1Model = { 24 | imageAlt: "snowy mountains", 25 | imagePath: "/some/image.png", 26 | description: '

    Paragraph

    ', 27 | routed: false, 28 | actions: [ 29 | { 30 | routed: false, 31 | title: "Link1", 32 | URL: "/content/link1" 33 | }, 34 | { 35 | routed: false, 36 | title: "Link2", 37 | URL: "/content/link2" 38 | } 39 | ], 40 | actionsEnabled: true, 41 | imageLinkHidden: false, 42 | linkURL: "/some/url.html", 43 | title: "Some title", 44 | pretitle: "Custom pretitle", 45 | titleType: "h2", 46 | titleLinkHidden: false 47 | } 48 | 49 | it('Renders without crashing', () => { 50 | const div = document.createElement('div'); 51 | ReactDOM.render( 52 | , 53 | div 54 | ); 55 | ReactDOM.unmountComponentAtNode(div); 56 | expect(1).toBe(1); 57 | }); 58 | 59 | it('Renders as expected', ()=> { 60 | const element = mount(); 61 | 62 | const content = element.find(".cmp-teaser__content"); 63 | expect(content).toHaveLength(1); 64 | 65 | //description 66 | const description = content.find('.cmp-teaser__description'); 67 | expect(description).toHaveLength(1); 68 | expect(description.html()).toEqual('
    ' + defaultProps.description + '
    '); 69 | 70 | //pretitle 71 | const pretitle = content.find('.cmp-teaser__pretitle'); 72 | expect(pretitle).toHaveLength(1); 73 | expect(pretitle.html()).toEqual('
    ' + defaultProps.pretitle + '
    '); 74 | 75 | //title 76 | const title = content.find('.cmp-teaser__title'); 77 | expect(title).toHaveLength(1); 78 | const expectedHtml = `
    <${defaultProps.titleType} class=\"cmp-teaser__title-text\">${defaultProps.title}
    `; 79 | expect(title.html()).toEqual(expectedHtml); 80 | 81 | 82 | //image 83 | const image = element.find(".cmp-teaser__image"); 84 | expect(image).toHaveLength(1); 85 | 86 | const img = image.find("img"); 87 | expect(img.prop("src")).toEqual(defaultProps.imagePath); 88 | expect(img.prop("alt")).toEqual(defaultProps.imageAlt) 89 | 90 | 91 | //actions 92 | const actionContainer = element.find(".cmp-teaser__action-container"); 93 | expect(actionContainer).toHaveLength(1); 94 | 95 | const actions = actionContainer.find("a.cmp-teaser__action-link"); 96 | expect(actions).toHaveLength(2); 97 | 98 | 99 | }); 100 | -------------------------------------------------------------------------------- /src/authoring/teaser/v1/TeaserV1.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import React, {Component} from "react"; 19 | import {withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 20 | import ImageV1 from "../../../authoring/image/v2/ImageV2"; 21 | import TitleV1 from "../../../authoring/title/v2/TitleV2"; 22 | import {RoutedLink} from "../../../routing/RoutedLink"; 23 | import {RoutedCoreComponentModel, RoutedModel} from "../../../routing/RoutedCoreComponent"; 24 | import {TeaserV1IsEmptyFn} from "./TeaserV1IsEmptyFn"; 25 | 26 | export interface TeaserV1Action extends RoutedModel{ 27 | title: string 28 | URL: string 29 | } 30 | 31 | export interface TeaserV1Model extends RoutedCoreComponentModel{ 32 | pretitle?: string 33 | title: string 34 | description?: string 35 | titleType: string 36 | linkURL: string 37 | actionsEnabled: boolean 38 | imageLinkHidden: boolean 39 | imageAlt: string 40 | titleLinkHidden: boolean 41 | actions: TeaserV1Action[] 42 | imagePath: string 43 | } 44 | 45 | const generateLink = (props:TeaserV1Model, action:TeaserV1Action, index:number) => { 46 | return ${action.title} 47 | } 48 | 49 | const TeaserV1Image = (props:TeaserV1Model) => { 50 | return ( 51 |
    52 | 53 |
    54 | ); 55 | }; 56 | 57 | const TeaserV1PreTitle = (props:TeaserV1Model) =>
    {props.pretitle}
    ; 58 | 59 | const TeaserV1Title = (props:TeaserV1Model) => 60 | ; 68 | 69 | 70 | const TeaserV1Description = (props:TeaserV1Model) => { 71 | const text:string = props.description as string; 72 | return
    ; 73 | }; 74 | 75 | const TeaserV1Actions = (props:TeaserV1Model) => { 76 | return ( 77 |
    78 | { 79 | props.actions.map((action, index) => { 80 | return generateLink(props,action,index) 81 | }) 82 | } 83 |
    84 | ) 85 | }; 86 | 87 | const TeaserV1Impl = (props:TeaserV1Model) => { 88 | const cssClass = props.baseCssClass || '' + (props.isInEditor) ? ' cq-dd-image' : ''; 89 | const showActions:boolean = ( props.actions.length > 0 ) && props.actionsEnabled; 90 | return ( 91 |
    92 | {props.imagePath && } 93 |
    94 | {props.pretitle && } 95 | {props.title && } 96 | {props.description && } 97 | {showActions && } 98 |
    99 |
    100 | ) 101 | }; 102 | 103 | 104 | const TeaserV1 = (props:TeaserV1Model) => { 105 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(TeaserV1Impl, "cmp-teaser"), TeaserV1IsEmptyFn, "Teaser V1") 106 | return 107 | }; 108 | 109 | export default TeaserV1; -------------------------------------------------------------------------------- /src/authoring/teaser/v1/TeaserV1IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import {TeaserV1Model} from "./TeaserV1"; 19 | 20 | export function TeaserV1IsEmptyFn(props:TeaserV1Model): boolean{ 21 | return (!props.imagePath && !props.description && props.actions.length == 0) 22 | } 23 | -------------------------------------------------------------------------------- /src/authoring/text/v2/TextV2.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | import TextV2, {TextV2Model} from './TextV2'; 22 | import {TextV2IsEmptyFn} from "./TextV2IsEmptyFn"; 23 | 24 | it('Has a proper isEmpty function', () => { 25 | 26 | const props:TextV2Model = { 27 | text: 'test', 28 | richText: true 29 | }; 30 | 31 | expect(TextV2IsEmptyFn(props)).toBe(false); 32 | 33 | const propsEmpty:TextV2Model = { 34 | richText: true, 35 | text: '' 36 | }; 37 | 38 | expect(TextV2IsEmptyFn(propsEmpty)).toBe(true); 39 | }); 40 | 41 | it('Renders without crashing', () => { 42 | const div = document.createElement('div'); 43 | ReactDOM.render( 44 | , 45 | div 46 | ); 47 | ReactDOM.unmountComponentAtNode(div); 48 | expect(1).toBe(1); 49 | }); 50 | 51 | 52 | it('Renders plain text', () => { 53 | 54 | const element = mount(); 55 | 56 | const p = element.find('.cmp-text__paragraph'); 57 | expect(p.text()).toEqual("plain text"); 58 | 59 | }); 60 | 61 | 62 | it('Renders rich text', () => { 63 | 64 | const richText = '
    richtext
    '; 65 | const expectedHtml = '
    richtext
    '; 66 | const element = mount(); 67 | 68 | const actualHtml = element.html(); 69 | expect(actualHtml).toEqual(expectedHtml); 70 | 71 | }); -------------------------------------------------------------------------------- /src/authoring/text/v2/TextV2.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React, {Component} from 'react'; 18 | import {CoreComponentModel, withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 19 | import {TextV2IsEmptyFn} from "./TextV2IsEmptyFn"; 20 | 21 | export interface TextV2Model extends CoreComponentModel{ 22 | text?: string; 23 | richText?: boolean 24 | cqPath?:string 25 | id?: string 26 | } 27 | 28 | export const TextV2RichText = (props:TextV2Model) => { 29 | const text:string = props.text as string; 30 | const id = (props.id) ? props.id : (props.cqPath ? props.cqPath.substr(props.cqPath.lastIndexOf('/') + 1) : ""); 31 | 32 | return
    33 | }; 34 | 35 | export const TextV2PlainText = (props:TextV2Model) => { 36 | return

    {props.text}

    37 | }; 38 | 39 | const TextV2Impl = (props:TextV2Model) => { 40 | const {richText = false} = props; 41 | return (richText) ? : ; 42 | }; 43 | 44 | const TextV2 = (props:TextV2Model) => { 45 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(TextV2Impl, "cmp-text"), TextV2IsEmptyFn, "Text V2") 46 | return 47 | }; 48 | 49 | export default TextV2; -------------------------------------------------------------------------------- /src/authoring/text/v2/TextV2IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {TextV2Model} from "./TextV2"; 2 | 3 | export function TextV2IsEmptyFn(props:TextV2Model): boolean{ 4 | return props.text == null || props.text.length === 0; 5 | } -------------------------------------------------------------------------------- /src/authoring/title/v2/TitleV2.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | import TitleV2, {TitleV2Model} from "./TitleV2"; 21 | 22 | it('Renders without crashing', () => { 23 | const div = document.createElement('div'); 24 | 25 | const props:TitleV2Model = { 26 | hidePlaceHolder: false, 27 | isInEditor: false, 28 | linkDisabled: false, 29 | routed: false, 30 | text: 'Hello World' 31 | }; 32 | 33 | ReactDOM.render( 34 | , 35 | div 36 | ); 37 | ReactDOM.unmountComponentAtNode(div); 38 | expect(1).toBe(1); 39 | }); 40 | 41 | 42 | it('Renders without link', () => { 43 | 44 | const props:TitleV2Model = { 45 | hidePlaceHolder: false, 46 | isInEditor: false, 47 | routed: false, 48 | linkDisabled: false, 49 | text: 'My awesome title' 50 | }; 51 | 52 | const element = mount(); 53 | 54 | const heading = element.find("h3"); 55 | 56 | expect(heading).toHaveLength(1); 57 | 58 | }); 59 | 60 | it('Renders a custom type without link', () => { 61 | 62 | const props:TitleV2Model = { 63 | hidePlaceHolder: false, 64 | isInEditor: false, 65 | linkDisabled: false, 66 | routed: false, 67 | type: 'h2', 68 | text: 'My awesome title' 69 | }; 70 | 71 | const element = mount(); 72 | 73 | const heading = element.find("h2"); 74 | 75 | expect(heading).toHaveLength(1); 76 | 77 | }); 78 | 79 | 80 | it('Renders a custom type with a link', () => { 81 | 82 | const props:TitleV2Model = { 83 | hidePlaceHolder: false, 84 | isInEditor: false, 85 | linkDisabled: false, 86 | routed: false, 87 | type: 'h2', 88 | text: 'My awesome title', 89 | linkURL: '/content/some/page.html' 90 | }; 91 | 92 | const element = mount(); 93 | 94 | const heading = element.find("h2"); 95 | 96 | expect(heading).toHaveLength(1); 97 | 98 | const anchor = element.find("a.cmp-title__link"); 99 | 100 | expect(anchor).toHaveLength(1); 101 | 102 | }); 103 | 104 | -------------------------------------------------------------------------------- /src/authoring/title/v2/TitleV2.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React, {Component} from 'react'; 18 | import {withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 19 | import {RoutedCoreComponentModel} from "../../../routing/RoutedCoreComponent"; 20 | import {RoutedLink} from "../../../routing/RoutedLink"; 21 | import {TitleV2IsEmptyFn} from "./TitleV2IsEmptyFn"; 22 | 23 | export interface TitleV2Model extends RoutedCoreComponentModel{ 24 | text: string; 25 | linkURL?: string; 26 | linkDisabled: boolean; 27 | type?: string; 28 | nested?: boolean 29 | } 30 | 31 | const bemModifierPrefix = (props:TitleV2Model) => props.nested ? '-' : '__'; 32 | 33 | export const TitleV2Link = (props:TitleV2Model) => { 34 | return ( 35 | 36 | {props.text} 37 | 38 | ); 39 | }; 40 | 41 | export const TitleV2Contents = (props:TitleV2Model) => { 42 | if( !props.linkDisabled){ 43 | return 44 | } 45 | 46 | return <>{props.text} 47 | }; 48 | 49 | const TitleV2Impl = (props:TitleV2Model) => { 50 | const elementType:string = (!!props.type) ? props.type.toString() : 'h3'; 51 | return ( 52 |
    53 | { 54 | React.createElement(elementType, 55 | { 56 | className: props.baseCssClass + bemModifierPrefix(props) + 'text', 57 | }, 58 | 59 | ) 60 | } 61 | 62 |
    63 | ) 64 | }; 65 | 66 | const TitleV2 = (props:TitleV2Model) => { 67 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(TitleV2Impl, "cmp-title"), TitleV2IsEmptyFn, "TitleV2") 68 | return 69 | }; 70 | 71 | export default TitleV2; -------------------------------------------------------------------------------- /src/authoring/title/v2/TitleV2IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {TitleV2Model} from "./TitleV2"; 2 | 3 | export function TitleV2IsEmptyFn(props:TitleV2Model): boolean{ 4 | return props.text == null || props.text.trim().length === 0; 5 | } -------------------------------------------------------------------------------- /src/common/placeholder.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import React, {Component} from 'react'; 17 | 18 | export interface PlaceHolderModel { 19 | componentTitle?: string 20 | classAppend?: string 21 | emptyTextAppend?: string 22 | } 23 | const DEFAULT_EMPTY_TEXT_LABEL = 'Please configure the component'; 24 | 25 | export const EditorPlaceHolder = (props:PlaceHolderModel) => { 26 | 27 | const part1: string = (props.componentTitle != null && props.componentTitle.length > 0) ? props.componentTitle + ' - ' : ''; 28 | const part2: string = (props.emptyTextAppend != null) ? props.emptyTextAppend : DEFAULT_EMPTY_TEXT_LABEL; 29 | const emptyText = part1 + part2; 30 | 31 | return ( 32 |
    34 | {emptyText} 35 |
    36 | ) 37 | }; -------------------------------------------------------------------------------- /src/default/v1/DefaultV1Component.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | import DefaultV1Component, {DefaultV1Model} from "./DefaultV1Component"; 22 | import {DefaultV1IsEmptyFn} from "./DefaultV1ComponentIsEmptyFn"; 23 | 24 | it('Renders without crashing', () => { 25 | const div = document.createElement('div'); 26 | ReactDOM.render( 27 | test"} />, 28 | div 29 | ); 30 | ReactDOM.unmountComponentAtNode(div); 31 | expect(1).toBe(1); 32 | }); 33 | 34 | it('Has a proper isEmpty function', () => { 35 | 36 | const props1:DefaultV1Model = { 37 | html:"

    some content

    ", 38 | isInEditor: false, 39 | hidePlaceHolder: false 40 | }; 41 | 42 | expect(DefaultV1IsEmptyFn(props1)).toEqual(false); 43 | 44 | const props2:DefaultV1Model = { 45 | html:" ", 46 | isInEditor: false, 47 | hidePlaceHolder: false 48 | }; 49 | 50 | expect(DefaultV1IsEmptyFn(props2)).toEqual(true); 51 | 52 | }); 53 | 54 | it('Renders some proper HTML', () => { 55 | 56 | const props1:DefaultV1Model = { 57 | html:"

    some content

    ", 58 | isInEditor: false, 59 | hidePlaceHolder: false 60 | }; 61 | const element = mount(); 62 | expect(element.html()).toEqual("

    some content

    "); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /src/default/v1/DefaultV1Component.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {CoreComponentModel, withConditionalPlaceHolder} from "../../AbstractCoreComponent"; 18 | import React, {Component} from "react"; 19 | import {DefaultV1IsEmptyFn} from "./DefaultV1ComponentIsEmptyFn"; 20 | 21 | export interface DefaultV1Model extends CoreComponentModel{ 22 | html: string 23 | } 24 | 25 | const DefaultV1ComponentImpl = (props:DefaultV1Model) =>
    ; 26 | 27 | const DefaultV1Component = (props:DefaultV1Model) => { 28 | const Wrapped = withConditionalPlaceHolder(DefaultV1ComponentImpl, DefaultV1IsEmptyFn, "cmp-default", "Default SPA Component") 29 | return 30 | }; 31 | 32 | export default DefaultV1Component; -------------------------------------------------------------------------------- /src/default/v1/DefaultV1ComponentIsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {DefaultV1Model} from "./DefaultV1Component"; 2 | 3 | export function DefaultV1IsEmptyFn(props:DefaultV1Model): boolean{ 4 | return props.html == null || props.html.trim().length === 0; 5 | } -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import * as CoreComponents from "./index"; 2 | import * as Types from "./types"; 3 | import * as EmptyFunctions from "./isEmptyFunctions"; 4 | 5 | it('Imports everything property', () => { 6 | expect(CoreComponents).toBeDefined(); 7 | expect(EmptyFunctions).toBeDefined(); 8 | expect(Types).toBeDefined(); 9 | }); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | //general 17 | export * from "./AbstractCoreComponent"; 18 | 19 | export * from "./default/v1/DefaultV1Component"; 20 | export * from "./default/v1/DefaultV1ComponentIsEmptyFn"; 21 | export { default as DefaultV1Component } from "./default/v1/DefaultV1Component"; 22 | 23 | export * from "./authoring/list/v2/ListV2"; 24 | export * from "./authoring/list/v2/ListV2IsEmptyFn" 25 | export { default as ListV2 } from "./authoring/list/v2/ListV2"; 26 | 27 | 28 | export * from "./authoring/text/v2/TextV2"; 29 | export * from "./authoring/text/v2/TextV2IsEmptyFn"; 30 | export { default as TextV2 } from "./authoring/text/v2/TextV2"; 31 | 32 | export * from './authoring/button/v1/ButtonV1'; 33 | export * from './authoring/button/v1/ButtonV1IsEmptyFn'; 34 | export { default as ButtonV1 } from './authoring/button/v1/ButtonV1'; 35 | 36 | export * from "./authoring/title/v2/TitleV2"; 37 | export * from "./authoring/title/v2/TitleV2IsEmptyFn"; 38 | export { default as TitleV2 } from "./authoring/title/v2/TitleV2"; 39 | 40 | export * from "./authoring/image/v2/ImageV2"; 41 | export * from "./authoring/image/v2/ImageV2IsEmptyFn"; 42 | export { default as ImageV2 } from "./authoring/image/v2/ImageV2"; 43 | 44 | export * from "./authoring/teaser/v1/TeaserV1"; 45 | export * from "./authoring/teaser/v1/TeaserV1IsEmptyFn"; 46 | export { default as TeaserV1 } from "./authoring/teaser/v1/TeaserV1"; 47 | 48 | export * from "./authoring/download/v1/DownloadV1"; 49 | export * from "./authoring/download/v1/DownloadV1IsEmptyFn"; 50 | export { default as DownloadV1 } from "./authoring/download/v1/DownloadV1"; 51 | 52 | export * from "./authoring/separator/v1/SeparatorV1"; 53 | export * from "./authoring/separator/v1/SeparatorV1IsEmptyFn"; 54 | export { default as SeparatorV1 } from "./authoring/separator/v1/SeparatorV1"; 55 | 56 | //layout 57 | export * from './layout/breadcrumb/v2/BreadCrumbV2'; 58 | export * from './layout/breadcrumb/v2/BreadCrumbV2IsEmptyFn'; 59 | export { default as BreadCrumbV2 } from "./layout/breadcrumb/v2/BreadCrumbV2"; 60 | 61 | export * from "./layout/navigation/v1/NavigationV1"; 62 | export * from "./layout/navigation/v1/NavigationV1IsEmptyFn"; 63 | 64 | export * from "./layout/language-navigation/v1/LanguageNavigationV1"; 65 | export * from "./layout/language-navigation/v1/LanguageNavigationV1IsEmptyFn"; 66 | export { default as LanguageNavigationV1 } from "./layout/language-navigation/v1/LanguageNavigationV1"; -------------------------------------------------------------------------------- /src/isEmptyFunctions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | //exports is empty functions for react suspense 17 | 18 | export * from "./default/v1/DefaultV1ComponentIsEmptyFn"; 19 | export * from "./authoring/list/v2/ListV2IsEmptyFn" 20 | export * from "./authoring/text/v2/TextV2IsEmptyFn"; 21 | export * from './authoring/button/v1/ButtonV1IsEmptyFn'; 22 | export * from "./authoring/title/v2/TitleV2IsEmptyFn"; 23 | export * from "./authoring/image/v2/ImageV2IsEmptyFn"; 24 | export * from "./authoring/teaser/v1/TeaserV1IsEmptyFn"; 25 | export * from "./authoring/download/v1/DownloadV1IsEmptyFn"; 26 | export * from "./authoring/separator/v1/SeparatorV1IsEmptyFn"; 27 | export * from './layout/breadcrumb/v2/BreadCrumbV2IsEmptyFn'; 28 | export * from "./layout/navigation/v1/NavigationV1IsEmptyFn"; 29 | export * from "./layout/language-navigation/v1/LanguageNavigationV1IsEmptyFn"; -------------------------------------------------------------------------------- /src/layout/breadcrumb/v2/BreadCrumbV2.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | import BreadCrumbV2, {BreadCrumbV2ItemModel} from './BreadCrumbV2'; 22 | import {MemoryRouter} from 'react-router-dom'; 23 | 24 | 25 | it('Renders without crashing', () => { 26 | const div = document.createElement('div'); 27 | const items:BreadCrumbV2ItemModel[] = []; 28 | ReactDOM.render( 29 | , 30 | div 31 | ); 32 | ReactDOM.unmountComponentAtNode(div); 33 | expect(1).toBe(1); 34 | }); 35 | 36 | 37 | it('Renders breadcrumb items if provided', () => { 38 | const items:BreadCrumbV2ItemModel[] = [ 39 | {active:false,url:'/content/some/url.html',title:'Item1', routed: false}, 40 | {active:false,url:'/content/some/url.html',title:'Item2', routed: false}, 41 | {active:true,url:'/content/some/url.html',title:'Item3', routed: false} 42 | ]; 43 | 44 | 45 | const wrapper = mount(); 46 | expect(wrapper.find("li")).toHaveLength(3); 47 | 48 | expect(wrapper.find(".cmp-breadcrumb__item--active").text()).toEqual("Item3"); 49 | }); 50 | 51 | 52 | 53 | 54 | it('Renders routed breadcrumb items if provided', () => { 55 | const items:BreadCrumbV2ItemModel[] = [ 56 | {active:false,url:'/content/some/url.html',title:'Item1', routed: true}, 57 | {active:false,url:'/content/some/url.html',title:'Item2', routed: true}, 58 | {active:true,url:'/content/some/url.html',title:'Item3', routed: true} 59 | ]; 60 | 61 | 62 | const wrapper = mount(); 63 | expect(wrapper.find("li")).toHaveLength(3); 64 | 65 | expect(wrapper.find(".cmp-breadcrumb__item--active").text()).toEqual("Item3"); 66 | }); -------------------------------------------------------------------------------- /src/layout/breadcrumb/v2/BreadCrumbV2.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React, {Component} from 'react'; 18 | import {HasBaseCssClass, withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 19 | import {RoutedCoreComponentModel, RoutedModel} from "../../../routing/RoutedCoreComponent"; 20 | import {RoutedLink} from "../../../routing/RoutedLink"; 21 | import {BreadCrumbV2IsEmptyFn} from "./BreadCrumbV2IsEmptyFn"; 22 | 23 | export interface BreadCrumbV2ItemModel extends RoutedModel, HasBaseCssClass{ 24 | active: boolean 25 | url: string 26 | title: string 27 | index?: number 28 | } 29 | 30 | export interface BreadCrumbV2Model extends RoutedCoreComponentModel { 31 | items: BreadCrumbV2ItemModel[] 32 | ariaLabelI18n: string 33 | } 34 | 35 | export const BreadCrumbV2SSpan = (crumbItem:BreadCrumbV2ItemModel) => { 36 | return ( 37 | {crumbItem.title} 38 | ) 39 | }; 40 | 41 | export const BreadCrumbV2Link = (crumbItem:BreadCrumbV2ItemModel) => { 42 | return ( 43 | 48 | 49 | 50 | ) 51 | }; 52 | 53 | export const BreadCrumbV2ListItem = (crumbItem:BreadCrumbV2ItemModel) => { 54 | 55 | const className = `${crumbItem.baseCssClass}__item` + (crumbItem.active ? ` ${crumbItem.baseCssClass}__item--active` : ''); 56 | const contentIndex:string = (crumbItem.index) ? crumbItem.index.toString(2) : 'noindex'; 57 | 58 | return ( 59 |
  • 61 | { 62 | !crumbItem.active && 63 | } 64 | { 65 | crumbItem.active && 66 | } 67 | 68 |
  • 69 | ); 70 | }; 71 | 72 | const BreadCrumbV2Impl = (props:BreadCrumbV2Model) => { 73 | 74 | const {ariaLabelI18n = 'BreadCrumbV2'} = props; 75 | 76 | return ( 77 | 84 | ); 85 | 86 | }; 87 | 88 | 89 | const BreadCrumbV2 = (props:BreadCrumbV2Model) => { 90 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(BreadCrumbV2Impl, "cmp-breadcrumb"), BreadCrumbV2IsEmptyFn, "Breadcrumb V2"); 91 | return 92 | }; 93 | 94 | export default BreadCrumbV2; -------------------------------------------------------------------------------- /src/layout/breadcrumb/v2/BreadCrumbV2IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {BreadCrumbV2Model} from "./BreadCrumbV2"; 2 | 3 | export function BreadCrumbV2IsEmptyFn(props:BreadCrumbV2Model): boolean{ 4 | return props.items == null || props.items.length === 0; 5 | } -------------------------------------------------------------------------------- /src/layout/language-navigation/v1/LanguageNavigationV1.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | import {items} from "./LanguageNavigationV1TestMockItems"; 22 | import LanguageNavigationV1, {LanguageNavigationV1Model} from "./LanguageNavigationV1"; 23 | import {MemoryRouter} from 'react-router-dom'; 24 | 25 | 26 | it('Renders without crashing', () => { 27 | const div = document.createElement('div'); 28 | ReactDOM.render( 29 | , 30 | div 31 | ); 32 | ReactDOM.unmountComponentAtNode(div); 33 | expect(1).toBe(1); 34 | }); 35 | 36 | 37 | it('Renders a basic navigation properly', () => { 38 | 39 | const properties:LanguageNavigationV1Model = { 40 | hidePlaceHolder: false, 41 | isInEditor: false, 42 | items: items, 43 | routed: false 44 | }; 45 | const wrapper = mount(); 46 | const nav = wrapper.find('nav'); 47 | 48 | expect(nav).toHaveLength(1); 49 | }); 50 | 51 | 52 | // it('Renders a basic navigation properly even with routing', () => { 53 | // 54 | // const properties:LanguageNavigationV1Model = { 55 | // hidePlaceHolder: false, 56 | // isInEditor: false, 57 | // items: items, 58 | // routed: true 59 | // }; 60 | // const wrapper = mount(); 61 | // const nav = wrapper.find('nav'); 62 | // 63 | // expect(nav).toHaveLength(1); 64 | // }); 65 | -------------------------------------------------------------------------------- /src/layout/language-navigation/v1/LanguageNavigationV1.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import {withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 19 | import {determineIsActive, NavigationV1Group, NavigationV1Item, NavigationV1Model} from "../../navigation/v1/NavigationV1"; 20 | import {RoutedLink} from "../../../routing/RoutedLink"; 21 | import {LanguageNavigationV1IsEmptyFn} from "./LanguageNavigationV1IsEmptyFn"; 22 | 23 | export interface LanguageNavigationV1Item extends NavigationV1Item { 24 | level: number, 25 | active: boolean, 26 | title: string, 27 | url: string, 28 | lastModified: number, 29 | description?: string, 30 | path: string, 31 | locale: string, 32 | country: string, 33 | language: string, 34 | children?: LanguageNavigationV1Item[] 35 | } 36 | 37 | export interface LanguageNavigationV1Model extends NavigationV1Model{ 38 | items:LanguageNavigationV1Item[] 39 | accessibilityLabel?: string 40 | } 41 | 42 | export const LanguageNavigationV1Link = (props:LanguageNavigationV1Item) => { 43 | if(props.level > 0){ 44 | return ( 45 | {props.title} 53 | ) 54 | }else{ 55 | return ( 56 | {props.title} 57 | ) 58 | } 59 | }; 60 | 61 | export const LanguageNavigationV1Group = (item:LanguageNavigationV1Item) => { 62 | return ( 63 | <> 64 | {!!item.children && item.children.length > 0 && ( 65 |
      66 | {item.children.map( 67 | (child,index) => 68 | 73 | )} 74 |
    75 | )} 76 | 77 | ) 78 | }; 79 | 80 | export const LanguageNavigationV1Item = (item:LanguageNavigationV1Item) => { 81 | 82 | const isActive = determineIsActive(item); 83 | const cssClass = item.baseCssClass + '__item ' + 84 | item.baseCssClass + '__item--level-' + item.level + ' ' + 85 | `${item.baseCssClass}__item--countrycode-${item.country} ${item.baseCssClass}__item--langcode-${item.language}` + 86 | + (isActive ? ' ' + item.baseCssClass + '__item--active' : ''); 87 | return ( 88 |
  • 89 | 90 | { 91 | !!item.children && item.children.length > 0 && 92 | } 93 |
  • 94 | ) 95 | 96 | }; 97 | 98 | const LanguageNavigationV1Impl = (props:LanguageNavigationV1Model) => { 99 | const selfClone:LanguageNavigationV1Item = { 100 | active: false, 101 | lastModified: 0, 102 | level: 0, 103 | path: "", 104 | title: "", 105 | url: "", 106 | language: "", 107 | country: "", 108 | locale: "", 109 | routed: props.routed, 110 | children: props.items 111 | }; 112 | 113 | return ( 114 | 120 | ) 121 | }; 122 | 123 | const LanguageNavigation = (props:LanguageNavigationV1Model) => { 124 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(LanguageNavigationV1Impl, "cmp-languagenavigation"), LanguageNavigationV1IsEmptyFn, "LanguageNavigation V1") 125 | return 126 | }; 127 | 128 | export default LanguageNavigation; -------------------------------------------------------------------------------- /src/layout/language-navigation/v1/LanguageNavigationV1IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {LanguageNavigationV1Model} from "./LanguageNavigationV1"; 2 | 3 | export function LanguageNavigationV1IsEmptyFn(props:LanguageNavigationV1Model): boolean{ 4 | return props.items == null || props.items.length === 0; 5 | } 6 | -------------------------------------------------------------------------------- /src/layout/language-navigation/v1/LanguageNavigationV1TestMockItems.ts: -------------------------------------------------------------------------------- 1 | import {LanguageNavigationV1Item} from "./LanguageNavigationV1"; 2 | 3 | export const items:LanguageNavigationV1Item[] = [ 4 | { 5 | "children": [ 6 | { 7 | "children": [], 8 | "level": 1, 9 | "active": false, 10 | "title": "Deutsch", 11 | "locale": "de_DE", 12 | "country": "DE", 13 | "language": "de-DE", 14 | "url": "/content/core-components-examples/library/templating/language-navigation/language-structure/de/de/language-navigation.html", 15 | "lastModified": 1559808060300, 16 | "description": "", 17 | "path": "/content/core-components-examples/library/templating/language-navigation/language-structure/de/de/language-navigation" 18 | } 19 | ], 20 | "level": 0, 21 | "active": false, 22 | "title": "Deutschland", 23 | "locale": "de_DE", 24 | "country": "DE", 25 | "language": "de-DE", 26 | "url": "/content/core-components-examples/library/templating/language-navigation/language-structure/de/de/language-navigation.html", 27 | "lastModified": 1559808060300, 28 | "description": "", 29 | "path": "/content/core-components-examples/library/templating/language-navigation/language-structure/de/de/language-navigation" 30 | }, 31 | { 32 | "children": [ 33 | { 34 | "children": [], 35 | "level": 1, 36 | "active": false, 37 | "title": "Français", 38 | "locale": "fr_FR", 39 | "country": "FR", 40 | "language": "fr-FR", 41 | "url": "/content/core-components-examples/library/templating/language-navigation/language-structure/fr/fr/language-navigation.html", 42 | "lastModified": 1559808154698, 43 | "description": "", 44 | "path": "/content/core-components-examples/library/templating/language-navigation/language-structure/fr/fr/language-navigation" 45 | } 46 | ], 47 | "level": 0, 48 | "active": false, 49 | "title": "France", 50 | "locale": "fr_FR", 51 | "country": "FR", 52 | "language": "fr-FR", 53 | "url": "/content/core-components-examples/library/templating/language-navigation/language-structure/fr/fr/language-navigation.html", 54 | "lastModified": 1559808154698, 55 | "description": "", 56 | "path": "/content/core-components-examples/library/templating/language-navigation/language-structure/fr/fr/language-navigation" 57 | }, 58 | { 59 | "children": [ 60 | { 61 | "children": [], 62 | "level": 1, 63 | "active": true, 64 | "title": "English", 65 | "locale": "en_US", 66 | "country": "US", 67 | "language": "en-US", 68 | "url": "/content/core-components-examples/library/templating/language-navigation/language-structure/us/en/language-navigation.html", 69 | "lastModified": 1559807944878, 70 | "description": "Display a language switcher", 71 | "path": "/content/core-components-examples/library/templating/language-navigation/language-structure/us/en/language-navigation" 72 | }, 73 | { 74 | "children": [], 75 | "level": 1, 76 | "active": false, 77 | "title": "Español", 78 | "locale": "es_ES", 79 | "country": "ES", 80 | "language": "es-ES", 81 | "url": "/content/core-components-examples/library/templating/language-navigation/language-structure/us/es/language-navigation.html", 82 | "lastModified": 1559808252616, 83 | "description": "", 84 | "path": "/content/core-components-examples/library/templating/language-navigation/language-structure/us/es/language-navigation" 85 | } 86 | ], 87 | "level": 0, 88 | "active": true, 89 | "title": "United States", 90 | "locale": "en_US", 91 | "country": "US", 92 | "language": "en-US", 93 | "url": "/content/core-components-examples/library/templating/language-navigation/language-structure/us/en/language-navigation.html", 94 | "lastModified": 1559807944878, 95 | "description": "Display a language switcher", 96 | "path": "/content/core-components-examples/library/templating/language-navigation/language-structure/us/en/language-navigation" 97 | } 98 | ]; -------------------------------------------------------------------------------- /src/layout/navigation/v1/NavigationV1.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import {mount} from 'enzyme'; 20 | 21 | import {items} from "./NavigationV1TestMockItems"; 22 | import NavigationV1, {NavigationV1Model} from "./NavigationV1"; 23 | import {MemoryRouter} from 'react-router-dom'; 24 | 25 | 26 | it('Renders without crashing', () => { 27 | const div = document.createElement('div'); 28 | ReactDOM.render( 29 | , 30 | div 31 | ); 32 | ReactDOM.unmountComponentAtNode(div); 33 | expect(1).toBe(1); 34 | }); 35 | 36 | 37 | it('Renders a basic navigation properly', () => { 38 | 39 | const properties:NavigationV1Model = { 40 | hidePlaceHolder: false, 41 | isInEditor: false, 42 | routed: false, 43 | items: items 44 | }; 45 | const wrapper = mount(); 46 | const nav = wrapper.find('nav'); 47 | 48 | expect(nav).toHaveLength(1); 49 | }); 50 | 51 | 52 | it('Renders a basic navigation properly even with routing enabled', () => { 53 | 54 | const properties:NavigationV1Model = { 55 | hidePlaceHolder: false, 56 | isInEditor: false, 57 | items: items, 58 | routed: true 59 | }; 60 | const wrapper = mount(); 61 | const nav = wrapper.find('nav'); 62 | 63 | expect(nav).toHaveLength(1); 64 | }); 65 | -------------------------------------------------------------------------------- /src/layout/navigation/v1/NavigationV1.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import {HasBaseCssClass, withConditionalPlaceHolder, withStandardBaseCssClass} from "../../../AbstractCoreComponent"; 19 | import {RoutedCoreComponentModel, RoutedModel} from "../../../routing/RoutedCoreComponent"; 20 | import {RoutedLink} from "../../../routing/RoutedLink"; 21 | import {NavigationV1IsEmptyFn} from "./NavigationV1IsEmptyFn"; 22 | import {LanguageNavigationV1Item} from "../../language-navigation/v1/LanguageNavigationV1"; 23 | 24 | export interface NavigationV1Item extends RoutedModel,HasBaseCssClass{ 25 | level: number, 26 | index?: number, 27 | active: boolean, 28 | title: string, 29 | url: string, 30 | lastModified: number, 31 | description?: string, 32 | path: string, 33 | children?: NavigationV1Item[] 34 | } 35 | 36 | export interface NavigationV1Model extends RoutedCoreComponentModel{ 37 | items:NavigationV1Item[] 38 | accessibilityLabel?: string 39 | } 40 | 41 | export const determineIsActive = (item:NavigationV1Item) => { 42 | return item.active; 43 | }; 44 | 45 | export const NavigationV1Group = (item:NavigationV1Item) => { 46 | return ( 47 | <> 48 | {!!item.children && item.children.length > 0 && ( 49 |
      50 | {item.children.map( 51 | (child,index) => 52 | 57 | )} 58 |
    59 | )} 60 | 61 | ) 62 | }; 63 | 64 | export const NavigationV1Item = (item:NavigationV1Item) => { 65 | 66 | const isActive = determineIsActive(item); 67 | const cssClass = item.baseCssClass + '__item ' + 68 | item.baseCssClass + '__item--level-' + item.level + ' ' 69 | + (isActive ? ' ' + item.baseCssClass + '__item--active' : ''); 70 | return ( 71 |
  • 72 | {item.title 74 | } 75 | { 76 | !!item.children && item.children.length > 0 && 77 | } 78 |
  • 79 | ) 80 | 81 | }; 82 | 83 | export const NavigationV1Impl = (props:NavigationV1Model) => { 84 | 85 | const selfClone:NavigationV1Item = { 86 | active: false, 87 | lastModified: 0, 88 | level: 0, 89 | path: "", 90 | title: "", 91 | url: "", 92 | routed: props.routed, 93 | children: props.items 94 | }; 95 | 96 | return ( 97 | 103 | ) 104 | 105 | } 106 | 107 | 108 | 109 | export const NavigationV1 = (props:NavigationV1Model) => { 110 | const Wrapped = withConditionalPlaceHolder(withStandardBaseCssClass(NavigationV1Impl, "cmp-navigation"), NavigationV1IsEmptyFn, "Navigation V1") 111 | return 112 | }; 113 | 114 | export default NavigationV1; -------------------------------------------------------------------------------- /src/layout/navigation/v1/NavigationV1IsEmptyFn.ts: -------------------------------------------------------------------------------- 1 | import {NavigationV1Model} from "./NavigationV1"; 2 | 3 | export function NavigationV1IsEmptyFn(props:NavigationV1Model): boolean{ 4 | return props.items == null || props.items.length === 0; 5 | } -------------------------------------------------------------------------------- /src/layout/navigation/v1/NavigationV1TestMockItems.ts: -------------------------------------------------------------------------------- 1 | import {NavigationV1Item} from "./NavigationV1"; 2 | 3 | export const items:NavigationV1Item[] = [ 4 | { 5 | "children": [ 6 | { 7 | "children": [ 8 | { 9 | "children": [], 10 | "level": 2, 11 | "active": true, 12 | "url": "/content/core-components-examples/library/templating/navigation.html", 13 | "lastModified": 1558694278229, 14 | "description": "Display a site navigation menu", 15 | "title": "Navigation", 16 | "path": "/content/core-components-examples/library/templating/navigation" 17 | }, 18 | { 19 | "children": [], 20 | "level": 2, 21 | "active": false, 22 | "url": "/content/core-components-examples/library/templating/language-navigation/language-structure/us/en/language-navigation.html", 23 | "lastModified": 1559807944878, 24 | "description": "Display a language switcher", 25 | "title": "Language Navigation", 26 | "path": "/content/core-components-examples/library/templating/language-navigation/language-structure/us/en/language-navigation" 27 | }, 28 | { 29 | "children": [], 30 | "level": 2, 31 | "active": false, 32 | "url": "/content/core-components-examples/library/templating/breadcrumb/hidden/level-1/level-2/breadcrumb.html", 33 | "lastModified": 1558607623701, 34 | "description": "Display a breadcrumb navigation", 35 | "title": "Breadcrumb", 36 | "path": "/content/core-components-examples/library/templating/breadcrumb/hidden/level-1/level-2/breadcrumb" 37 | } 38 | ], 39 | "level": 1, 40 | "active": true, 41 | "url": "/content/core-components-examples/library/templating.html", 42 | "lastModified": 1544121656470, 43 | "description": "", 44 | "title": "Templating", 45 | "path": "/content/core-components-examples/library/templating" 46 | }, 47 | { 48 | "children": [ 49 | { 50 | "children": [], 51 | "level": 2, 52 | "active": false, 53 | "url": "/content/core-components-examples/library/page-authoring/title.html", 54 | "lastModified": 1547642198741, 55 | "description": "Display a page heading", 56 | "title": "Title", 57 | "path": "/content/core-components-examples/library/page-authoring/title" 58 | }, 59 | { 60 | "children": [], 61 | "level": 2, 62 | "active": false, 63 | "url": "/content/core-components-examples/library/page-authoring/text.html", 64 | "lastModified": 1548159422163, 65 | "description": "Display a rich text paragraph", 66 | "title": "Text", 67 | "path": "/content/core-components-examples/library/page-authoring/text" 68 | }, 69 | { 70 | "children": [], 71 | "level": 2, 72 | "active": false, 73 | "url": "/content/core-components-examples/library/page-authoring/image.html", 74 | "lastModified": 1550255022224, 75 | "description": "Display an image asset", 76 | "title": "Image", 77 | "path": "/content/core-components-examples/library/page-authoring/image" 78 | }, 79 | { 80 | "children": [], 81 | "level": 2, 82 | "active": false, 83 | "url": "/content/core-components-examples/library/page-authoring/button.html", 84 | "lastModified": 1547062227177, 85 | "description": "Display a button or anchor button", 86 | "title": "Button", 87 | "path": "/content/core-components-examples/library/page-authoring/button" 88 | }, 89 | { 90 | "children": [], 91 | "level": 2, 92 | "active": false, 93 | "url": "/content/core-components-examples/library/page-authoring/teaser.html", 94 | "lastModified": 1575799718587, 95 | "description": "Link an image and text", 96 | "title": "Teaser", 97 | "path": "/content/core-components-examples/library/page-authoring/teaser" 98 | }, 99 | { 100 | "children": [], 101 | "level": 2, 102 | "active": false, 103 | "url": "/content/core-components-examples/library/page-authoring/download.html", 104 | "lastModified": 1558992253683, 105 | "description": "Display an asset for download", 106 | "title": "Download", 107 | "path": "/content/core-components-examples/library/page-authoring/download" 108 | }, 109 | { 110 | "children": [], 111 | "level": 2, 112 | "active": false, 113 | "url": "/content/core-components-examples/library/page-authoring/list.html", 114 | "lastModified": 1547642282466, 115 | "description": "Display a list of pages", 116 | "title": "List", 117 | "path": "/content/core-components-examples/library/page-authoring/list" 118 | }, 119 | { 120 | "children": [], 121 | "level": 2, 122 | "active": false, 123 | "url": "/content/core-components-examples/library/page-authoring/experience-fragment.html", 124 | "lastModified": 1566294323252, 125 | "description": "Display an experience fragment", 126 | "title": "Experience Fragment", 127 | "path": "/content/core-components-examples/library/page-authoring/experience-fragment" 128 | }, 129 | { 130 | "children": [], 131 | "level": 2, 132 | "active": false, 133 | "url": "/content/core-components-examples/library/page-authoring/content-fragment.html", 134 | "lastModified": 1547644839952, 135 | "description": "Display a content fragment asset", 136 | "title": "Content Fragment", 137 | "path": "/content/core-components-examples/library/page-authoring/content-fragment" 138 | }, 139 | { 140 | "children": [], 141 | "level": 2, 142 | "active": false, 143 | "url": "/content/core-components-examples/library/page-authoring/content-fragment-list.html", 144 | "lastModified": 1554130037469, 145 | "description": "Display a list of content fragments", 146 | "title": "Content Fragment List", 147 | "path": "/content/core-components-examples/library/page-authoring/content-fragment-list" 148 | }, 149 | { 150 | "children": [], 151 | "level": 2, 152 | "active": false, 153 | "url": "/content/core-components-examples/library/page-authoring/embed.html", 154 | "lastModified": 1567092519658, 155 | "description": "Embed a third-party widget", 156 | "title": "Embed", 157 | "path": "/content/core-components-examples/library/page-authoring/embed" 158 | }, 159 | { 160 | "children": [], 161 | "level": 2, 162 | "active": false, 163 | "url": "/content/core-components-examples/library/page-authoring/social-sharing.html", 164 | "lastModified": 1547062206375, 165 | "description": "Add social sharing links", 166 | "title": "Social Sharing", 167 | "path": "/content/core-components-examples/library/page-authoring/social-sharing" 168 | }, 169 | { 170 | "children": [], 171 | "level": 2, 172 | "active": false, 173 | "url": "/content/core-components-examples/library/page-authoring/separator.html", 174 | "lastModified": 1547062195738, 175 | "description": "Display a section divider", 176 | "title": "Separator", 177 | "path": "/content/core-components-examples/library/page-authoring/separator" 178 | } 179 | ], 180 | "level": 1, 181 | "active": false, 182 | "url": "/content/core-components-examples/library/page-authoring.html", 183 | "lastModified": 1544121656470, 184 | "description": "", 185 | "title": "Page Authoring", 186 | "path": "/content/core-components-examples/library/page-authoring" 187 | }, 188 | { 189 | "children": [ 190 | { 191 | "children": [], 192 | "level": 2, 193 | "active": false, 194 | "url": "/content/core-components-examples/library/container/container.html", 195 | "lastModified": 1560275793712, 196 | "description": "Group and layout components", 197 | "title": "Container", 198 | "path": "/content/core-components-examples/library/container/container" 199 | }, 200 | { 201 | "children": [], 202 | "level": 2, 203 | "active": false, 204 | "url": "/content/core-components-examples/library/container/carousel.html", 205 | "lastModified": 1550252345916, 206 | "description": "Cycle through content panels", 207 | "title": "Carousel", 208 | "path": "/content/core-components-examples/library/container/carousel" 209 | }, 210 | { 211 | "children": [], 212 | "level": 2, 213 | "active": false, 214 | "url": "/content/core-components-examples/library/container/tabs.html", 215 | "lastModified": 1548157896096, 216 | "description": "Switchable content panels", 217 | "title": "Tabs", 218 | "path": "/content/core-components-examples/library/container/tabs" 219 | }, 220 | { 221 | "children": [], 222 | "level": 2, 223 | "active": false, 224 | "url": "/content/core-components-examples/library/container/accordion.html", 225 | "lastModified": 1548157896096, 226 | "description": "Toggle panels of related content", 227 | "title": "Accordion", 228 | "path": "/content/core-components-examples/library/container/accordion" 229 | } 230 | ], 231 | "level": 1, 232 | "active": false, 233 | "url": "/content/core-components-examples/library/container.html", 234 | "lastModified": 1544121656470, 235 | "description": "", 236 | "title": "Container", 237 | "path": "/content/core-components-examples/library/container" 238 | }, 239 | { 240 | "children": [ 241 | { 242 | "children": [], 243 | "level": 2, 244 | "active": false, 245 | "url": "/content/core-components-examples/library/form/container.html", 246 | "lastModified": 1576771730790, 247 | "description": "Group and Layout Form Components", 248 | "title": "Container", 249 | "path": "/content/core-components-examples/library/form/container" 250 | }, 251 | { 252 | "children": [], 253 | "level": 2, 254 | "active": false, 255 | "url": "/content/core-components-examples/library/form/input.html", 256 | "lastModified": 1576767461500, 257 | "description": "Display an input field", 258 | "title": "Input", 259 | "path": "/content/core-components-examples/library/form/input" 260 | }, 261 | { 262 | "children": [], 263 | "level": 2, 264 | "active": false, 265 | "url": "/content/core-components-examples/library/form/options.html", 266 | "lastModified": 1579192036788, 267 | "description": "Display different options to choose from", 268 | "title": "Options", 269 | "path": "/content/core-components-examples/library/form/options" 270 | }, 271 | { 272 | "children": [], 273 | "level": 2, 274 | "active": false, 275 | "url": "/content/core-components-examples/library/form/hidden.html", 276 | "lastModified": 1576771327704, 277 | "description": "Hidden Form Component ", 278 | "title": "Hidden", 279 | "path": "/content/core-components-examples/library/form/hidden" 280 | }, 281 | { 282 | "children": [], 283 | "level": 2, 284 | "active": false, 285 | "url": "/content/core-components-examples/library/form/button.html", 286 | "lastModified": 1576770945395, 287 | "description": "Display a form button", 288 | "title": "Button", 289 | "path": "/content/core-components-examples/library/form/button" 290 | } 291 | ], 292 | "level": 1, 293 | "active": false, 294 | "url": "/content/core-components-examples/library/form.html", 295 | "lastModified": 1544121656470, 296 | "description": "null", 297 | "title": "Form", 298 | "path": "/content/core-components-examples/library/form" 299 | } 300 | ], 301 | "level": 0, 302 | "active": true, 303 | "url": "/content/core-components-examples/library.html", 304 | "lastModified": 1559143556781, 305 | "description": "AEM Core Components - Component Library", 306 | "title": "Component Library", 307 | "path": "/content/core-components-examples/library" 308 | } 309 | ]; 310 | 311 | -------------------------------------------------------------------------------- /src/routing/RoutedCoreComponent.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from "react"; 18 | import {CoreComponentModel} from "../AbstractCoreComponent"; 19 | 20 | export interface RoutedModel { 21 | routed?: boolean 22 | } 23 | 24 | export interface RoutedCoreComponentModel extends CoreComponentModel, RoutedModel{ 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/routing/RoutedLink.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {createMemoryHistory, MemoryHistory} from "history"; 18 | import {fireEvent, render} from "@testing-library/react"; 19 | import {Route, Router, Switch} from "react-router-dom"; 20 | import React from "react"; 21 | import {RoutedLink} from "./RoutedLink"; 22 | import {RoutedModel} from "./RoutedCoreComponent"; 23 | 24 | interface DummyModel extends RoutedModel{ 25 | url?: string 26 | } 27 | 28 | const RoutedDummyComponent = (props:DummyModel) => { 29 | return ( 30 |
    31 | 32 |
    33 | 34 | ) 35 | }; 36 | 37 | const createRoutedDummyComponent = (url:string, routed = true) => { 38 | const history:MemoryHistory = createMemoryHistory({ 39 | initialEntries: ['/page1'], 40 | initialIndex: 0 41 | }); 42 | return { 43 | ...render( 44 | 45 | 46 | 47 |

    Hello World

    48 |
    49 | 50 | 51 | 52 |
    53 |
    54 | ) 55 | } 56 | }; 57 | 58 | const createUnspecifiedDummyComponent = (url:string) => { 59 | const history:MemoryHistory = createMemoryHistory({ 60 | initialEntries: ['/page1'], 61 | initialIndex: 0 62 | }); 63 | return { 64 | ...render( 65 | 66 | 67 | 68 |

    Hello World

    69 |
    70 | 71 | 72 | 73 |
    74 |
    75 | ) 76 | } 77 | }; 78 | 79 | let oldConsoleError:()=>void; 80 | 81 | beforeAll(() => { 82 | oldConsoleError = console.error; 83 | console.error = jest.fn(); 84 | }); 85 | 86 | afterAll(() => { 87 | console.error = oldConsoleError; 88 | }); 89 | 90 | it('Renders and routes properly', () => { 91 | 92 | 93 | const { container } = createRoutedDummyComponent("/page2"); 94 | 95 | const image:HTMLElement|null = container.querySelector('.routedDummyComponent'); 96 | 97 | expect(image).toBeDefined(); 98 | 99 | const anchor = container.querySelector('a.dummyLink'); 100 | 101 | expect(anchor).toBeDefined(); 102 | 103 | if(anchor != null){ 104 | fireEvent.click(anchor); 105 | } 106 | 107 | const h1 = container.querySelector("h1.dummy"); 108 | expect(h1).toBeDefined(); 109 | 110 | }); 111 | 112 | it('Does route if the link is relative and isRouted is not specified', () => { 113 | 114 | const { container } = createUnspecifiedDummyComponent("/page3"); 115 | 116 | const image:HTMLElement|null = container.querySelector('.routedDummyComponent'); 117 | 118 | expect(image).toBeDefined(); 119 | 120 | const anchor = container.querySelector('a.dummyLink'); 121 | 122 | expect(anchor).toBeDefined(); 123 | 124 | if(anchor != null){ 125 | fireEvent.click(anchor); 126 | } 127 | 128 | const h1 = container.querySelector("h1.dummy"); 129 | expect(h1).toBeDefined(); 130 | 131 | }); 132 | 133 | it('Does NOT route if the link is empty', () => { 134 | 135 | 136 | const { container } = createRoutedDummyComponent("", false); 137 | 138 | const image:HTMLElement|null = container.querySelector('.routedDummyComponent'); 139 | 140 | expect(image).toBeDefined(); 141 | 142 | const anchor = container.querySelector('a.dummyLink'); 143 | 144 | expect(anchor).toBeDefined(); 145 | 146 | let found = false; 147 | 148 | if(anchor != null){ 149 | const href:Attr | null = anchor.attributes.getNamedItem("href"); 150 | 151 | if(href != null){ 152 | expect(href.value).toEqual("#"); 153 | found = true; 154 | } 155 | } 156 | 157 | expect(found).toEqual(true); 158 | 159 | if(anchor != null){ 160 | fireEvent.click(anchor); 161 | } 162 | 163 | const h1 = container.querySelector("h1.dummy"); 164 | expect(h1).toBeNull(); 165 | 166 | }); 167 | 168 | 169 | it('Does NOT route if we tell it to', () => { 170 | 171 | 172 | const { container } = createRoutedDummyComponent("/page2", false); 173 | 174 | const image:HTMLElement|null = container.querySelector('.routedDummyComponent'); 175 | 176 | expect(image).toBeDefined(); 177 | 178 | const anchor = container.querySelector('a.dummyLink'); 179 | 180 | expect(anchor).toBeDefined(); 181 | 182 | if(anchor != null){ 183 | fireEvent.click(anchor); 184 | } 185 | 186 | const h1 = container.querySelector("h1.dummy"); 187 | expect(h1).toBeNull(); 188 | 189 | }); 190 | 191 | 192 | it('Will never route external URLs', () => { 193 | 194 | 195 | const { container } = createRoutedDummyComponent("https://adobe.com", true); 196 | 197 | const image:HTMLElement|null = container.querySelector('.routedDummyComponent'); 198 | 199 | expect(image).toBeDefined(); 200 | 201 | const anchor = container.querySelector('a.dummyLink'); 202 | 203 | expect(anchor).toBeDefined(); 204 | 205 | if(anchor != null){ 206 | fireEvent.click(anchor); 207 | } 208 | 209 | const h1 = container.querySelector("h1.dummy"); 210 | expect(h1).toBeNull(); 211 | 212 | }); 213 | 214 | -------------------------------------------------------------------------------- /src/routing/RoutedLink.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import {Link as RouterLink} from 'react-router-dom'; 19 | 20 | export interface LinkProps { 21 | to?: string; 22 | isRouted?: boolean; 23 | [prop: string]: any 24 | } 25 | export const RoutedLink = (props:LinkProps) => { 26 | const {to, isRouted, ...otherProps} = props; 27 | 28 | const isRoutedChecked = typeof props.isRouted === 'boolean' ? props.isRouted : true; 29 | 30 | if(to === undefined || to.trim().length === 0){ 31 | return ; 34 | } 35 | const isExternal = /^https?:\/\//.test(to); 36 | 37 | return isExternal || !isRoutedChecked? 38 | () 42 | : 43 | ( ) 46 | }; -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {configure} from 'enzyme'; 18 | import Adapter from 'enzyme-adapter-react-16'; 19 | 20 | configure({ adapter: new Adapter()}); 21 | 22 | -------------------------------------------------------------------------------- /src/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@adobe/aem-react-editable-components": ["./"], 8 | "@adobe/aem-react-editable-components/*": ["./*"] 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "module": "es2015", 6 | "target": "esnext", 7 | "importHelpers": true, 8 | "removeComments": false, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "declarationDir": "../dist", 12 | "emitDeclarationOnly": true 13 | }, 14 | 15 | "exclude": [ 16 | "./internal/umd.ts" 17 | ] 18 | } -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | export * from "./index"; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "inlineSourceMap": true, 10 | "inlineSources": true, 11 | "experimentalDecorators": true, 12 | "allowJs": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true, 15 | "allowSyntheticDefaultImports": true, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "jsx": "react" 23 | }, 24 | "include": [ 25 | "src" 26 | ] 27 | } 28 | --------------------------------------------------------------------------------