diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..1b1569ade --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +.idea/ +*.css +*.opts +*.scss +*.snap +*.svg +node_modules/** \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..3b91d121e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,146 @@ +env: + browser: true + node: true + es6: true + jest: true + +globals: + $: true + +parser: 'babel-eslint' + +#extends: 'react-app' + +plugins: + - ie11 + - jsx-a11y + - loosely-restrict-imports + - react + +rules: + #brace-style: 2 + camelcase: 2 + comma-dangle: [2, 'never'] + comma-spacing: 2 + comma-style: [2, 'last'] + #compat/compat: 2 + default-case: 2 + dot-notation: 2 + eol-last: 2 + #eqeqeq: 2 + guard-for-in: 2 + handle-callback-err: 2 + ie11/no-collection-args: 2 + ie11/no-for-in-const: 2 + ie11/no-weak-collections: 2 + #indent: [2, 4, {"SwitchCase": 1}] + jsx-a11y/accessible-emoji: 2 + jsx-a11y/alt-text: 2 + #jsx-a11y/anchor-has-content: 2 + #jsx-a11y/anchor-is-valid: 2 + jsx-a11y/aria-activedescendant-has-tabindex: 2 + jsx-a11y/aria-props: 2 + jsx-a11y/aria-proptypes: 2 + #jsx-a11y/aria-role: 2 + jsx-a11y/aria-unsupported-elements: 2 + #jsx-a11y/click-events-have-key-events: 2 + jsx-a11y/heading-has-content: 2 + jsx-a11y/html-has-lang: 2 + jsx-a11y/iframe-has-title: 2 + jsx-a11y/img-redundant-alt: 2 + #jsx-a11y/interactive-supports-focus: 2 + jsx-a11y/label-has-for: [2, {'allowChildren': true, 'required': {'every': [ 'id' ]}}] + jsx-a11y/media-has-caption: 2 + jsx-a11y/mouse-events-have-key-events: 2 + jsx-a11y/no-access-key: 2 + jsx-a11y/no-autofocus: 2 + jsx-a11y/no-distracting-elements: 2 + jsx-a11y/no-interactive-element-to-noninteractive-role: 2 + #jsx-a11y/no-noninteractive-element-interactions: 2 + #jsx-a11y/no-noninteractive-element-to-interactive-role: [2, { ul: ['tablist', 'menu']}] + jsx-a11y/no-noninteractive-tabindex: 2 + #jsx-a11y/no-onchange: 2 + jsx-a11y/no-redundant-roles: 2 + #jsx-a11y/no-static-element-interactions: 2 + jsx-a11y/role-has-required-aria-props: 2 + #jsx-a11y/role-supports-aria-props: 2 + jsx-a11y/scope: 2 + jsx-a11y/tabindex-no-positive: 2 + jsx-quotes: [ 2, prefer-single ] + key-spacing: 2 + keyword-spacing: [2, { 'before': true }] + loosely-restrict-imports/loosely-restrict-imports: [2, '.jsx'] + new-cap: 2 + no-cond-assign: 2 + #no-console: 2 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty-character-class: 2 + #no-empty: 2 + no-ex-assign: 2 + no-extra-bind: 2 + no-extra-boolean-cast: 0 + no-extra-parens: 0 + no-extra-semi: 2 + no-fallthrough: 2 + no-floating-decimal: 2 + no-func-assign: 2 + no-inner-declarations: 2 + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-loop-func: 2 + no-mixed-spaces-and-tabs: 2 + no-multi-spaces: 2 + no-negated-in-lhs: 2 + #no-nested-ternary: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + #no-shadow: 2 + no-spaced-func: 2 + #no-sparse-arrays: 2 + no-trailing-spaces: 2 + no-undef: 2 + #no-undefined: 2 + no-underscore-dangle: 0 + no-unreachable: 2 + #no-unused-vars: [2, { ignoreRestSiblings: true }] + quote-props: [2, 'as-needed', { 'keywords': true, 'unnecessary': false }] + quotes: [2, 'single'] + #radix: 2 + react/display-name: 2 + react/jsx-boolean-value: 2 + react/jsx-closing-bracket-location: [2, 'after-props'] + react/jsx-equals-spacing: [2, 'never'] + react/jsx-indent: 2 + react/jsx-indent-props: 2 + react/jsx-max-props-per-line: [2, { "maximum": 2 }] + react/jsx-no-undef: 2 + react/jsx-tag-spacing: [2, { "beforeSelfClosing": "always" }] + #react/jsx-sort-props: 2 + react/jsx-uses-react: 2 + react/jsx-uses-vars: 2 + react/jsx-wrap-multilines: 2 + react/no-did-mount-set-state: 2 + react/no-did-update-set-state: 2 + #react/no-multi-comp: 2 + react/no-unknown-property: 2 + #react/prop-types: 2 + #react/react-in-jsx-scope: 2 + react/self-closing-comp: 2 + react/sort-prop-types: [2, { 'callbacksLast' : true, 'ignoreCase' : true, 'requiredFirst' : true, } ] + semi-spacing: 2 + semi: 2 + #sort-imports: [2, { 'ignoreCase': true, 'ignoreMemberSort': false, 'memberSyntaxSortOrder': ['none', 'single', 'multiple', 'all'] }] + space-before-blocks: 2 + space-before-function-paren: [2, 'never'] + space-infix-ops: 2 + spaced-comment: [0, 'always', { exceptions: ['-']}] + strict: [2, 'global'] + #use-isnan: 2 + #valid-jsdoc: [2, { prefer: { 'return': 'returns'}}] + #valid-typeof: 2 + wrap-iife: [2, 'any'] diff --git a/.npmignore b/.npmignore new file mode 100644 index 000000000..1c6340a8e --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +/node-modules +/scripts +/src +/config +/ci-scripts \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8d19c3d69..9ad645049 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,14 @@ branches: only: - develop - "/^feature\\/.*$/" + - "/^fix\\/.*$/" jobs: include: - - stage: "Fundamental-react build" + - stage: "Fundamental-react: Lint" + script: npm run lint + - stage: "Fundamental-react: Test" name: "Unit tests" - script: "./ci-scripts/unit-tests.sh" + script: npm run test:coverage notifications: email: on_failure: always diff --git a/README.md b/README.md index ac341ccd2..92748ad8d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,21 @@ -# Fundamental-react -React.JS components for [SAP Fiori Fundamentals](https://github.com/SAP/fundamental) -- [Playground](https://sap.github.io/fundamental-react/) -- [npm package fundamental-react](https://www.npmjs.com/package/fundamental-react) -- [![Build Status](https://travis-ci.org/SAP/fundamental-react.svg?branch=develop)](https://travis-ci.org/SAP/fundamental-react) -- [GitHub repo of Angular implementation of SAP Fiori Fundamentals](https://github.com/SAP/fundamental-ngx) -- [GitHub repo of Vue implementation of SAP Fiori Fundamentals](https://github.com/SAP/fundamental-vue) +Fundamental-react is a set of [React.JS](https://reactjs.org/) components implementation of [SAP Fiori Fundamentals library](https://sap.github.io/fundamental/). + +**[Component Documentation](https://sap.github.io/fundamental-react/)** + +## Current Version + +``` +0.0.8-beta +``` + +## Build Status +[![Build Status](https://travis-ci.org/SAP/fundamental-react.svg?branch=develop)](https://travis-ci.org/SAP/fundamental-react) ## Description -Fundamental-react is a set of [React](https://reactjs.org/) components implementation of [SAP Fiori Fundamentals library](https://sap.github.io/fundamental/). SAP Fiori Fundamentals library is a Design System and HTML/CSS Component Library used to build modern Product User Experiences with the SAP look and feel. This will allow you to stay/use React for your application and get SAP look and feel. -Fundamental-react is an open source library and it is open for contribution as long as you follow certain [rules/guidelines](./CONTRIBUTING.md). +Fundamental-react is a set of [React.JS](https://reactjs.org/) components implementation of [SAP Fiori Fundamentals library](https://sap.github.io/fundamental/). SAP Fiori Fundamentals library is a Design System and HTML/CSS Component Library used to build modern Product User Experiences with the SAP look and feel. This will allow you to stay/use React for your application and get SAP look and feel. + ## Requirements @@ -19,9 +24,43 @@ https://www.npmjs.com/get-npm Some prior knowledge of React is required for using this library. -## Available Scripts (Associated with Create React App) +# Getting started + +## Install + +To download and use this library, you first need to install the node package manager - [npm](https://www.npmjs.com/get-npm). + +1. Install Fiori Fundamentals: + +`npm install --save fiori-fundamentals` + +2. Install Fundamental-react: + +`npm install --save fundamental-react` + +[npm package for fundamental-react](https://www.npmjs.com/package/fundamental-react) + +3. Include the Fiori Fundamentals CSS in your React application. In your App.css or App.scss file include the following lines: + +``` +$fd-icons-path: "~fiori-fundamentals/scss/icons/"; +$fd-fonts-path: "~fiori-fundamentals/scss/fonts/"; +@import '../node_modules/fiori-fundamentals/scss/all.scss'; +``` + +You can now use the [Component Documentation](https://sap.github.io/fundamental-react/) to browse the components currently available with Fundamental Vue. To use a Fundamental-react component, paste the desired code snippet from the Component Documentation and configure it as necessarry: + + + ... + + + + ... + + +## Available Scripts -#### `npm start` +`npm start` Runs the app in the development mode.
Open [http://localhost:3000](http://localhost:3000) to view it in the browser. @@ -29,43 +68,29 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will automatically reload on changes.
Lint errors are shown in the console. -#### `npm test` +`npm test` Launches the test runner in the interactive watch mode. -#### `npm test -- --coverage` +`npm test -- --coverage` Launches the test runner and display code coverage report. -#### `npm test -- --coverage --watch` +`npm test -- --coverage --watch` Launches the test runner and display code coverage report and interactive watch mode. -#### `npm run build` +`npm run build` Builds the app for production to the `build` folder. -## Download and Installation - -#### 1. Download Fiori Fundamentals: - -`npm install --save fiori-fundamentals` - -#### 2. Include the Fiori Fundamentals CSS in your React application. In your App.css or App.scss file include the following lines: - -``` -$fd-icons-path: "~fiori-fundamentals/scss/icons/"; -$fd-fonts-path: "~fiori-fundamentals/scss/fonts/"; -@import '../node_modules/fiori-fundamentals/scss/all.scss'; -``` - ## Known Issues -Click [here](https://github.com/SAP/fundamental-react/issues) to view the current issues. +There are no known major issues. ## How to obtain support -If you encounter an issue, you can [create a ticket](https://github.com/SAP/fundamental-react/issues) +If you encounter an issue, you can [create a ticket](https://github.com/SAP/fundamental-react/issues/new) ## Contributing @@ -75,3 +100,8 @@ If you want to contribute, please check the [CONTRIBUTING.md](./CONTRIBUTING.md) Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](https://github.com/SAP/fundamental-react/blob/master/LICENSE.txt) + +## Similar Projects +- [GitHub repo of Angular implementation of SAP Fiori Fundamentals](https://github.com/SAP/fundamental-ngx) +- [GitHub repo of Vue implementation of SAP Fiori Fundamentals](https://github.com/SAP/fundamental-vue) + diff --git a/config/env.js b/config/env.js new file mode 100644 index 000000000..b0344c5a8 --- /dev/null +++ b/config/env.js @@ -0,0 +1,93 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); + +// Make sure that including paths.js after env.js will read .env variables. +delete require.cache[require.resolve('./paths')]; + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + throw new Error( + 'The NODE_ENV environment variable is required but was not specified.' + ); +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +var dotenvFiles = [ + `${paths.dotenv}.${NODE_ENV}.local`, + `${paths.dotenv}.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `${paths.dotenv}.local`, + paths.dotenv, +].filter(Boolean); + +// Load environment variables from .env* files. Suppress warnings using silent +// if this file is missing. dotenv will never modify any environment variables +// that have already been set. Variable expansion is supported in .env files. +// https://github.com/motdotla/dotenv +// https://github.com/motdotla/dotenv-expand +dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + require('dotenv-expand')( + require('dotenv').config({ + path: dotenvFile, + }) + ); + } +}); + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebook/create-react-app/issues/253. +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. +// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 +// We also resolve them to make sure all tools using them work consistently. +const appDirectory = fs.realpathSync(process.cwd()); +process.env.NODE_PATH = (process.env.NODE_PATH || '') + .split(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter); + +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in Webpack configuration. +const REACT_APP = /^REACT_APP_/i; + +function getClientEnvironment(publicUrl) { + const raw = Object.keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce( + (env, key) => { + env[key] = process.env[key]; + return env; + }, + { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + NODE_ENV: process.env.NODE_ENV || 'development', + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + PUBLIC_URL: publicUrl, + } + ); + // Stringify all values so we can feed into Webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, {}), + }; + + return { raw, stringified }; +} + +module.exports = getClientEnvironment; diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js new file mode 100644 index 000000000..8f6511481 --- /dev/null +++ b/config/jest/cssTransform.js @@ -0,0 +1,14 @@ +'use strict'; + +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process() { + return 'module.exports = {};'; + }, + getCacheKey() { + // The output is always the same. + return 'cssTransform'; + }, +}; diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js new file mode 100644 index 000000000..07010e33a --- /dev/null +++ b/config/jest/fileTransform.js @@ -0,0 +1,30 @@ +'use strict'; + +const path = require('path'); + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process(src, filename) { + const assetFilename = JSON.stringify(path.basename(filename)); + + if (filename.match(/\.svg$/)) { + return `module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: (props) => ({ + $$typeof: Symbol.for('react.element'), + type: 'svg', + ref: null, + key: null, + props: Object.assign({}, props, { + children: ${assetFilename} + }) + }), + };`; + } + + return `module.exports = ${assetFilename};`; + }, +}; diff --git a/config/paths.js b/config/paths.js new file mode 100644 index 000000000..c24b4dd1f --- /dev/null +++ b/config/paths.js @@ -0,0 +1,89 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebook/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); + +const envPublicUrl = process.env.PUBLIC_URL; + +function ensureSlash(inputPath, needsSlash) { + const hasSlash = inputPath.endsWith('/'); + if (hasSlash && !needsSlash) { + return inputPath.substr(0, inputPath.length - 1); + } else if (!hasSlash && needsSlash) { + return `${inputPath}/`; + } else { + return inputPath; + } +} + +const getPublicUrl = appPackageJson => + envPublicUrl || require(appPackageJson).homepage; + +// We use `PUBLIC_URL` environment variable or "homepage" field to infer +// "public path" at which the app is served. +// Webpack needs to know it to put the right