├── .browserslistrc ├── .codeclimate.yml ├── .create ├── .editorconfig ├── .env ├── .env.example ├── .eslintignore ├── .eslintrc ├── .firebaserc ├── .gitattributes ├── .githooks ├── pre-commit │ ├── commit │ ├── hello.js │ └── test └── pre-push │ └── prevent-force-push-to-master ├── .gitignore ├── .serverc ├── .stylelintignore ├── .stylelintrc ├── .travis.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── babel.config.js ├── firebase.json ├── jest.config.js ├── jsconfig.json ├── package.json ├── prettier.config.js ├── revision.txt ├── src ├── __tests__ │ ├── app.test.js │ ├── counter.test.js │ ├── enzyme.test.js │ ├── ezyme.test.js │ ├── input.test.js │ ├── predicts.test.js │ ├── reducer.test.js │ ├── shallow.test.js │ ├── sinon.test.js │ ├── snap.test.js │ └── snapshot.test.js ├── app.pug ├── app.tsx ├── assets │ ├── favicon │ │ ├── .gitkeep │ │ ├── favicon.png │ │ └── favicon.svg │ ├── fonts │ │ ├── .gitkeep │ │ ├── BLOKKNeue │ │ │ ├── Regular.woff │ │ │ └── Regular.woff2 │ │ └── Ruble │ │ │ ├── PTRoubleSans.woff │ │ │ └── PTRoubleSans.woff2 │ ├── images │ │ ├── 2018-08-10 08.37.13.jpg │ │ ├── cat.jpg │ │ ├── dog.jpg │ │ ├── dog.wdp │ │ ├── dog.webp │ │ └── logo.svg │ ├── public │ │ ├── .gitkeep │ │ ├── .htaccess │ │ ├── data │ │ │ ├── products.json │ │ │ └── words.md │ │ └── robots.txt │ ├── styles │ │ ├── _animation.scss │ │ ├── _base.scss │ │ ├── _font-resize.scss │ │ ├── _fonts.scss │ │ ├── _global.scss │ │ ├── _media.scss │ │ ├── _mixins.scss │ │ ├── _normalize.scss │ │ ├── _reset.scss │ │ ├── _root.scss │ │ ├── _variable.scss │ │ ├── client.scss │ │ └── mixins │ │ │ ├── _button.scss │ │ │ ├── _clearfix.scss │ │ │ ├── _fluid.scss │ │ │ ├── _fontface.scss │ │ │ ├── _fontsize.scss │ │ │ ├── _layout.scss │ │ │ ├── _multi-line-ellipsis.scss │ │ │ ├── _outline.scss │ │ │ └── _text-crop.scss │ └── svgstore │ │ ├── .gitkeep │ │ ├── close.svg │ │ └── favorite.svg ├── client.tsx ├── components │ ├── async-component │ │ └── index.tsx │ ├── badge │ │ ├── index.tsx │ │ └── styles.scss │ ├── button │ │ ├── index.test.js │ │ ├── index.tsx │ │ ├── input.styled.tsx │ │ ├── styles.scss │ │ └── styles.ts │ ├── card │ │ ├── index.test.js │ │ ├── index.tsx │ │ ├── styles.scss │ │ └── types.ts │ ├── checkbox │ │ ├── index.tsx │ │ └── styles.scss │ ├── circular-progress │ │ ├── index.tsx │ │ └── styles.scss │ ├── dynamic-import │ │ └── index.tsx │ ├── error-boundary │ │ └── index.tsx │ ├── form │ │ ├── i.tsx │ │ └── i2.tsx │ ├── icon │ │ ├── index.tsx │ │ └── styles.scss │ ├── image │ │ └── index.tsx │ ├── index.ts │ ├── input │ │ ├── assets │ │ │ └── clear.svg │ │ ├── index.tsx │ │ └── styles.scss │ ├── label │ │ └── index.tsx │ ├── link │ │ ├── index.tsx │ │ └── styles.scss │ ├── loader │ │ ├── index.tsx │ │ └── styles.scss │ ├── logo │ │ ├── assets │ │ │ └── logo.svg │ │ ├── index.tsx │ │ └── styles.scss │ ├── modal │ │ └── styles.scss │ ├── notification │ │ └── index.tsx │ ├── pagination │ │ ├── assets │ │ │ ├── next.svg │ │ │ └── prev.svg │ │ ├── index.tsx │ │ └── styles.scss │ ├── picture │ │ ├── index.tsx │ │ ├── styles.scss │ │ └── types.ts │ ├── post │ │ └── index.tsx │ ├── quantity │ │ ├── index.tsx │ │ ├── present.tsx │ │ └── styles.scss │ ├── radio-group │ │ └── index.tsx │ ├── radio │ │ ├── index.tsx │ │ └── styles.scss │ ├── sandwich │ │ ├── index.tsx │ │ └── styles.scss │ ├── simple-input │ │ ├── index.tsx │ │ └── styles.scss │ ├── spinner │ │ ├── index.tsx │ │ ├── styles.scss │ │ └── types.ts │ ├── svg │ │ └── index.tsx │ ├── title │ │ ├── index.tsx │ │ └── styles.scss │ └── widget │ │ ├── index.tsx │ │ └── styles.scss ├── fragments │ ├── animate │ │ ├── animated.tsx │ │ ├── index.tsx │ │ └── styles.scss │ ├── content │ │ ├── index.tsx │ │ └── styles.scss │ ├── dialog │ │ ├── index.tsx │ │ └── styles.scss │ ├── fetching │ │ ├── index.tsx │ │ ├── styles.scss │ │ └── types.ts │ ├── group │ │ ├── index.tsx │ │ ├── styles.scss │ │ └── types.ts │ ├── header │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── locale │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ └── navigation │ │ │ │ ├── example.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ ├── index.tsx │ │ └── styles.scss │ ├── index.ts │ ├── layout │ │ ├── index.tsx │ │ └── styles.scss │ ├── list │ │ └── index.tsx │ ├── loading │ │ ├── index.tsx │ │ └── types.ts │ └── tabs │ │ ├── styles.scss │ │ ├── tab.tsx │ │ ├── tabs.tsx │ │ └── usage.tsx ├── helpers │ ├── access-token.ts │ ├── api.ts │ ├── browser-history.ts │ ├── classes.ts │ ├── index.ts │ ├── predicts │ │ ├── comparison.ts │ │ ├── has-class.ts │ │ ├── index.ts │ │ ├── is-active.ts │ │ ├── is-email.ts │ │ ├── is-float.ts │ │ ├── is-function.ts │ │ ├── is-int.ts │ │ ├── is-null.ts │ │ ├── is-phone.ts │ │ └── is-undefined.ts │ └── utils │ │ ├── animation.ts │ │ ├── concat.ts │ │ ├── create-markup.ts │ │ ├── decl-of-num.ts │ │ ├── display-name.ts │ │ ├── format-date.ts │ │ ├── format-money-native.ts │ │ ├── format-money.ts │ │ ├── formated-phone.ts │ │ ├── index.ts │ │ ├── kebab-case.ts │ │ ├── logout.ts │ │ ├── make-query.ts │ │ ├── nl2br.ts │ │ ├── non-breaking.ts │ │ ├── object-sort.ts │ │ ├── parse-errors.ts │ │ ├── parse-query.ts │ │ ├── preload.ts │ │ ├── prepare-field.ts │ │ ├── prepare-phone.ts │ │ ├── range.ts │ │ ├── remove-spaces.ts │ │ ├── replace-mask.ts │ │ ├── request.ts │ │ ├── timestamp-date.ts │ │ ├── trim.ts │ │ ├── uc-first.ts │ │ └── validate.ts ├── layouts │ ├── core-layout │ │ ├── index.tsx │ │ └── styles.scss │ └── index.ts ├── pages │ ├── example-page │ │ ├── index.tsx │ │ ├── route.ts │ │ └── styles.scss │ ├── index.ts │ ├── main-page │ │ ├── assets │ │ │ └── image.jpg │ │ ├── index.tsx │ │ ├── route.ts │ │ └── styles.scss │ ├── nomatch-page │ │ ├── index.tsx │ │ ├── route.ts │ │ └── styles.scss │ └── panels-page │ │ ├── index.tsx │ │ ├── route.ts │ │ └── styles.scss ├── routes.ts ├── server.tsx ├── service-worker.ts ├── settings │ ├── constants.ts │ ├── dictionaries.ts │ ├── environment.ts │ ├── firebase.ts │ ├── index.ts │ ├── setup.js │ └── variables.ts ├── store │ ├── create-store.ts │ ├── index.ts │ └── providers │ │ ├── _ │ │ ├── cart.ts │ │ ├── students.ts │ │ ├── todo-demo.ts │ │ └── todostore.ts │ │ ├── app-store.ts │ │ ├── index.ts │ │ └── ui-store.ts ├── theme │ ├── colors.ts │ ├── common-styles.ts │ ├── index.ts │ ├── size.ts │ └── theme.ts ├── typings │ ├── declarations.d.ts │ ├── global.d.ts │ ├── images.d.ts │ ├── react.d.ts │ └── types.d.ts └── utils │ ├── index.ts │ ├── load-component │ ├── error-display.tsx │ └── index.tsx │ ├── outside-clicked │ └── index.tsx │ ├── render-dev-tools │ └── index.tsx │ ├── render-route │ └── index.tsx │ ├── svg-fixer │ └── index.tsx │ └── type │ └── index.ts ├── tsconfig.jest.json ├── tsconfig.json ├── tslint.json ├── typings.json ├── wallaby.conf.js ├── webpack ├── aliases.js ├── config.js ├── define.js ├── development.config.js ├── dll.config.js ├── entry-point.js ├── environment.js ├── functions.js ├── helpers.js ├── jest │ ├── fileTransformer.js │ ├── setup.js │ ├── shim.js │ └── tsPreprocessor.js ├── log.js ├── minimizer.js ├── optimization.js ├── plugins.js ├── plugins │ ├── analyzer.js │ ├── bower.js │ ├── compression.js │ ├── development.js │ ├── dll.js │ ├── general.js │ ├── hard-cache.js │ ├── lint.js │ ├── manifest.js │ ├── prebuild.js │ ├── precache.js │ ├── production.js │ ├── static.js │ └── visualizer.js ├── polyfills.js ├── postcss.config.js ├── prebuild.js ├── production.config.js ├── proxy.js ├── rules.js ├── rules │ ├── files.js │ ├── fonts.js │ ├── images.js │ ├── images__.js │ ├── markdown.js │ ├── media.js │ ├── scripts.js │ ├── styles.js │ └── template.js ├── server.express.js ├── server.js ├── server.new.js ├── server0.js ├── server2.js ├── stats.js ├── svgo.js └── utils │ ├── add.js │ └── helpers.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | [production staging] 2 | > 0.25% 3 | last 2 versions 4 | ie >= 11 5 | 6 | [development] 7 | last 1 chrome version 8 | last 1 firefox version 9 | 10 | [modern] 11 | Firefox >= 53 12 | Edge >= 15 13 | Chrome >= 58 14 | iOS >= 10.1 15 | 16 | [legacy] 17 | > 0.25% 18 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | eslint: 3 | enabled: true 4 | channel: "eslint-4" 5 | config: 6 | config: .eslintrc 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_size = 2 7 | end_of_line = lf 8 | indent_style = space 9 | max_line_length = 100 10 | insert_final_newline = true 11 | 12 | [*.{js,jsx,ts,tsx}] 13 | file_type = jsx 14 | trim_trailing_whitespace = true 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | 19 | [{*.md, COMMIT_EDITMSG}] 20 | max_line_length = 0 21 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_NAME = 'React starter' 2 | REACT_APP_BG_COLOR = '#2185d0' 3 | REACT_APP_THEME_COLOR = '#3367D6' 4 | REACT_APP_NAME_SHORT = 'RS-PWA' 5 | REACT_APP_NAME_DESC = 'React-starter a react web sterter kit!' 6 | REACT_APP_SECRET = 'dev_Secret' 7 | REACT_APP_API_URL = 'http://0.0.0.0:4002' 8 | 9 | # DaData 10 | DADATA_API_KEY = '' 11 | DADATA_COMPANY_API_URL = '' 12 | DADATA_ADDRESS_API_URL = '' 13 | 14 | # Firebase 15 | # https://console.firebase.google.com/project/_/settings/general/ 16 | # https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk 17 | 18 | FIREBASE_API_KEY = '' 19 | FIREBASE_AUTH_DOMAIN = '' 20 | FIREBASE_DATABASE_URL = '' 21 | FIREBASE_PROJECT_ID = '' 22 | FIREBASE_STORAGE_BUCKET = '' 23 | FIREBASE_MESSAGING_SENDER_ID = '' 24 | 25 | # Authentication 26 | JWT_SECRET = xxxxx 27 | 28 | GOOGLE_CLIENT_ID = xxxxx 29 | GOOGLE_CLIENT_SECRET = xxxxx 30 | GOOGLE_ANALYTICS_ID = 'UA-0000000-00' 31 | 32 | FACEBOOK_APP_ID = xxxxx 33 | FACEBOOK_APP_SECRET = xxxxx 34 | 35 | # PostgreSQL 36 | # https://www.postgresql.org/docs/current/static/libpq-envars.html 37 | 38 | PGHOST = localhost 39 | PGUSER = postgres 40 | PGDATABASE = app 41 | PGPASSWORD = '' 42 | PGPORT = 5432 43 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_NAME = 'React starter' 2 | REACT_APP_BG_COLOR = '#2185d0' 3 | REACT_APP_THEME_COLOR = '#3367D6' 4 | REACT_APP_NAME_SHORT = 'RS-PWA' 5 | REACT_APP_NAME_DESC = 'React-starter a react web sterter kit!' 6 | REACT_APP_SECRET = 'dev_Secret' 7 | REACT_APP_API_URL = 'http://0.0.0.0:4002' 8 | 9 | # DaData 10 | DADATA_API_KEY = '' 11 | DADATA_COMPANY_API_URL = '' 12 | DADATA_ADDRESS_API_URL = '' 13 | 14 | # Firebase 15 | # https://console.firebase.google.com/project/_/settings/general/ 16 | # https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk 17 | 18 | FIREBASE_API_KEY = '' 19 | FIREBASE_AUTH_DOMAIN = '' 20 | FIREBASE_DATABASE_URL = '' 21 | FIREBASE_PROJECT_ID = '' 22 | FIREBASE_STORAGE_BUCKET = '' 23 | FIREBASE_MESSAGING_SENDER_ID = '' 24 | 25 | # Authentication 26 | JWT_SECRET = xxxxx 27 | 28 | GOOGLE_CLIENT_ID = xxxxx 29 | GOOGLE_CLIENT_SECRET = xxxxx 30 | GOOGLE_ANALYTICS_ID = 'UA-0000000-00' 31 | 32 | FACEBOOK_APP_ID = xxxxx 33 | FACEBOOK_APP_SECRET = xxxxx 34 | 35 | # PostgreSQL 36 | # https://www.postgresql.org/docs/current/static/libpq-envars.html 37 | 38 | PGHOST = localhost 39 | PGUSER = postgres 40 | PGDATABASE = app 41 | PGPASSWORD = '' 42 | PGPORT = 5432 43 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack/* 2 | typings/* 3 | coverage/ 4 | node_modules/ 5 | **/*.d.ts 6 | **/*.scss.d.ts 7 | !**/__tests__/**/*.js 8 | 9 | # tmp 10 | dist/* 11 | logs/ 12 | .cache/ 13 | .awcache/ 14 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "db": "react-cms-641a6", 4 | "prod": "react-firebase-graphql", 5 | "dev": "react-firebase-dev" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto 4 | package-lock.json -diff 5 | bin/* eol=lf 6 | 7 | # For the following file types, normalize line endings to LF on 8 | # checkin and prevent conversion to CRLF when they are checked out 9 | # (this is required in order to prevent newline related issues like, 10 | # for example, after the build script is run) 11 | .* text eol=lf 12 | *.html text eol=lf 13 | *.css text eol=lf 14 | *.less text eol=lf 15 | *.styl text eol=lf 16 | *.scss text eol=lf 17 | *.sass text eol=lf 18 | *.sss text eol=lf 19 | *.js text eol=lf 20 | *.jsx text eol=lf 21 | *.ts text eol=lf 22 | *.tsx text eol=lf 23 | *.json text eol=lf 24 | *.md text eol=lf 25 | *.mjs text eol=lf 26 | *.svg text eol=lf 27 | *.txt text eol=lf 28 | *.xml text eol=lf 29 | -------------------------------------------------------------------------------- /.githooks/pre-commit/commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PATH_ROOT="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 4 | CHANGELOG="$PATH_ROOT/revision.txt" 5 | 6 | ENV=$1 7 | 8 | if [[ -z "$SOURCE_COMMIT" ]]; then 9 | export SOURCE_COMMIT="${SOURCE_COMMIT:-$(git rev-parse HEAD)}" 10 | echo "$ENV:$SOURCE_COMMIT" | tee -i $CHANGELOG # -ai | write new line 11 | 12 | git add $CHANGELOG 13 | fi 14 | 15 | # "predev-build": "./.commit development", 16 | # "preprod-build": "./.commit production", 17 | # "prestage-build": "./.commit staging", 18 | -------------------------------------------------------------------------------- /.githooks/pre-commit/hello.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | console.log('hi!'); 4 | -------------------------------------------------------------------------------- /.githooks/pre-commit/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PATCH_FILE="working-tree.patch" 4 | 5 | function cleanup { 6 | exit_code=$? 7 | if [ -f "$PATCH_FILE" ]; then 8 | git apply "$PATCH_FILE" 2> /dev/null 9 | rm "$PATCH_FILE" 10 | fi 11 | exit $exit_code 12 | } 13 | 14 | trap cleanup EXIT SIGINT SIGHUP 15 | 16 | # Cancel any changes to the working tree that are not going to be committed 17 | git diff > "$PATCH_FILE" 18 | git checkout -- . 19 | 20 | git_cached_files=$(git diff --cached --name-only --diff-filter=ACMR | grep "\.js$") 21 | if [ "$git_cached_files" ]; then 22 | npm test --silent || exit 1 23 | fi 24 | -------------------------------------------------------------------------------- /.githooks/pre-push/prevent-force-push-to-master: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Sometimes it's better to not allow users to make force pushes to some branches. 4 | # It can cause losing of some commits and can be pain for a team. 5 | # If you want to protect from doing such a harmful action then add protected branches to "protected_branches" variable. 6 | 7 | protected_branch='(master|dev)' # you can set up multiple protected branches 8 | 9 | policy='[Policy] Never force push or delete the '$protected_branch' branch! (Prevented with pre-push hook.)' 10 | 11 | parent_pid=$(ps -oppid= -p $PPID) 12 | push_command=$(ps -ocommand= -p $parent_pid) 13 | 14 | is_destructive='force|delete|\-f' 15 | will_remove_protected_branch=':'$protected_branch 16 | 17 | if ([[ $push_command =~ $is_destructive ]] && [[ $push_command =~ $protected_branch ]]) \ 18 | || [[ $push_command =~ $will_remove_protected_branch ]] 19 | then 20 | echo $policy 21 | exit 1 22 | fi 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Autogenerated folders & files 2 | fabfile.py 3 | fabfile.pyc 4 | coverage 5 | .coverage 6 | .deepcheck 7 | features.md 8 | code-example 9 | release-builds 10 | **/client.scss.d.ts 11 | **/styles.scss.d.ts 12 | **/__snapshots__ 13 | .env.* 14 | !.env.example 15 | 16 | # Compiled output 17 | dist 18 | profiling 19 | .docz 20 | .cache 21 | .awcache 22 | 23 | # Numerous always-ignore extensions 24 | *.bak 25 | *.diff 26 | *.err 27 | *.exe 28 | *.orig 29 | *.rej 30 | *.sass-cache 31 | *.swo 32 | *.swp 33 | *.vi 34 | *.scss.d.ts 35 | *~ 36 | 37 | # ignore log files and databases 38 | *.log 39 | *.sql 40 | *.sqlite 41 | npm-debug.log* 42 | yarn-debug.log* 43 | yarn-error.log* 44 | 45 | # ignore compiled files 46 | *.com 47 | *.class 48 | *.dll 49 | *.exe 50 | *.o 51 | *.so 52 | 53 | # ignore packaged files 54 | *.7z 55 | *.dmg 56 | *.gz 57 | *.iso 58 | *.jar 59 | *.rar 60 | *.tar 61 | *.zip 62 | 63 | # ignore all files starting with . or ~ 64 | ~* 65 | 66 | # ignore Editor files 67 | *.sublime-project 68 | *.sublime-workspace 69 | *.komodoproject 70 | 71 | # OS or Editor folders 72 | *.sublime-grunt.cache 73 | *.sublime-project 74 | *.sublime-workspace 75 | ._* 76 | .cache 77 | .vscode 78 | .DS_Store 79 | .project 80 | .settings 81 | .tmproj 82 | ehthumbs.db 83 | Thumbs.db 84 | 85 | # IDEs 86 | .buildpath 87 | .project 88 | .settings/ 89 | .build/ 90 | .idea/ 91 | 92 | # Vagrant 93 | .vagrant/ 94 | 95 | # Folders to ignore 96 | .CVS 97 | .git 98 | .hg 99 | .svn 100 | bower_components 101 | .publish 102 | 103 | # Node.JS 104 | stats.json 105 | node_modules 106 | npm-shrinkwrap.json 107 | 108 | # Directories potentially created on remote AFP share 109 | Network Trash Folder 110 | Temporary Items 111 | 112 | # ------------------------- 113 | # BEGIN Whitelisted Files 114 | # ------------------------- 115 | 116 | # track these files, if they exist 117 | !.gitignore 118 | !README.md 119 | !CHANGELOG.md 120 | !package.json 121 | !composer.json 122 | -------------------------------------------------------------------------------- /.serverc: -------------------------------------------------------------------------------- 1 | { 2 | "hot": true, 3 | "hmr": true, 4 | "open": true, 5 | "overlay": { 6 | "errors": true, 7 | "warnings": true 8 | }, 9 | "port": 8080, 10 | "host": "0.0.0.0", 11 | "stats": { 12 | "assets": true, 13 | "children": false, 14 | "chunks": false, 15 | "hash": false, 16 | "modules": false, 17 | "publicPath": false, 18 | "chunkGroups": true, 19 | "timings": true, 20 | "version": false, 21 | "warnings": true, 22 | "contentBase": "./dist", 23 | "optimizationBailout": true, 24 | "colors": { 25 | "green": "\u001b[32m" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | 4 | # tmp 5 | dist 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "8" 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | notifications: 13 | email: 14 | on_failure: change 15 | on_success: change 16 | slack: 17 | rooms: 18 | - celebro:RBaf3z6RftDAVPt3TjN4QBgE#react-starter 19 | 20 | cache: 21 | yarn: true 22 | directories: 23 | - node_modules 24 | 25 | before_install: 26 | - export CHROME_BIN=chromium-browser 27 | - export DISPLAY=:99.0 28 | - sh -e /etc/init.d/xvfb start 29 | 30 | install: 31 | # TODO: Use npm again functions runtime is updated to a node verion with lock files 32 | - yarn install 33 | 34 | script: 35 | - npm run lint 36 | - npm run test 37 | - npm run example-build 38 | 39 | after_success: 40 | - npm run deploy 41 | # - firebase deploy --token $FIREBASE_TOKEN --non-interactive 42 | # - npm run lh -- https://staging.example.com 43 | 44 | addons: 45 | code_climate: 46 | repo_token: 2b32652090b39c2cc95f0ea3ca3e2c7ff452b75388a81f0f4cfacbce4588f6e6 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | # v2.3.3 / 2018-09-19 6 | - Improve some components 7 | - Fixed typescript bugs 8 | 9 | #x.x.x / -- 10 | ================== 11 | 12 | * Bug fix - 13 | * Feature - 14 | * Chore - 15 | * Docs - 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.10.0-alpine 2 | 3 | # Set a working directory 4 | WORKDIR /src 5 | 6 | COPY ./package.json . 7 | COPY ./yarn.lock . 8 | 9 | # Install Node.js dependencies 10 | RUN yarn install --production --no-progress 11 | 12 | # Copy application files 13 | COPY ./dist . 14 | 15 | # Run the container under "node" user by default 16 | USER node 17 | 18 | CMD [ "node" ] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Edik Bulikyan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "jsx": "react", 5 | "paths": { 6 | "hocs/*": [ 7 | "./hocs/*" 8 | ], 9 | "pages/*": [ 10 | "./pages/*" 11 | ], 12 | "utils/*": [ 13 | "./utils/*" 14 | ], 15 | "config/*": [ 16 | "./config/*" 17 | ], 18 | "assets/*": [ 19 | "./assets/*" 20 | ], 21 | "typings/*": [ 22 | "./typings/*" 23 | ], 24 | "layouts/*": [ 25 | "./layouts/*" 26 | ], 27 | "settings/*": [ 28 | "./settings/*" 29 | ], 30 | "fragments/*": [ 31 | "./fragments/*" 32 | ], 33 | "components/*": [ 34 | "./components/*" 35 | ], 36 | "variables/*": [ 37 | "./variables/*" 38 | ], 39 | "styles/*": [ 40 | "./assets/styles/*" 41 | ], 42 | "images/*": [ 43 | "./assets/images/*" 44 | ], 45 | "scripts/*": [ 46 | "./assets/scripts/*" 47 | ], 48 | "svgstore/*": [ 49 | "./assets/svgstore/*" 50 | ], 51 | } 52 | }, 53 | "include": [ 54 | "src/**", 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | bracketSpacing: true, 6 | jsxBracketSameLine: false, 7 | tabWidth: 4, 8 | semi: true, 9 | } 10 | -------------------------------------------------------------------------------- /revision.txt: -------------------------------------------------------------------------------- 1 | :a86ddd074120c1b54577ca6374727f9c3587e152 2 | -------------------------------------------------------------------------------- /src/__tests__/app.test.js: -------------------------------------------------------------------------------- 1 | test('App works', () => {}) 2 | 3 | // import React from 'react'; 4 | // import ReactDOM from 'react-dom'; 5 | // import App from './App'; 6 | 7 | // it('renders without crashing', () => { 8 | // const div = document.createElement('div'); 9 | // ReactDOM.render(, div); 10 | // ReactDOM.unmountComponentAtNode(div); 11 | // }); 12 | 13 | // import React from 'react' 14 | // import { render, fireEvent } from 'react-testing-library' 15 | // import App from '../App' 16 | 17 | // test('App works', () => { 18 | // const { container } = render() 19 | // console.log(container) 20 | // const buttons = container.querySelectorAll('button') 21 | 22 | // expect(buttons[0].textContent).toBe('+1') 23 | // expect(buttons[1].textContent).toBe('+10') 24 | // expect(buttons[2].textContent).toBe('+100') 25 | // expect(buttons[3].textContent).toBe('+1000') 26 | 27 | // const result = container.querySelector('span') 28 | // expect(result.textContent).toBe('0') 29 | // fireEvent.click(buttons[0]) 30 | // expect(result.textContent).toBe('1') 31 | // fireEvent.click(buttons[1]) 32 | // expect(result.textContent).toBe('11') 33 | // fireEvent.click(buttons[2]) 34 | // expect(result.textContent).toBe('111') 35 | // fireEvent.click(buttons[3]) 36 | // expect(result.textContent).toBe('1111') 37 | // fireEvent.click(buttons[2]) 38 | // expect(result.textContent).toBe('1211') 39 | // fireEvent.click(buttons[1]) 40 | // expect(result.textContent).toBe('1221') 41 | // fireEvent.click(buttons[0]) 42 | // expect(result.textContent).toBe('1222') 43 | // }) 44 | 45 | // import React from 'react' 46 | 47 | // // import { render, unmountComponentAtNode } from 'react-dom' 48 | // import { render } from 'react-dom' 49 | // import { App } from '../app' 50 | 51 | // it('renders without crashing', () => { 52 | // const div = document.createElement('div') 53 | // render(, div) 54 | // // unmountComponentAtNode(div) 55 | // }) 56 | -------------------------------------------------------------------------------- /src/__tests__/counter.test.js: -------------------------------------------------------------------------------- 1 | test('test', () => {}) 2 | 3 | // src/components/__tests__/counter_spec.tsx 4 | // import * as React from 'react' 5 | // import * as TestUtils from 'react-dom/test-utils' 6 | // import * as ReactShallowRenderer from 'react-test-renderer/shallow' 7 | 8 | // import { createStore } from 'redux' 9 | 10 | // import { Counter } from '../counter' 11 | // import { reducers } from '../../reducers' 12 | 13 | // describe('', () => { 14 | // it('renders', () => { 15 | // const store = createStore(reducers) 16 | // const renderer = new ReactShallowRenderer() 17 | 18 | // expect(renderer.render( 19 | // 20 | // )).toMatchSnapshot() 21 | // }) 22 | // }); 23 | -------------------------------------------------------------------------------- /src/__tests__/enzyme.test.js: -------------------------------------------------------------------------------- 1 | test('test', () => { }) 2 | 3 | // import React from 'react'; 4 | // import {shallow} from 'enzyme'; 5 | // import CheckboxWithLabel from '../CheckboxWithLabel'; 6 | 7 | // test('CheckboxWithLabel changes the text after click', () => { 8 | // // Render a checkbox with label in the document 9 | // const checkbox = shallow(); 10 | 11 | // expect(checkbox.text()).toEqual('Off'); 12 | 13 | // checkbox.find('input').simulate('change'); 14 | 15 | // expect(checkbox.text()).toEqual('On'); 16 | // }); 17 | -------------------------------------------------------------------------------- /src/__tests__/input.test.js: -------------------------------------------------------------------------------- 1 | // import React from 'react' 2 | // import Link from '../Link.react' 3 | // import renderer from 'react-test-renderer' 4 | 5 | const sum = (a, b) => { 6 | return a + b 7 | } 8 | 9 | const hello = name => { 10 | return `Hello ${name}` 11 | } 12 | 13 | test('should add 2 numbers', () => { 14 | const result = sum(2, 3) 15 | expect(result).toBe(5) 16 | }) 17 | 18 | test('should say hello to you', () => { 19 | const result = hello('name') 20 | expect(result).toBe('Hello name') 21 | }) 22 | -------------------------------------------------------------------------------- /src/__tests__/predicts.test.js: -------------------------------------------------------------------------------- 1 | // import { 2 | // isInt, 3 | // // isNull, 4 | // // isPhone, 5 | // // isFloat, 6 | // // compare, 7 | // // isActive, 8 | // // isUndefined, 9 | // } from 'helpers/predicts' 10 | 11 | test('test', () => {}) 12 | 13 | // test('isInt(1) to equal true', () => { 14 | // expect(isInt(1)).toBeTruthy() 15 | // }) 16 | 17 | // test('isInt("1") to equal false', () => { 18 | // expect(isInt('1')).toBeFalsy() 19 | // }) 20 | 21 | // test('isUndefined(param) to equal true', () => { 22 | // expect(isUndefined('undefined')).toBeTruthy() 23 | // }) 24 | 25 | // test('isUndefined(param) to equal true', () => { 26 | // const param = '' 27 | // expect(isUndefined(param)).toBeFalsy() 28 | // }) 29 | -------------------------------------------------------------------------------- /src/__tests__/reducer.test.js: -------------------------------------------------------------------------------- 1 | test('test', () => {}) 2 | 3 | // // src/reducers/__tests__/index_spec.tsx 4 | // import reducers, { 5 | // initialState, 6 | // } from '../index' 7 | 8 | // import { 9 | // incrementCounter 10 | // } from '../../actions' 11 | 12 | // const resultOf = actions => 13 | // actions.reduce(reducers, initialState) 14 | 15 | // describe('reducers/counter', () => { 16 | // it('starts at 0', () => 17 | // expect(resultOf([])).toMatchSnapshot()) 18 | 19 | // it('increments to 6', () => 20 | // expect(resultOf([ 21 | // incrementCounter(1), 22 | // incrementCounter(2), 23 | // incrementCounter(3), 24 | // ])).toMatchSnapshot()) 25 | // }) 26 | -------------------------------------------------------------------------------- /src/__tests__/shallow.test.js: -------------------------------------------------------------------------------- 1 | test('predicts', () => {}) 2 | 3 | // import * as React from "react"; 4 | // import { shallow } from "enzyme"; 5 | 6 | // import Hello from "../Hello"; 7 | 8 | // it("renders the heading", () => { 9 | // const result = shallow().contains(

Hello!

); 10 | // expect(result).toBeTruthy(); 11 | // }); 12 | 13 | // /* tslint:disable-next-line */ 14 | // import React from 'react'; 15 | // /* tslint:disable-next-line */ 16 | // import Enzyme, { shallow } from 'enzyme'; 17 | // /* tslint:disable-next-line */ 18 | // import Adapter from 'enzyme-adapter-react-16'; 19 | // import Wow from './Wow'; 20 | // Enzyme.configure({ adapter: new Adapter() }); 21 | // it('shallow renders without crashing', () => { 22 | // shallow(); 23 | // }); 24 | -------------------------------------------------------------------------------- /src/__tests__/snap.test.js: -------------------------------------------------------------------------------- 1 | test('test', () => { }) 2 | 3 | // describe('YourComponent test', () => { 4 | // let component 5 | 6 | // beforeEach(() => { 7 | // component = mount() 8 | // }) 9 | 10 | // it('should render valid mode', () => { 11 | // component.setProps({ isValid: true }) 12 | // expect(component).toMatchSnapshot() 13 | // }) 14 | 15 | // it('should render invalid mode', () => { 16 | // component.setProps({ isValid: false }) 17 | // expect(component).toMatchSnapshot() 18 | // }) 19 | // }) 20 | -------------------------------------------------------------------------------- /src/__tests__/snapshot.test.js: -------------------------------------------------------------------------------- 1 | // import React from 'react' 2 | // import Link from '../__utils__/link' 3 | // import renderer from 'react-test-renderer' 4 | 5 | // it('отображается корректно', () => { 6 | // const component = renderer.create(Facebook) 7 | // const tree = component.toJSON() 8 | // expect(tree).toMatchSnapshot() 9 | // }) 10 | 11 | // // Link.react.test.js 12 | // import React from 'react'; 13 | // import Link from '../Link.react'; 14 | // import renderer from 'react-test-renderer'; 15 | 16 | test('test', () => { }) 17 | 18 | // test('Link changes the class when hovered', () => { 19 | // const component = renderer.create( 20 | // Facebook, 21 | // ); 22 | // let tree = component.toJSON(); 23 | // expect(tree).toMatchSnapshot(); 24 | 25 | // // manually trigger the callback 26 | // tree.props.onMouseEnter(); 27 | // // re-rendering 28 | // tree = component.toJSON(); 29 | // expect(tree).toMatchSnapshot(); 30 | 31 | // // manually trigger the callback 32 | // tree.props.onMouseLeave(); 33 | // // re-rendering 34 | // tree = component.toJSON(); 35 | // expect(tree).toMatchSnapshot(); 36 | // }); 37 | -------------------------------------------------------------------------------- /src/app.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html( 3 | lang='ru' 4 | class='no-js' 5 | ) 6 | head 7 | meta(charset='UTF-8') 8 | meta(http-equiv='X-UA-Compatible', content='IE=edge') 9 | 10 | link(rel='dns-prefetch', href='https://cdn.polyfill.io') 11 | link(rel='preload', href='https://cdn.polyfill.io/v2/polyfill.min.js', as="script") 12 | 13 | title React template 14 | 15 | meta(name='viewport', content='width=device-width,initial-scale=1,maximum-scale=5') 16 | 17 | base(href=htmlWebpackPlugin.options.basePath) 18 | meta(name='fragment', content='!') 19 | 20 | meta(name='google', content='notranslate') 21 | meta(name='mobile-web-app-capable', content='yes') 22 | 23 | //- Article Specific Metadata 24 | meta(name='robots', content='index,follow') 25 | //- meta(name='keywords', content='React, starter, pwa') 26 | //- meta(name='description', content='React-starter a react web sterter kit') 27 | meta(name='author', content='Edik Bulikyan ') 28 | 29 | body.loading 30 | main#app.layout-wrapper 31 | 32 | noscript 33 | .bulletin 34 | p.headline MODERN BROWSER EXPERIENCE 35 | p Your browser doesn't support the main features of this website. 36 | p 37 | | Please upgrade to 38 | a(target='_blank', href='https://www.google.com/chrome/browser/desktop/index.html') Google Chrome 39 | | , Safari or Firefox for a better experience. 40 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import 'styles/client.scss' 4 | 5 | import { hot } from 'react-hot-loader' 6 | 7 | import { Router, Switch } from 'react-router-dom' 8 | 9 | import { CoreLayout } from 'layouts' 10 | 11 | import { ErrorBoundary } from 'components' 12 | 13 | import { renderRoute, renderDevTools } from 'utils' 14 | 15 | import { environment } from 'settings' 16 | 17 | import { routes } from './routes' 18 | 19 | function App ({ history }: any) { 20 | return ( 21 | 22 | 23 | 24 | 25 | {routes && routes.map(renderRoute)} 26 | 27 | 28 | {environment.development && renderDevTools()} 29 | 30 | 31 | 32 | ) 33 | } 34 | 35 | export default hot(module)(App) 36 | -------------------------------------------------------------------------------- /src/assets/favicon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/favicon/.gitkeep -------------------------------------------------------------------------------- /src/assets/favicon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/favicon/favicon.png -------------------------------------------------------------------------------- /src/assets/favicon/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/fonts/.gitkeep -------------------------------------------------------------------------------- /src/assets/fonts/BLOKKNeue/Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/fonts/BLOKKNeue/Regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/BLOKKNeue/Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/fonts/BLOKKNeue/Regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Ruble/PTRoubleSans.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/fonts/Ruble/PTRoubleSans.woff -------------------------------------------------------------------------------- /src/assets/fonts/Ruble/PTRoubleSans.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/fonts/Ruble/PTRoubleSans.woff2 -------------------------------------------------------------------------------- /src/assets/images/2018-08-10 08.37.13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/images/2018-08-10 08.37.13.jpg -------------------------------------------------------------------------------- /src/assets/images/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/images/cat.jpg -------------------------------------------------------------------------------- /src/assets/images/dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/images/dog.jpg -------------------------------------------------------------------------------- /src/assets/images/dog.wdp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/images/dog.wdp -------------------------------------------------------------------------------- /src/assets/images/dog.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/images/dog.webp -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/public/.gitkeep -------------------------------------------------------------------------------- /src/assets/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /src/assets/styles/_animation.scss: -------------------------------------------------------------------------------- 1 | :global { 2 | .fade-exit, 3 | .fade-enter { 4 | transition: opacity .3s ease-in-out, 5 | transform .3s ease-in-out; 6 | } 7 | 8 | .fade-exit { 9 | opacity: 1; 10 | z-index: 1; 11 | transform: translate3d(0, 0, 0); 12 | } 13 | 14 | .fade-enter { 15 | opacity: 0; 16 | z-index: 2; 17 | transform: translate3d(0, 100px, 0); 18 | } 19 | 20 | .fade-exit.fade-exit-active { 21 | opacity: 0; 22 | transform: translate3d(0, 100px, 0); 23 | } 24 | 25 | .fade-enter.fade-enter-active { 26 | transform: translate3d(0, 0, 0); 27 | opacity: 1; 28 | } 29 | } 30 | 31 | @keyframes spinner { 32 | 33 | 0% { transform: rotate(0deg); } 34 | 35 | 100% { transform: rotate(360deg); } 36 | } 37 | 38 | @keyframes SVGRotate { 39 | to { 40 | stroke-dashoffset: 0; 41 | } 42 | } 43 | 44 | @keyframes swing { 45 | 15% { 46 | transform: translateX(5px); 47 | } 48 | 49 | 30% { 50 | transform: translateX(-5px); 51 | } 52 | 53 | 50% { 54 | transform: translateX(3px); 55 | } 56 | 57 | 65% { 58 | transform: translateX(-3px); 59 | } 60 | 61 | 80% { 62 | transform: translateX(2px); 63 | } 64 | 65 | 100% { 66 | transform: translateX(0); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/assets/styles/_base.scss: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-touch-callout: none; 3 | -webkit-overflow-scrolling: touch; 4 | 5 | &, 6 | &:before, 7 | &:after { 8 | box-sizing: border-box; 9 | } 10 | } 11 | 12 | html, 13 | body { 14 | height: 100%; 15 | } 16 | 17 | html { 18 | @include fluid-type(font-size, 320px, 1366px, $font-size-base, $font-size-limit); 19 | font-family: $font-family-base; 20 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 21 | -webkit-tap-highlight-color: transparent; 22 | } 23 | 24 | body { 25 | width: 100%; 26 | color: $c-text-color; 27 | font-kerning: none; 28 | background-color: #fafafa; 29 | } 30 | 31 | p, 32 | div, 33 | body, 34 | input, 35 | button, 36 | select, 37 | textarea { 38 | font-feature-settings: 'kern', 'onum', 'liga'; 39 | } 40 | 41 | textarea { 42 | text-align: left; 43 | font-synthesis: none; 44 | text-rendering: optimizeLegibility; 45 | -webkit-font-smoothing: antialiased; 46 | -moz-osx-font-smoothing: grayscale; 47 | } 48 | 49 | svg { 50 | color: inherit; 51 | fill: currentColor; 52 | shape-rendering: geometricPrecision; /* auto | optimizeSpeed | crispEdges | geometricPrecision | inherit; */ 53 | 54 | &:not(:root) { 55 | overflow: hidden; 56 | } 57 | } 58 | 59 | ::selection { 60 | color: $c-select-color; 61 | background-color: $c-select-background; 62 | text-shadow: none; 63 | } 64 | 65 | [hidden], 66 | template { 67 | display: none; 68 | } 69 | 70 | [role = 'button'] { 71 | cursor: pointer; 72 | } 73 | 74 | :global { 75 | .layout-wrapper { 76 | width: 100%; 77 | min-height: 100%; 78 | overflow-x: hidden; 79 | overflow-y: auto; 80 | } 81 | 82 | .is-overflow { 83 | overflow: hidden; 84 | } 85 | 86 | .bulletin { 87 | position: absolute 0; 88 | width: 100%; 89 | height: 100%; 90 | font-size: 2rem; 91 | line-height: 1.5; 92 | display: flex; 93 | align-items: center; 94 | justify-content: center; 95 | flex-direction: column; 96 | } 97 | 98 | .headline { 99 | font-weight: 700; 100 | margin-bottom: 5px; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/assets/styles/_font-resize.scss: -------------------------------------------------------------------------------- 1 | $min_width: 400; 2 | $max_width: 800; 3 | 4 | $min_font: 12; 5 | $max_font: 24; 6 | 7 | :root { font-size: #{$min_font}px; } 8 | 9 | @media (min-width : #{$min_width}px) and (max-width : #{$max_width}px) { 10 | :root { 11 | font-size: calc(#{$min_font}px + (#{$max_font} - #{$min_font}) * ( (100vw - #{$min_width}px) / ( #{$max_width} - #{$min_width}))); 12 | } 13 | } 14 | 15 | @media (min-width : #{$max_width}px) { 16 | :root { 17 | font-size: #{$max_font}px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/styles/_fonts.scss: -------------------------------------------------------------------------------- 1 | // @font-face { 2 | // font-family: 'PTRoubleSans'; 3 | // font-weight: 400; 4 | // font-style: normal; 5 | // src: url('assets/fonts/Ruble/PTRoubleSans.woff2') format('woff2'), 6 | // url('assets/fonts/Ruble/PTRoubleSans.woff') format('woff'); 7 | // } 8 | 9 | // @font-face { 10 | // font-family: 'BLOKK'; 11 | // font-style: normal; 12 | // font-weight: 400; 13 | // src: url('assets/fonts/BLOKKNeue/Regular.woff2') format('woff2'), 14 | // url('assets/fonts/BLOKKNeue/Regular.woff') format('woff'); 15 | // } 16 | 17 | // @font-face { 18 | // font-family: 'SFPROText'; 19 | // font-weight: 300; 20 | // font-style: normal; 21 | // font-display: swap; 22 | // src: local('SF Pro Text Light'), 23 | // local('SFProText-Light'), 24 | // url('/assets/fonts/SF/light.woff2') format('woff2'), 25 | // url('/assets/fonts/SF/light.woff') format('woff'); 26 | // } 27 | 28 | /* 29 | @media screen and (-webkit-min-device-pixel-ratio:0) { 30 | @font-face { 31 | font-family: 'BLOKK'; 32 | src: url('assets/fonts/BLOKKNeue/Regular.svg') format('svg'); 33 | } 34 | } */ 35 | -------------------------------------------------------------------------------- /src/assets/styles/_global.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_variable.scss'; 2 | @import 'styles/_mixins.scss'; 3 | @import 'styles/_media.scss'; 4 | 5 | /* @custom-selector :--heading h1, h2, h3, h4, h5, h6; */ 6 | 7 | // article :--heading + p { 8 | // margin-top: 0; 9 | // } 10 | -------------------------------------------------------------------------------- /src/assets/styles/_media.scss: -------------------------------------------------------------------------------- 1 | @custom-media --phone-medium (width >= 375px); 2 | @custom-media --phone-big (width >= 480px); 3 | @custom-media --tablet (width > 640px); 4 | @custom-media --tablet-middle (width >= 768px); 5 | @custom-media --tablet-big (width >= 860px); 6 | @custom-media --desktop (width >= 980px); 7 | @custom-media --desktop-less (width > 980px) and (width < 1160px); 8 | @custom-media --desktop-more (width >= 1160px); 9 | @custom-media --desktop-middle (width >= 1280px); 10 | @custom-media --desktop-large (width >= 1400px); 11 | @custom-media --portrait (orientation: portrait); 12 | -------------------------------------------------------------------------------- /src/assets/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/mixins/_fluid.scss'; 2 | @import 'styles/mixins/_layout.scss'; 3 | @import 'styles/mixins/_button.scss'; 4 | @import 'styles/mixins/_outline.scss'; 5 | @import 'styles/mixins/_clearfix.scss'; 6 | @import 'styles/mixins/_text-crop.scss'; 7 | -------------------------------------------------------------------------------- /src/assets/styles/_reset.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v3.0 | 20180413 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font-family: inherit; 24 | vertical-align: baseline; 25 | } 26 | 27 | /* HTML5 display-role reset for older browsers */ 28 | article, aside, details, figcaption, figure, 29 | footer, header, hgroup, main, menu, nav, section { 30 | display: block; 31 | } 32 | 33 | /* HTML5 hidden-attribute fix for newer browsers */ 34 | *[hidden] { 35 | display: none; 36 | } 37 | 38 | body { 39 | line-height: 1; 40 | } 41 | 42 | ol, ul { 43 | list-style: none; 44 | } 45 | 46 | blockquote, q { 47 | quotes: none; 48 | } 49 | 50 | blockquote:before, blockquote:after, 51 | q:before, q:after { 52 | content: ''; 53 | content: none; 54 | } 55 | 56 | table { 57 | border-collapse: collapse; 58 | border-spacing: 0; 59 | } 60 | -------------------------------------------------------------------------------- /src/assets/styles/_root.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --nav-link: #007bff; 3 | --blue: #007bff; 4 | --indigo: #6610f2; 5 | --purple: #6f42c1; 6 | --pink: #e83e8c; 7 | --red: #dc3545; 8 | --orange: #fd7e14; 9 | --yellow: #ffc107; 10 | --green: #28a745; 11 | --teal: #20c997; 12 | --cyan: #17a2b8; 13 | --white: #fff; 14 | --gray: #868e96; 15 | --gray-dark: #343a40; 16 | --primary: #007bff; 17 | --secondary: #868e96; 18 | --success: #28a745; 19 | --info: #17a2b8; 20 | --warning: #ffc107; 21 | --danger: #dc3545; 22 | --light: #f8f9fa; 23 | --dark: #343a40; 24 | --breakpoint-xs: 0; 25 | --breakpoint-sm: 576px; 26 | --breakpoint-md: 768px; 27 | --breakpoint-lg: 992px; 28 | --breakpoint-xl: 1200px; 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/styles/client.scss: -------------------------------------------------------------------------------- 1 | // @import 'styles/_root.scss'; 2 | @import 'styles/_global.scss'; 3 | 4 | /* Fonts */ 5 | @import 'styles/_fonts.scss'; 6 | 7 | /* Vendors */ 8 | @import 'styles/_reset.scss'; 9 | 10 | /* Base style sheets */ 11 | @import 'styles/_base.scss'; 12 | 13 | /* Animations */ 14 | @import 'styles/_animation.scss'; 15 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/_button.scss: -------------------------------------------------------------------------------- 1 | @mixin button-reset { 2 | padding: 0; 3 | border: 0; 4 | font: inherit; 5 | color: inherit; 6 | background-color: transparent; 7 | cursor: pointer; 8 | 9 | &::-moz-focus-inner { 10 | border: none; 11 | } 12 | } 13 | 14 | %button { 15 | @include button-reset; 16 | margin: 0; 17 | text-align: center; 18 | white-space: nowrap; 19 | vertical-align: middle; 20 | overflow: visible; 21 | position: relative; 22 | user-select: none; 23 | text-decoration: none; 24 | display: inline-block; 25 | appearance: none; 26 | transition: background-color $a-time $a-func, border-color $a-time $a-func, color $a-time $a-func; 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/_clearfix.scss: -------------------------------------------------------------------------------- 1 | %clearfix { 2 | zoom: 1; 3 | 4 | &:after { 5 | clear: both; 6 | } 7 | 8 | &:before, 9 | &:after { 10 | content: ''; 11 | display: table; 12 | line-height: 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/_fluid.scss: -------------------------------------------------------------------------------- 1 | // ---- 2 | // libsass (v3.5.0.beta.2) 3 | // ---- 4 | 5 | // ========================================================================= 6 | // 7 | // PRECISE CONTROL OVER RESPONSIVE TYPOGRAPHY FOR SASS 8 | // --------------------------------------------------- 9 | // Indrek Paas @indrekpaas 10 | // 11 | // Inspired by Mike Riethmuller's Precise control over responsive typography 12 | // http://madebymike.com.au/writing/precise-control-responsive-typography/ 13 | // 14 | // Borrowed `strip-unit` function from Hugo Giraudel 15 | // https://css-tricks.com/snippets/sass/strip-unit-function/ 16 | // 17 | // 02.04.2018 Remove `screen` keyword from media queries 18 | // 11.08.2016 Remove redundant `&` self-reference 19 | // 31.03.2016 Remove redundant parenthesis from output 20 | // 02.10.2015 Add support for multiple properties 21 | // 24.04.2015 Initial release 22 | // 23 | // ========================================================================= 24 | 25 | /* stylelint-disable */ 26 | @mixin fluid-type($properties, $min-vw, $max-vw, $min_value, $max_value) { 27 | @each $property in $properties { 28 | #{$property}: $min_value; 29 | } 30 | 31 | @media (min-width : $min-vw) { 32 | @each $property in $properties { 33 | #{$property}: calc( 34 | #{$min_value} + #{strip-unit($max_value - $min_value)} * (100vw - #{$min-vw}) / #{strip-unit( 35 | $max-vw - $min-vw 36 | )} 37 | ); 38 | } 39 | } 40 | 41 | @media (min-width : $max-vw) { 42 | @each $property in $properties { 43 | #{$property}: $max_value; 44 | } 45 | } 46 | } 47 | 48 | @function strip-unit($number) { 49 | @if type-of($number) == 'number' and not unitless($number) { 50 | @return $number / ($number * 0 + 1); 51 | } 52 | 53 | @return $number; 54 | } 55 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/_fontface.scss: -------------------------------------------------------------------------------- 1 | @mixin font-face($font-family, $file-filename, $font-weight: normal, $font-style: normal, $font-stretch : normal) { 2 | @font-face { 3 | font-family: quote($font-family); 4 | font-weight: $font-weight; 5 | font-style: $font-style; 6 | font-stretch: $font-stretch; 7 | font-display: optional; 8 | src: url($file-filename + '.eot'); 9 | src: url($file-filename + '.woff2') format('woff2'), url($file-filename + '.woff') format('woff'); 10 | } 11 | } 12 | 13 | /* 14 | 100 - Thin 15 | 200 - Extra Light (Ultra Light) 16 | 300 - Light 17 | 400 - Normal 18 | 500 - Medium 19 | 600 - Semi Bold (Demi Bold) 20 | 700 - Bold 21 | 800 - Extra Bold (Ultra Bold) 22 | 900 - Black (Heavy) 23 | 24 | 'extrathin': 50, 25 | 'hairline': 50, 26 | 'ultrathin': 50, 27 | 'thin': 100, 28 | 'extralight': 200, 29 | 'ultralight': 200, 30 | 'light': 300, 31 | 'book': 400, 32 | 'normal': 400, 33 | 'plain': 400, 34 | 'regular': 400, 35 | 'medium': 500, 36 | 'demibold': 600, 37 | 'semibold': 600, 38 | 'bold': 700, 39 | 'extrabold': 800, 40 | 'heavy': 800, 41 | 'ultrabold': 800, 42 | 'black': 900, 43 | 'extrablack': 950, 44 | 'fat': 950, 45 | 'poster': 950, 46 | 'ultrablack': 950 47 | */ 48 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/_fontsize.scss: -------------------------------------------------------------------------------- 1 | @mixin fontsize($min_font, $max_font, $min_width: 480, $max_width: 1000) { 2 | font-size: #{$min_font}px; 3 | 4 | @media (min-width : #{$min_width}px) and (max-width : #{$max_width}px) { 5 | .navigation__link { 6 | font-size: calc((#{$min_font} * 1px) + (#{$max_font} - #{$min_font}) * ( (100vw - #{$min_width}px) / (#{$max_width} - #{$min_width}))); 7 | } 8 | } 9 | 10 | @media (min-width : #{$max_width}px) { 11 | font-size: #{$max_font}px; 12 | } 13 | } 14 | 15 | /* 16 | :root { font-size: #{$min_font}px; } 17 | 18 | @media (min-width: #{$min_width}px) and (max-width: #{$max_width}px) { 19 | :root { 20 | font-size: calc(#{$min_font}px + (#{$max_font} - #{$min_font}) * ( (100vw - #{$min_width}px) / ( #{$max_width} - #{$min_width}))); 21 | } 22 | } 23 | 24 | @media (min-width: #{$max_width}px) { 25 | :root { 26 | font-size: #{$max_font}px; 27 | } 28 | } 29 | */ 30 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/_layout.scss: -------------------------------------------------------------------------------- 1 | %layout { 2 | width: 100%; 3 | height: 100%; 4 | max-width: $layout-width; 5 | padding-left: $layout-margin; 6 | padding-right: $layout-margin; 7 | margin-left: auto; 8 | margin-right: auto; 9 | position: relative; 10 | // outline: 1px solid #0FF; 11 | 12 | @media (--tablet) { 13 | padding-left: 10px; 14 | padding-right: 10px; 15 | } 16 | 17 | @media (--desktop) { 18 | padding-left: 20px; 19 | padding-right: 20px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/_multi-line-ellipsis.scss: -------------------------------------------------------------------------------- 1 | @mixin multiLineEllipsis($lineHeight: 1.2em, $lineCount: 1, $bgColor: #fff) { 2 | overflow: hidden; 3 | position: relative; 4 | line-height: $lineHeight; 5 | max-height: $lineHeight * $lineCount; 6 | text-align: justify; 7 | margin-right: -1em; 8 | padding-right: 1em; 9 | 10 | &:before { 11 | content: '...'; 12 | position: absolute; 13 | right: 0; 14 | bottom: 0; 15 | } 16 | 17 | &:after { 18 | content: ''; 19 | position: absolute; 20 | right: 0; 21 | width: 1em; 22 | height: 1em; 23 | margin-top: .2em; 24 | background: $bgColor; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/_outline.scss: -------------------------------------------------------------------------------- 1 | @mixin outline($color, $shadow-size: 2, $box-shadow: true, $text-shadow: false, $add-shadow: '') { 2 | outline: 0; 3 | 4 | &:focus { 5 | @if $box-shadow == true { 6 | box-shadow: 0 0 0 $shadow-size #fff, 0 0 0 $shadow-size + $shadow-size $color $add-shadow; 7 | } 8 | 9 | @if $text-shadow == true { 10 | text-shadow: 1px 1px 0 $color; 11 | } 12 | 13 | @else { 14 | box-shadow: 0 0 0 $shadow-size rgba($color, .5) $add-shadow; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/assets/svgstore/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/assets/svgstore/.gitkeep -------------------------------------------------------------------------------- /src/assets/svgstore/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgstore/favorite.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/client.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { render } from 'react-dom' 3 | import { Provider } from 'mobx-react' 4 | // import * as serviceWorker from './serviceWorker' 5 | 6 | import { RouterStore, syncHistoryWithStore } from 'mobx-react-router' 7 | 8 | import App from './app' 9 | 10 | import { createStore } from 'store' 11 | import { environment } from 'settings' 12 | import { browserHistory } from 'helpers' 13 | 14 | const routingStore = new RouterStore() 15 | 16 | const store = createStore(routingStore) 17 | 18 | const history = syncHistoryWithStore(browserHistory, routingStore) 19 | 20 | const appRoot = document.getElementById('app') 21 | 22 | if (appRoot == null) { 23 | throw new Error('No root element') 24 | } 25 | 26 | const renderApp = () => { 27 | render( 28 | 29 | 30 | , 31 | appRoot 32 | ) 33 | document.body.classList.remove('loading') 34 | } 35 | 36 | if (environment.development && module.hot) { 37 | module.hot.accept('./app', renderApp) 38 | } else if (environment.production) { 39 | const isHttps = location.protocol.indexOf('https') >= 0 40 | 41 | if ('serviceWorker' in navigator && isHttps) { 42 | navigator.serviceWorker.register('/sw.js') 43 | } 44 | } 45 | 46 | renderApp() 47 | 48 | // If you want your app to work offline and load faster, you can change 49 | // unregister() to register() below. Note this comes with some pitfalls. 50 | // Learn more about service workers: http://bit.ly/CRA-PWA 51 | // serviceWorker.unregister() 52 | -------------------------------------------------------------------------------- /src/components/async-component/index.tsx: -------------------------------------------------------------------------------- 1 | // import * as React from 'react' 2 | 3 | // export interface S { 4 | // Component: any; 5 | // } 6 | 7 | // export const AsyncComponent = (loader: any, collection: any) => 8 | // (class AsyncComponent extends React.Component<{}, S> { 9 | // state = { 10 | // Component: null, 11 | // } 12 | 13 | // componentDidMount() { 14 | // if (!this.state.Component) { 15 | // loader().then((Component: any) => { 16 | // this.setState({ Component }) 17 | // }) 18 | // } 19 | // } 20 | 21 | // render() { 22 | // const { Component } = this.state 23 | 24 | // if (Component) { 25 | // return ( 26 | // // @ts-ignore 27 | // 28 | // ) 29 | // } 30 | 31 | // return null 32 | // } 33 | // }) 34 | -------------------------------------------------------------------------------- /src/components/badge/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | // import * as css from './styles.scss' 3 | 4 | import styled from 'styled-components' 5 | 6 | export interface P { 7 | title?: string; 8 | bolded?: boolean; 9 | variant?: 'info' | 'normal' | 'primary' | 'success' | 'warning' | 'danger'; 10 | children?: JSX.Element[] | JSX.Element | string; 11 | className?: string; 12 | } 13 | 14 | // color: ${props => props.variant}; 15 | // border: 1px solid ${props => props.variant}; 16 | // background-color: ${props => props.variant}; 17 | 18 | // background-color: ${props => 19 | // (props.variant === 'primary' && '$primary') || 20 | // (props.variant === 'danger' && '$danger') 21 | // }; 22 | 23 | const StyledBadge = styled.div` 24 | padding: 3px; 25 | font-size: 1.3rem; 26 | line-height: 1; 27 | border-radius: 2px; 28 | display: inline-block; 29 | vertical-align: baseline; 30 | font-weight: ${(props: P) => (props.bolded ? '700' : '400')}; 31 | color: $c-primary-color; 32 | border: 1px solid $c-info-border; 33 | background-color: $c-info-background; 34 | ` 35 | 36 | export default class Badge extends React.PureComponent { 37 | 38 | static defaultProps = { 39 | title: '', 40 | variant: 'normal', 41 | className: '', 42 | } 43 | 44 | render () { 45 | const { title, className, children, ...rest } = this.props 46 | 47 | return ( 48 | 49 | {title || children} 50 | 51 | ) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/components/badge/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .badge { 4 | line-height: 1; 5 | font-size: 1.3rem; 6 | border-radius: 2px; 7 | padding: 3px; 8 | display: inline-block; 9 | vertical-align: baseline; 10 | } 11 | 12 | .bold { 13 | font-weight: 700; 14 | } 15 | 16 | .info { 17 | color: $c-info-color; 18 | border: 1px solid $c-info-border; 19 | background-color: $c-info-background; 20 | } 21 | 22 | .normal { 23 | color: $c-normal-color; 24 | border: 1px solid $c-normal-border; 25 | background-color: $c-normal-background; 26 | } 27 | 28 | .primary { 29 | color: $c-primary-color; 30 | border: 1px solid $c-primary-border; 31 | background-color: $c-primary-background; 32 | } 33 | 34 | .success { 35 | color: $c-success-color; 36 | border: 1px solid $c-success-border; 37 | background-color: $c-success-background; 38 | } 39 | 40 | .danger { 41 | color: $c-danger-color; 42 | border: 1px solid $c-danger-border; 43 | background-color: $c-danger-background; 44 | } 45 | 46 | .warning { 47 | color: $c-warning-color; 48 | border: 1px solid $c-warning-border; 49 | background-color: $c-warning-background; 50 | } 51 | -------------------------------------------------------------------------------- /src/components/button/index.test.js: -------------------------------------------------------------------------------- 1 | // import * as React from 'react' 2 | // import { Button } from './' 3 | // import renderer from 'react-test-renderer' 4 | 5 | // it('отображается корректно', () => { 6 | // const component = renderer.create() 7 | // const tree = component.toJSON() 8 | // expect(tree).toMatchSnapshot() 9 | 10 | // // tree.props.onMouseEnter() 11 | 12 | // // tree = component.toJSON() 13 | // // expect(tree).toMatchSnapshot() 14 | 15 | // // tree.props.onMouseLeave() 16 | // // tree = component.toJSON() 17 | // // expect(tree).toMatchSnapshot() 18 | // }) 19 | 20 | test('test', () => { }) 21 | -------------------------------------------------------------------------------- /src/components/button/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | export type P = { 7 | type?: string; 8 | size?: 'small' | 'normal' | 'large'; 9 | label?: string; 10 | icon?: boolean; 11 | info?: boolean; 12 | danger?: boolean; 13 | normal?: boolean; 14 | success?: boolean; 15 | primary?: boolean; 16 | warning?: boolean; 17 | circle?: boolean; 18 | loading?: boolean; 19 | disabled?: boolean; 20 | className?: string; 21 | children?: JSX.Element[] | JSX.Element | string; 22 | handleClick?: () => void | boolean; 23 | } 24 | 25 | const cx = Classes.bind(css) 26 | 27 | export default function Button ({ type, size = 'small', circle, label, loading, children, handleClick, ...rest }: P) { 28 | const { 29 | info, 30 | danger, 31 | normal, 32 | success, 33 | primary, 34 | warning, 35 | } = rest 36 | 37 | return ( 38 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/components/button/input.styled.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import styled, { css } from 'styled-components' 3 | 4 | const StyledButton = styled.button` 5 | background: transparent; 6 | border-radius: 3px; 7 | border: 2px solid palevioletred; 8 | color: palevioletred; 9 | outline: 0; 10 | cursor: pointer; 11 | margin: 0.5em 1em; 12 | padding: 0.25em 1em; 13 | 14 | &:hover { 15 | border-color: #f00; 16 | background-color: #f00; 17 | } 18 | 19 | ${(props: any) => props.primary && css` 20 | background: palevioletred; 21 | color: white; 22 | `} 23 | ` 24 | 25 | const Container = styled.div` 26 | text-align: center; 27 | ` 28 | 29 | export type P = { 30 | type?: string; 31 | size?: 'small' | 'normal' | 'large'; 32 | label?: string; 33 | icon?: boolean; 34 | info?: boolean; 35 | danger?: boolean; 36 | normal?: boolean; 37 | success?: boolean; 38 | primary?: boolean; 39 | warning?: boolean; 40 | circle?: boolean; 41 | loading?: boolean; 42 | disabled?: boolean; 43 | className?: string; 44 | children?: JSX.Element[] | JSX.Element | string; 45 | handleClick?: () => void | boolean; 46 | } 47 | 48 | export default function Button ({ type, size = 'small', circle, label, loading, children, handleClick, ...rest }: P) { 49 | return ( 50 | 51 | 56 | {label || children} 57 | 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /src/components/button/styles.ts: -------------------------------------------------------------------------------- 1 | // const Button = styled.a` 2 | // /* This renders the buttons above... Edit me! */ 3 | // display: inline-block; 4 | // border-radius: 3px; 5 | // padding: 0.5rem 0; 6 | // margin: 0.5rem 1rem; 7 | // width: 11rem; 8 | // background: transparent; 9 | // color: white; 10 | // border: 2px solid white; 11 | 12 | // /* The GitHub button is a primary button 13 | // * edit this to target it specifically! */ 14 | // ${props => props.primary && css` 15 | // background: white; 16 | // color: palevioletred; 17 | // `} 18 | // ` 19 | 20 | // render( 21 | //
22 | // 30 | 31 | // 34 | //
35 | // ) 36 | -------------------------------------------------------------------------------- /src/components/card/index.test.js: -------------------------------------------------------------------------------- 1 | // import * as React from 'react' 2 | // import { Card } from './' 3 | // import renderer from 'react-test-renderer' 4 | 5 | // it('отображается корректно', () => { 6 | // const component = renderer.create(test) 7 | // const tree = component.toJSON() 8 | // expect(tree).toMatchSnapshot() 9 | // }) 10 | 11 | test('test', () => { }) 12 | -------------------------------------------------------------------------------- /src/components/card/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | import { P } from './types' 7 | 8 | const cx = Classes.bind(css) 9 | 10 | export default function Card ({ children, className = '' }: P) { 11 | return ( 12 |
13 | {children} 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/card/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .card { 4 | padding: 15px; 5 | position: relative; 6 | text-align: left; 7 | background-color: $c-white; 8 | border: 1px solid $c-border; 9 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05); 10 | transition: border-color .3s ease-in-out, box-shadow .3s ease-in-out; 11 | 12 | // &:hover { 13 | // border-color: #8da60d; 14 | // box-shadow: 0 2px 4px 0 rgba(0,0,0,.1); 15 | // } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/card/types.ts: -------------------------------------------------------------------------------- 1 | export interface P { 2 | children?: JSX.Element | JSX.Element[] | string; 3 | className?: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/circular-progress/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | export interface P { 7 | color: string; 8 | stroke: number; 9 | radius?: number; 10 | content?: string | number; 11 | progress: number; 12 | } 13 | 14 | const cx = Classes.bind(css) 15 | 16 | export default function CircularProgress ({ color, radius = 60, stroke, content, progress }: P) { 17 | const normalizedRadius = radius - stroke * 2 18 | const circumference = normalizedRadius * 2 * Math.PI 19 | 20 | const strokeDashoffset = circumference - progress / 100 * circumference 21 | 22 | return ( 23 |
24 |
{content}
25 | 26 | 30 | 41 | 42 |
43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/components/circular-progress/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .progress { 4 | position: relative; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | border: 1px solid lime; 9 | } 10 | 11 | .value { 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | position: absolute 0; 16 | margin: auto; 17 | color: $c-black; 18 | opacity: .6; 19 | font-size: 1.2rem; 20 | } 21 | 22 | .circle { 23 | transition: stroke-dashoffset .35s; 24 | transform: rotate(-90deg); 25 | transform-origin: 50% 50%; 26 | } 27 | -------------------------------------------------------------------------------- /src/components/dynamic-import/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export interface P { 4 | load: any; 5 | children?: any; 6 | } 7 | 8 | export interface S { 9 | component: any; 10 | } 11 | 12 | export default class DynamicImport extends React.Component { 13 | 14 | state = { 15 | component: null, 16 | } 17 | 18 | componentDidMount () { 19 | this.props.load().then((component: any) => { 20 | this.setState(() => ({ 21 | component: component.default ? component.default : component, 22 | })) 23 | }) 24 | } 25 | 26 | render () { 27 | const { children } = this.props 28 | const { component } = this.state 29 | 30 | return children && children(component) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/components/error-boundary/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import styled from 'styled-components' 3 | 4 | export interface Props { 5 | children?: JSX.Element | JSX.Element[]; 6 | } 7 | 8 | export interface State { 9 | error: boolean; 10 | info: any; 11 | } 12 | 13 | const StyledError = styled.div` 14 | background: #f00; 15 | border-radius: 3px; 16 | border: 2px solid palevioletred; 17 | color: #fff; 18 | outline: 0; 19 | cursor: pointer; 20 | margin: 0.5em 1em; 21 | padding: 0.25em 1em; 22 | ` 23 | 24 | const StyledDetails = styled.details` 25 | whiteSpace: 'pre-wrap'; 26 | ` 27 | 28 | class ErrorBoundary extends React.PureComponent { 29 | 30 | state = { 31 | error: false, 32 | info: null, 33 | } 34 | 35 | componentDidCatch (error: Error | boolean, info: unknown) { 36 | this.setState({ 37 | error, 38 | info, 39 | }) 40 | } 41 | 42 | render () { 43 | const { error, info } = this.state 44 | 45 | if (error) { 46 | return ( 47 | 48 |

Something went wrong.

49 | 50 | 51 | {JSON.stringify(info)} 52 | 53 |
54 | ) 55 | } 56 | 57 | return this.props.children 58 | } 59 | 60 | } 61 | 62 | export default ErrorBoundary 63 | -------------------------------------------------------------------------------- /src/components/form/i.tsx: -------------------------------------------------------------------------------- 1 | // import * as React from 'react' 2 | 3 | // export default class FormSimple extends React.Component { 4 | 5 | // values = { 6 | // email: '', 7 | // password: '', 8 | // } 9 | 10 | // handleChange = e => { 11 | // const { id, value } = e.target 12 | // this.values[id] = value 13 | // } 14 | 15 | // handleSubmit = e => { 16 | // e.preventDefault() 17 | // alert('SUCCESS: ' + JSON.stringify(this.values)) 18 | // } 19 | 20 | // render() { 21 | 22 | // return ( 23 | //
24 | // 25 | //
26 | // 27 | //
28 | // 29 | //
30 | // ) 31 | // } 32 | // } 33 | -------------------------------------------------------------------------------- /src/components/icon/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | export interface PropsIcon { 7 | hidden?: boolean; 8 | symbol?: string; 9 | className?: string; 10 | } 11 | 12 | const cx = Classes.bind(css) 13 | 14 | export default function Icon ({ symbol = '', hidden = false, className = '' }: PropsIcon) { 15 | return ( 16 | 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/icon/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .icon { 4 | fill: currentColor; 5 | color: inherit; 6 | line-height: 1; 7 | max-width: 100%; 8 | max-height: 100%; 9 | display: inline-block; 10 | vertical-align: middle; 11 | shape-rendering: geometricPrecision; 12 | transition: color $a-micro-time $a-func; 13 | 14 | &:not(:root) { 15 | overflow: hidden; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Icon } from './icon' 2 | export { default as Post } from './post' 3 | export { default as Card } from './card' 4 | export { default as Title } from './title' 5 | export { default as Input } from './input' 6 | export { default as Button } from './button/input.styled' 7 | export { default as Spinner } from './spinner' 8 | export { default as Picture } from './picture' 9 | export { default as Quantity } from './quantity' 10 | export { default as Sandwich } from './sandwich' 11 | export { default as ErrorBoundary } from './error-boundary' 12 | export { default as CircularProgress } from './circular-progress' 13 | -------------------------------------------------------------------------------- /src/components/input/assets/clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/label/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export default function Label ({ label }: { label: string }) { 4 | return ( 5 |

{label}

6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/components/link/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | export interface P { 7 | href: string; 8 | children: JSX.Element[] | JSX.Element | any; 9 | className: string; 10 | } 11 | 12 | const cx = Classes.bind(css) 13 | 14 | export default function Link ({ href, children = '', className = '', ...props }: P) { 15 | return ( 16 | 17 | {children} 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/components/link/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .link { 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/loader/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | export default function Loader () { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/components/loader/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | $a-animation-time: 1.4s; 4 | $a-animation-bounce: $a-animation-time / 2; 5 | 6 | @keyframes bounce-delay { 7 | 0%, 8 | 80%, 9 | 100% { 10 | transform: scale(0); 11 | } 12 | 13 | 40% { 14 | transform: scale(1); 15 | } 16 | } 17 | 18 | .preloader { 19 | margin: 0 auto; 20 | text-align: center; 21 | 22 | &:not(:last-child) { 23 | margin-bottom: 5px; 24 | } 25 | 26 | &__bullet { 27 | width: 14px; 28 | height: 14px; 29 | margin: 0 2px; 30 | border-radius: 100%; 31 | display: inline-block; 32 | background-color: $c-cobalt; 33 | animation-name: bounce-delay; 34 | animation-duration: $a-animation-time; 35 | animation-iteration-count: infinite; 36 | animation-timing-function: ease-in-out; 37 | animation-fill-mode: both; 38 | 39 | &:nth-child(1) { 40 | animation-delay: - $a-animation-bounce / 2; 41 | } 42 | 43 | &:nth-child(2) { 44 | animation-delay: - $a-animation-bounce / 3; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/logo/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/logo/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { NavLink } from 'react-router-dom' 5 | 6 | export interface P { 7 | link: string 8 | } 9 | 10 | export default class Logo extends React.PureComponent { 11 | 12 | static defaultProps = { 13 | link: '/', 14 | } 15 | 16 | render () { 17 | const { link } = this.props 18 | 19 | return ( 20 | 21 | 22 | React Starter 23 | 24 | ) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/components/logo/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .logo { 4 | width: 48px; 5 | height: 48px; 6 | display: block; 7 | line-height: 48px; 8 | text-align: center; 9 | position: relative; 10 | } 11 | 12 | .image { 13 | width: 28px; 14 | height: 28px; 15 | display: inline-block; 16 | vertical-align: middle; 17 | background-image: url(components/logo/assets/logo.svg); 18 | background-repeat: no-repeat; 19 | background-position: 50% 50%; 20 | background-size: 28px 28px; 21 | } 22 | 23 | .title { 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | text-indent: -1000px; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/modal/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | /* body { 4 | overflow: hidden; 5 | } 6 | 7 | .wrap { 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | height: 100%; 13 | overflow-y: auto; 14 | background-color: rgba(230, 230, 230, .1); 15 | } 16 | 17 | .popup { 18 | position: absolute; 19 | width: 400px; 20 | height: 300px; 21 | right: 0; 22 | left: 0; 23 | top: 0; 24 | bottom: 0; 25 | margin: auto; 26 | } */ 27 | -------------------------------------------------------------------------------- /src/components/notification/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | export interface P { 5 | children?: JSX.Element[] | JSX.Element | any; 6 | } 7 | 8 | export default function Notification ({ children }: P) { 9 | // if (location.protocol.indexOf('https') >= 0) { 10 | // Notification.requestPermission((permission) => { 11 | // console.log('Результат запроса прав:', permission) 12 | 13 | // const notification: any = new Notification('Ответ?', 14 | // { body: 'Только ты!', dir: 'auto', icon: 'icon.jpg' } 15 | // ) 16 | 17 | // notification.onshow = () => { console.log('onshow') } 18 | // notification.onclick = () => { console.log('click') } 19 | // notification.onerror = () => { console.log('onerror') } 20 | // }) 21 | // } 22 | 23 | return
{children}
24 | } 25 | 26 | export default Notification 27 | -------------------------------------------------------------------------------- /src/components/pagination/assets/next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/pagination/assets/prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/pagination/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .nav { 4 | font-size: 0; 5 | display: flex; 6 | flex-wrap: wrap; 7 | align-items: center; 8 | justify-content: center; 9 | margin-top: 15px; 10 | margin-bottom: 10px; 11 | } 12 | 13 | .btn, 14 | .ellipse { 15 | margin: 0 5px; 16 | height: 28px; 17 | outline: 0; 18 | border: 0; 19 | font-size: 1.4rem; 20 | font-weight: 400; 21 | line-height: 28px; 22 | } 23 | 24 | .ellipse { 25 | color: $c-text-color; 26 | } 27 | 28 | .btn { 29 | padding: 0 5px; 30 | color: $c-jelly-bean; 31 | cursor: pointer; 32 | background: none; 33 | 34 | &.active { 35 | color: $c-text-color; 36 | font-weight: 500; 37 | } 38 | } 39 | 40 | .control { 41 | width: 28px; 42 | height: 28px; 43 | border: 0; 44 | outline: 0; 45 | padding: 0; 46 | font-size: 0; 47 | cursor: pointer; 48 | line-height: 28px; 49 | text-align: center; 50 | background-color: transparent; 51 | background-repeat: no-repeat; 52 | background-position: 50% 50%; 53 | background-size: 10px 19px; 54 | 55 | &:disabled { 56 | cursor: default; 57 | opacity: .4; 58 | pointer-events: none; 59 | } 60 | 61 | &.prev { 62 | background-image: url(components/pagination/assets/prev.svg); 63 | } 64 | 65 | &.next { 66 | background-image: url(components/pagination/assets/next.svg); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/picture/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .image { 4 | font-size: 0; 5 | display: inline-block; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/picture/types.ts: -------------------------------------------------------------------------------- 1 | export interface P { 2 | image: any; 3 | lqip?: string; 4 | width?: number; 5 | height?: number; 6 | title?: string; 7 | alt?: string; 8 | className?: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/post/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Skeleton from 'react-loading-skeleton' 3 | 4 | export default class Post extends React.Component<{}, {}> { 5 | 6 | static defaultProps = {} 7 | 8 | componentDidMount () { 9 | console.log(this.props) 10 | } 11 | 12 | render () { 13 | return ( 14 |
15 |

16 | 17 |
18 | ) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/components/quantity/present.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | import { Classes } from 'helpers' 4 | 5 | export interface P { 6 | value: string; 7 | } 8 | 9 | const cx = Classes.bind(css) 10 | 11 | export default function Present ({ value }: P) { 12 | return ( 13 |
14 |

value: {value}

15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/quantity/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .quantity { 4 | width: 100%; 5 | max-width: 180px; 6 | height: 28px; 7 | position: relative; 8 | user-select: none; 9 | display: inline-block; 10 | vertical-align: middle; 11 | padding: 0 32px; 12 | } 13 | 14 | .count { 15 | width: 100%; 16 | height: 28px; 17 | color: #000; 18 | padding: 0; 19 | outline: 0; 20 | padding-left: 14px; 21 | font-size: 1.3rem; 22 | font-weight: 400; 23 | border-radius: 2px; 24 | background: none; 25 | text-align: center; 26 | border: 1px solid #ddd; 27 | 28 | &:focus { 29 | box-shadow: 0 0 0 2px rgba($c-focus-color, .75); 30 | } 31 | } 32 | 33 | .control { 34 | width: 24px; 35 | height: 24px; 36 | color: #fff; 37 | padding: 0; 38 | border: 0; 39 | outline: 0; 40 | line-height: 24px; 41 | margin-top: -12px; 42 | position: absolute; 43 | top: 50%; 44 | cursor: pointer; 45 | text-align: center; 46 | border-radius: 50%; 47 | background-color: #708090; 48 | 49 | &:focus { 50 | box-shadow: 0 0 0 2px rgba($c-focus-color, .75); 51 | } 52 | } 53 | 54 | .decrease { 55 | left: 0; 56 | } 57 | 58 | .increase { 59 | right: 0; 60 | } 61 | -------------------------------------------------------------------------------- /src/components/radio-group/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export interface P { 4 | name: string; 5 | checked?: boolean | string | number; 6 | children?: JSX.Element[] | JSX.Element | string; 7 | handleChange?: (value: number | string | boolean) => void | boolean; 8 | } 9 | 10 | export interface S { 11 | checked: boolean | string | number; 12 | } 13 | 14 | export default class RadioGroup extends React.Component { 15 | 16 | static defaultProps = { 17 | checked: false, 18 | handleChange: (value: string | number) => { 19 | console.log('check radio: = ', value) 20 | }, 21 | } 22 | 23 | state = { 24 | checked: this.props.checked || false, 25 | } 26 | 27 | handleChange = (checked: boolean | string | number) => this.setState({ checked }) 28 | 29 | render () { 30 | const { checked } = this.state 31 | const { name, children } = this.props 32 | 33 | const items: any = React.Children.map(children, (radio: any) => 34 | React.cloneElement(radio, { 35 | name, 36 | checked: checked && checked === radio.props.value, 37 | handleChange: this.handleChange, 38 | }) 39 | ) 40 | 41 | return {items} 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/components/radio/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | export interface P { 5 | name: string; 6 | label?: string; 7 | checked?: boolean; 8 | className?: string; 9 | value?: string | number; 10 | children?: JSX.Element[] | JSX.Element | string; 11 | handleChange?: (value: number | string | boolean) => void | boolean; 12 | } 13 | 14 | export interface S { 15 | checked: any; 16 | } 17 | 18 | export default class Radio extends React.PureComponent { 19 | 20 | static defaultProps = { 21 | label: '', 22 | value: '', 23 | checked: false, 24 | className: '', 25 | handleChange: (value: number | string | boolean) => { 26 | console.log('check radio: = ', value) 27 | }, 28 | } 29 | 30 | state = { 31 | checked: false, 32 | } 33 | 34 | static getDerivedStateFromProps (props: P, state: S) { 35 | if (state.checked !== props.checked) { 36 | return { 37 | checked: props.checked, 38 | } 39 | } 40 | 41 | return null 42 | } 43 | 44 | handleChange = (e: React.SyntheticEvent) => { 45 | const checked = e.target.value 46 | 47 | this.setState( 48 | (state: S) => { 49 | return { 50 | checked: !state.checked, 51 | } 52 | }, 53 | () => { 54 | if (this.props.handleChange) { 55 | this.props.handleChange(checked) 56 | } 57 | } 58 | ) 59 | } 60 | 61 | render () { 62 | const { checked } = this.state 63 | const { name, value, label, children } = this.props 64 | 65 | const id = `radio_${name}_${value}` 66 | 67 | const props: any = { 68 | id, 69 | type: 'radio', 70 | name, 71 | value, 72 | checked, 73 | className: css.input, 74 | onChange: this.handleChange, 75 | } 76 | 77 | return ( 78 | 83 | ) 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/components/radio/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .radio { 4 | font-size: 0; 5 | cursor: pointer; 6 | color: $c-black; 7 | position: relative; 8 | display: inline-block; 9 | vertical-align: middle; 10 | transition: color .1s ease; 11 | } 12 | 13 | .input { 14 | position: absolute; 15 | top: -15px; 16 | left: -15px; 17 | width: 0; 18 | height: 0; 19 | opacity: 0; 20 | visibility: hidden; 21 | } 22 | 23 | .label { 24 | color: $c-black; 25 | font-size: 1.4rem; 26 | line-height: 1.15; 27 | padding-left: 26px; 28 | position: relative; 29 | display: inline-block; 30 | vertical-align: middle; 31 | 32 | &:after, 33 | &:before { 34 | content: ''; 35 | position: absolute; 36 | } 37 | 38 | &:before { 39 | top: 0; 40 | left: 0; 41 | width: 16px; 42 | height: 16px; 43 | border-radius: 16px; 44 | background-color: $c-white; 45 | border: solid 1px $c-whisper; 46 | } 47 | 48 | &:after { 49 | position: absolute; 50 | top: 4px; 51 | left: 4px; 52 | content: ''; 53 | width: 8px; 54 | height: 8px; 55 | opacity: 0; 56 | border-radius: 8px; 57 | background-color: $c-cobalt; 58 | transition: opacity $a-micro-time $a-func; 59 | } 60 | } 61 | 62 | .input:checked + .label { 63 | &:before { 64 | border-color: $c-cobalt; 65 | } 66 | 67 | &:after { 68 | opacity: 1; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/components/sandwich/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | export interface P { 7 | isOpened?: boolean; 8 | } 9 | 10 | export interface S { 11 | isOpened: boolean; 12 | } 13 | 14 | const cx = Classes.bind(css) 15 | 16 | export default class Sandwich extends React.PureComponent { 17 | 18 | static defaultProps = { 19 | isOpened: false, 20 | } 21 | 22 | state = { 23 | isOpened: false, 24 | } 25 | 26 | static getDerivedStateFromProps (props: P, state: S) { 27 | if (state.isOpened !== props.isOpened) { 28 | return { 29 | isOpened: props.isOpened, 30 | } 31 | } 32 | 33 | return null 34 | } 35 | 36 | handleChange = () => { 37 | this.setState((state: S) => ({ 38 | isOpened: !state.isOpened, 39 | })) 40 | } 41 | 42 | render () { 43 | const { isOpened } = this.state 44 | 45 | return ( 46 | 49 | ) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/components/sandwich/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .sandwich { 4 | width: 42px; 5 | height: 38px; 6 | outline: 0; 7 | cursor: pointer; 8 | position: relative; 9 | background-color: #fff; 10 | border: 1px solid $c-gainsboro; 11 | 12 | @media (--tablet) { 13 | width: 42px; 14 | height: 38px; 15 | } 16 | } 17 | 18 | .line { 19 | top: 50%; 20 | left: 50%; 21 | transform: translateX(-50%); 22 | transition: background-color .3s ease-in-out; 23 | margin-top: -1px; 24 | 25 | @media (--tablet) { 26 | margin-top: -1px; 27 | } 28 | 29 | &:before, 30 | &:after { 31 | top: 0; 32 | left: 0; 33 | content: ''; 34 | display: block; 35 | transition: transform .3s ease-in-out; 36 | } 37 | 38 | &:before { 39 | transform: translate3d(0, -5px, 0); 40 | } 41 | 42 | &:after { 43 | transform: translate3d(0, 5px, 0); 44 | } 45 | 46 | &_open { 47 | background-color: #fff; 48 | 49 | &:before { 50 | transform: translateY(0) rotate(45deg); 51 | } 52 | 53 | &:after { 54 | transform: translateY(0) rotate(-45deg); 55 | } 56 | } 57 | 58 | &, 59 | &:before, 60 | &:after { 61 | position: absolute; 62 | width: 17px; 63 | height: 1px; 64 | background-color: $c-cobalt; 65 | 66 | @media (--tablet) { 67 | width: 17px; 68 | height: 1px; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/components/spinner/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | import { P } from './types' 7 | 8 | const cx = Classes.bind(css) 9 | 10 | export default function Spinner ({ className = '' }: P) { 11 | return
12 | } 13 | -------------------------------------------------------------------------------- /src/components/spinner/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | @keyframes bounce { 4 | 0%, 5 | 100% { 6 | transform: scale(0); 7 | } 8 | 9 | 50% { 10 | transform: scale(1); 11 | } 12 | } 13 | 14 | .spinner { 15 | width: 40px; 16 | height: 40px; 17 | margin: 0 auto; 18 | position: relative; 19 | 20 | &:before, 21 | &:after { 22 | content: ''; 23 | width: 100%; 24 | height: 100%; 25 | border-radius: 50%; 26 | background-color: #333; 27 | opacity: .6; 28 | position: absolute; 29 | top: 0; 30 | left: 0; 31 | animation: bounce 2s infinite ease-in-out; 32 | } 33 | 34 | &:after { 35 | animation-delay: -1s; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/spinner/types.ts: -------------------------------------------------------------------------------- 1 | export interface P { 2 | className?: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/svg/index.tsx: -------------------------------------------------------------------------------- 1 | // const rectangle = ( 2 | // 7 | // ) 8 | 9 | // const circle = ( 10 | // 15 | // ) 16 | 17 | // const triangle = ( 18 | // 22 | // ) 23 | 24 | // const SmallHandle = ({ coordinates, onMouseDown }) => ( 25 | // 36 | // ) 37 | 38 | // const shape = ( 39 | // 40 | // 49 | // 50 | // ); 51 | 52 | // render(shape); 53 | // render( 54 | // 58 | // {rectangle} 59 | // {circle} 60 | // {triangle} 61 | // 62 | // ) 63 | -------------------------------------------------------------------------------- /src/components/title/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | export interface P { 7 | // size: 'tiny' | 'small' | 'normal' | 'medium' | 'huge'; 8 | // type: 'primary' | 'secondary'; 9 | label?: string; 10 | center?: boolean; 11 | children?: string; 12 | className?: string; 13 | } 14 | 15 | const cx = Classes.bind(css) 16 | 17 | export default function Title ({ label = '', children = '', center = false, className = '' }: P) { 18 | return ( 19 |
20 | {label || children} 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/title/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .title { 4 | color: $c-text-color; 5 | } 6 | 7 | .center { 8 | text-align: center; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/widget/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | export interface P { 5 | title: string; 6 | className?: string; 7 | children?: JSX.Element[] | JSX.Element | string; 8 | } 9 | 10 | export default class Widget extends React.PureComponent { 11 | 12 | render () { 13 | const cn: string[] = [] 14 | const { title, children, className } = this.props 15 | 16 | cn.push(css.widget) 17 | 18 | if (className) { 19 | cn.push(className) 20 | } 21 | 22 | return ( 23 |
24 |
25 |
{title}
26 | 27 | {children} 28 |
29 |
30 | ) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/components/widget/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .widget { 4 | color: #444; 5 | font-size: 1.6rem; 6 | line-height: 1.125; 7 | position: relative; 8 | margin-bottom: 12px; 9 | 10 | &:before { 11 | width: 100%; 12 | height: 100%; 13 | content: ''; 14 | position: absolute 0; 15 | box-shadow: rgba(0, 0, 0, .15) 0 1px 2px 0; 16 | background-color: #fff; 17 | border-radius: 2px; 18 | } 19 | } 20 | 21 | .content { 22 | position: relative; 23 | z-index: 1; 24 | padding: 15px 20px; 25 | } 26 | 27 | .title { 28 | color: rgb(153, 153, 153); 29 | font-size: 1.8rem; 30 | line-height: 1.3333333333; 31 | 32 | &:not(:last-child) { 33 | margin-bottom: 15px; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/fragments/animate/animated.tsx: -------------------------------------------------------------------------------- 1 | // import {Animated} from "react-animated-css"; 2 | 3 | // 4 | //
5 | // hello world ;) 6 | //
7 | //
8 | -------------------------------------------------------------------------------- /src/fragments/animate/index.tsx: -------------------------------------------------------------------------------- 1 | // import * as React from 'react' 2 | // // import * as css from './styles.scss' 3 | 4 | // // import ReactCSSTransitionGroup from 'react-addons-css-transition-group' 5 | 6 | // export class Animate extends React.PureComponent { 7 | // state = { items: ['hello', 'world', 'click', 'me'] } 8 | 9 | // // componentWillAppear() 10 | // // componentDidAppear() 11 | // // componentWillEnter() 12 | // // componentDidEnter() 13 | // // componentWillLeave() 14 | // // componentDidLeave() 15 | 16 | // handleAdd = () => { 17 | // // const newItems = this.state.items.concat([ 18 | // // prompt('Enter some text') 19 | // // ]) 20 | // // this.setState({ items: newItems }) 21 | // } 22 | 23 | // handleRemove = (i) => { 24 | // const newItems = this.state.items.slice() 25 | // newItems.splice(i, 1) 26 | // this.setState({ items: newItems }) 27 | // } 28 | 29 | // render() { 30 | // // const items = this.state.items.map((item, i) => ( 31 | // //
this.handleRemove(i)}> 32 | // // {item} 33 | // //
34 | // // )) 35 | 36 | // // 42 | // //

Fading at Initial Mount

43 | // //
44 | 45 | // return ( 46 | //
47 | // 48 | // {/* 52 | 53 | // {items} 54 | // */} 55 | //
56 | // ) 57 | // } 58 | // } 59 | -------------------------------------------------------------------------------- /src/fragments/animate/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .example-enter { 4 | opacity: .01; 5 | } 6 | 7 | .example-enter.example-enter-active { 8 | opacity: 1; 9 | transition: opacity 500ms ease-in; 10 | } 11 | 12 | .example-leave { 13 | opacity: 1; 14 | } 15 | 16 | .example-leave.example-leave-active { 17 | opacity: .01; 18 | transition: opacity 300ms ease-in; 19 | } 20 | -------------------------------------------------------------------------------- /src/fragments/content/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | export interface P { 7 | className?: string; 8 | children?: JSX.Element[] | JSX.Element | string; 9 | } 10 | 11 | const cx = Classes.bind(css) 12 | 13 | export function Content ({ children, className = '' }: P) { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | 21 | export default Content 22 | -------------------------------------------------------------------------------- /src/fragments/content/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .content { 4 | width: 100%; 5 | flex-grow: 0; 6 | flex-shrink: 0; 7 | flex-basis: 100%; 8 | padding: $content-padding; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/fragments/dialog/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Icon, Input, Button } from 'components' 5 | 6 | export interface Props { 7 | type?: 'alert' | 'prompt' | 'confirm'; 8 | title?: string; 9 | value?: string | number; 10 | placeholder?: string; 11 | } 12 | 13 | export class Dialog extends React.PureComponent { 14 | 15 | static defaultProps = { 16 | type: 'alert', 17 | title: '', 18 | value: '', 19 | placeholder: '', 20 | } 21 | 22 | render () { 23 | const props: any = {} 24 | const { title, value, placeholder } = this.props 25 | 26 | props.value = value 27 | props.placeholder = placeholder 28 | 29 | return ( 30 |
31 | 34 | 35 |
36 |
{title}
37 | 38 |
39 | 40 |
41 | 44 | 47 |
48 |
49 | ) 50 | } 51 | 52 | } 53 | 54 | export default Dialog 55 | -------------------------------------------------------------------------------- /src/fragments/dialog/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .dialog { 4 | width: 400px; 5 | position: relative; 6 | border: 1px solid #ddd; 7 | } 8 | 9 | .close { 10 | position: absolute; 11 | top: 10px; 12 | right: 10px; 13 | width: 16px; 14 | height: 16px; 15 | padding: 0; 16 | color: $c-pink-swan; 17 | border: 0; 18 | outline: 0; 19 | line-height: 1; 20 | text-align: center; 21 | cursor: pointer; 22 | background-color: transparent; 23 | 24 | &:hover { 25 | color: $c-blue; 26 | } 27 | } 28 | 29 | .icon { 30 | width: 14px; 31 | height: 14px; 32 | } 33 | 34 | .title { 35 | font-size: 1.6rem; 36 | font-weight: 600; 37 | line-height: 1; 38 | text-align: center; 39 | margin-bottom: 20px; 40 | } 41 | 42 | .body { 43 | padding: 20px 15px; 44 | } 45 | 46 | .footer { 47 | padding: 15px; 48 | text-align: center; 49 | background-color: #efefef; 50 | } 51 | 52 | .button { 53 | margin: 0 10px; 54 | } 55 | -------------------------------------------------------------------------------- /src/fragments/fetching/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Spinner } from 'components' 5 | 6 | import { P } from './types' 7 | 8 | export function Fetching ({ pending = true, children }: P) { 9 | return
{pending ? : children}
10 | } 11 | -------------------------------------------------------------------------------- /src/fragments/fetching/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .fetch { 4 | border: 1px solid lime; 5 | } 6 | -------------------------------------------------------------------------------- /src/fragments/fetching/types.ts: -------------------------------------------------------------------------------- 1 | export interface P { 2 | pending?: boolean; 3 | children?: JSX.Element[] | JSX.Element | any; 4 | } 5 | -------------------------------------------------------------------------------- /src/fragments/group/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | import { P } from './types' 7 | 8 | const cx = Classes.bind(css) 9 | 10 | export function Group ({ type = 'grid', children = [], className = '' }: P) { 11 | let items: any = null 12 | 13 | const division = children && Object.keys(children).length % 4 14 | 15 | if (children && Object.keys(children).length) { 16 | items = React.Children.map(children, (card: any) => 17 | React.cloneElement(
{card}
, { 18 | className: cx(css.item), 19 | }) 20 | ) 21 | } 22 | 23 | return ( 24 |
{items}
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/fragments/group/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .layout { 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: space-between; 7 | } 8 | 9 | .exact { 10 | &:after { 11 | content: ''; 12 | flex: 1 10 auto; 13 | } 14 | } 15 | 16 | .grid { 17 | margin-left: -8px; 18 | margin-right: -8px; 19 | align-items: stretch; 20 | } 21 | 22 | .list { 23 | flex-direction: column; 24 | align-items: flex-start; 25 | margin-bottom: 10px; 26 | } 27 | 28 | .item { 29 | width: 20%; 30 | padding: 0 8px; 31 | margin-bottom: 16px; 32 | } 33 | -------------------------------------------------------------------------------- /src/fragments/group/types.ts: -------------------------------------------------------------------------------- 1 | export interface P { 2 | type?: 'grid' | 'list'; 3 | children?: JSX.Element[] | JSX.Element | string; 4 | className?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/fragments/header/components/index.ts: -------------------------------------------------------------------------------- 1 | export { Locale } from './locale' 2 | export { Navigation } from './navigation' 3 | -------------------------------------------------------------------------------- /src/fragments/header/components/locale/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | import { Link } from 'react-router-dom' 7 | 8 | export interface P { 9 | checked?: string; 10 | className?: string; 11 | // handleChange?: () => void | boolean; 12 | } 13 | 14 | const cx = Classes.bind(css) 15 | 16 | export function Locale ({ checked, className = '' }: P) { 17 | const list: string[] = ['ru', 'en', 'de'] 18 | 19 | return ( 20 |
21 | {list && 22 | list.map((lang: string) => ( 23 | 27 | {lang} 28 | 29 | ))} 30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/fragments/header/components/locale/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .locale { 4 | display: flex; 5 | align-items: center; 6 | justify-content: space-end; 7 | margin-left: 20px; 8 | } 9 | 10 | .link { 11 | color: #fafafa; 12 | font-size: 1.6rem; 13 | line-height: 1; 14 | white-space: nowrap; 15 | 16 | &:not(:last-child) { 17 | margin-right: 10px; 18 | } 19 | } 20 | 21 | .active { 22 | color: #ffe97c; 23 | } 24 | -------------------------------------------------------------------------------- /src/fragments/header/components/navigation/example.tsx: -------------------------------------------------------------------------------- 1 | // import React from "react"; 2 | // import { withRouter } from "react-router-dom"; 3 | 4 | // const Nav = ({ history }) => ( 5 | // 10 | // ) 11 | 12 | // export default withRouter(Nav); 13 | -------------------------------------------------------------------------------- /src/fragments/header/components/navigation/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { NavLink } from 'react-router-dom' 5 | 6 | export function Navigation () { 7 | return ( 8 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/fragments/header/components/navigation/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .navigation { 4 | width: 100%; 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-between; 8 | } 9 | 10 | .group { 11 | flex: 1 1 auto; 12 | display: flex; 13 | align-items: center; 14 | justify-content: flex-start; 15 | } 16 | 17 | .link { 18 | color: #fafafa; 19 | font-size: 1.6rem; 20 | line-height: 1; 21 | margin: 0 30px; 22 | text-decoration: underline; 23 | } 24 | 25 | .active { 26 | color: #fc0; 27 | text-decoration: none; 28 | } 29 | 30 | .code-link { 31 | color: #fafafa; 32 | font-weight: 700; 33 | font-size: 1.6rem; 34 | line-height: 1; 35 | margin-right: 15px; 36 | } 37 | -------------------------------------------------------------------------------- /src/fragments/header/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | import { 7 | Locale, 8 | Navigation, 9 | } from './components' 10 | 11 | const cx = Classes.bind(css) 12 | 13 | export function Header () { 14 | return ( 15 |
16 |
React Starter Kit
17 | 18 | 19 | 20 | 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/fragments/header/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .header { 4 | display: flex; 5 | align-items: center; 6 | justify-content: space-between; 7 | width: 100%; 8 | height: 120px; 9 | flex-grow: 0; 10 | flex-shrink: 0; 11 | flex-basis: 100%; 12 | margin-bottom: 30px; 13 | padding: $content-padding; 14 | background-color: #29323d; 15 | } 16 | 17 | .title { 18 | color: #fff; 19 | font-size: 1.8rem; 20 | margin-right: 10px; 21 | white-space: nowrap; 22 | } 23 | -------------------------------------------------------------------------------- /src/fragments/index.ts: -------------------------------------------------------------------------------- 1 | export { Group } from './group' 2 | export { Header } from './header' 3 | export { Layout } from './layout' 4 | export { Loading } from './loading' 5 | export { Fetching } from './fetching' 6 | -------------------------------------------------------------------------------- /src/fragments/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | import { Classes } from 'helpers' 4 | 5 | export interface P { 6 | className?: string; 7 | children?: JSX.Element[] | JSX.Element | string; 8 | } 9 | 10 | const cx = Classes.bind(css) 11 | 12 | export function Layout ({ children = '', className = '' }: P) { 13 | return ( 14 |
15 | {children} 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/fragments/list/index.tsx: -------------------------------------------------------------------------------- 1 | // import * as React from 'react' 2 | 3 | // export class List extends React.PureComponent<{}, {}> { 4 | // // deleteItem = indexToDelete => { 5 | // // this.setState(({ list }) => ({ 6 | // // list: list.filter((toDo, index) => index !== indexToDelete) 7 | // // })) 8 | // // } 9 | 10 | // render() { 11 | // return null 12 | // // return 13 | // } 14 | // } 15 | -------------------------------------------------------------------------------- /src/fragments/loading/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { Spinner } from 'components' 4 | 5 | import { P } from './types' 6 | 7 | export function Loading ({ error, loading, pastSpinnerDelay, timedOut, pastDelay }: P) { 8 | if (error) { 9 | return
Error!
10 | } 11 | 12 | if (loading || pastDelay || pastSpinnerDelay) { 13 | return 14 | } 15 | 16 | if (timedOut) { 17 | return
Taking a long time...
18 | } 19 | 20 | return null 21 | } 22 | -------------------------------------------------------------------------------- /src/fragments/loading/types.ts: -------------------------------------------------------------------------------- 1 | export interface P { 2 | error: any | null; 3 | pastDelay: null; 4 | loading: boolean; 5 | timedOut: boolean; 6 | pastSpinnerDelay: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/fragments/tabs/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .tab-list { 4 | border-bottom: 1px solid #ccc; 5 | padding-left: 0; 6 | } 7 | 8 | .tab-list-item { 9 | display: inline-block; 10 | list-style: none; 11 | margin-bottom: -1px; 12 | padding: .5rem .75rem; 13 | } 14 | 15 | .tab-list-active { 16 | background-color: white; 17 | border: solid #ccc; 18 | border-width: 1px 1px 0 1px; 19 | } 20 | -------------------------------------------------------------------------------- /src/fragments/tabs/tab.tsx: -------------------------------------------------------------------------------- 1 | // import React, { Component } from 'react' 2 | // import PropTypes from 'prop-types' 3 | 4 | // class Tab extends Component { 5 | // static propTypes = { 6 | // activeTab: PropTypes.string.isRequired, 7 | // label: PropTypes.string.isRequired, 8 | // onClick: PropTypes.func.isRequired 9 | // } 10 | 11 | // onClick = () => { 12 | // const { label, onClick } = this.props 13 | // onClick(label) 14 | // } 15 | 16 | // render() { 17 | // const { onClick, props: { activeTab, label } } = this 18 | 19 | // let className = 'tab-list-item' 20 | 21 | // if (activeTab === label) { 22 | // className += ' tab-list-active' 23 | // } 24 | 25 | // return ( 26 | //
  • 27 | // {label} 28 | //
  • 29 | // ) 30 | // } 31 | // } 32 | 33 | // export default Tab 34 | -------------------------------------------------------------------------------- /src/fragments/tabs/tabs.tsx: -------------------------------------------------------------------------------- 1 | // import React, { Component } from 'react' 2 | // import PropTypes from 'prop-types' 3 | 4 | // import Tab from './Tab' 5 | 6 | // class Tabs extends Component { 7 | // static propTypes = { 8 | // children: PropTypes.instanceOf(Array).isRequired, 9 | // } 10 | 11 | // constructor(props) { 12 | // super(props) 13 | 14 | // this.state = { 15 | // activeTab: this.props.children[0].props.label, 16 | // } 17 | // } 18 | 19 | // onClickTabItem = (tab) => { 20 | // this.setState({ activeTab: tab }) 21 | // } 22 | 23 | // render() { 24 | // const { 25 | // onClickTabItem, 26 | // props: { 27 | // children, 28 | // }, 29 | // state: { 30 | // activeTab, 31 | // } 32 | // } = this 33 | 34 | // return ( 35 | //
    36 | //
      37 | // {children.map((child) => { 38 | // const { label } = child.props 39 | 40 | // return ( 41 | // 47 | // ) 48 | // })} 49 | //
    50 | //
    51 | // {children.map((child) => { 52 | // if (child.props.label !== activeTab) return undefined 53 | // return child.props.children 54 | // })} 55 | //
    56 | //
    57 | // ) 58 | // } 59 | // } 60 | 61 | // export default Tabs 62 | -------------------------------------------------------------------------------- /src/fragments/tabs/usage.tsx: -------------------------------------------------------------------------------- 1 | // import React from 'react' 2 | // import { render } from "react-dom" 3 | 4 | // import Tabs from './Tabs' 5 | // require('./styles.css') 6 | 7 | // function App() { 8 | // return ( 9 | //
    10 | //

    Tabs Demo

    11 | // 12 | //
    13 | // See ya later, Alligator! 14 | //
    15 | 16 | //
    17 | // After 'while, Crocodile! 18 | //
    19 | 20 | //
    21 | // Nothing to see here, this tab is extinct! 22 | //
    23 | //
    24 | //
    25 | // ) 26 | // } 27 | 28 | // const container = document.createElement('div') 29 | // document.body.appendChild(container) 30 | // render(, container) 31 | -------------------------------------------------------------------------------- /src/helpers/access-token.ts: -------------------------------------------------------------------------------- 1 | // import { session, local } from 'storage.io' 2 | // https://www.npmjs.com/package/storage.io 3 | import { session } from 'storage.io' 4 | 5 | class Storage { 6 | 7 | get = (name: string = 'token') => session.get(name) 8 | 9 | set = (name: string = 'token', value: string) => session.set(name, value) 10 | 11 | clear = (name: string = 'token') => session.remove(name) 12 | 13 | } 14 | 15 | export default Storage 16 | -------------------------------------------------------------------------------- /src/helpers/api.ts: -------------------------------------------------------------------------------- 1 | export const { 2 | REACT_APP_API_URL: API_URL, 3 | DADATA_API_KEY, 4 | DADATA_COMPANY_API_URL, 5 | DADATA_ADDRESS_API_URL, 6 | } = process.env 7 | -------------------------------------------------------------------------------- /src/helpers/browser-history.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history' 2 | 3 | // https://github.com/mjackson/history 4 | const browserHistory: any = process.env.BROWSER && createBrowserHistory() 5 | 6 | export default browserHistory 7 | -------------------------------------------------------------------------------- /src/helpers/classes.ts: -------------------------------------------------------------------------------- 1 | export interface S { 2 | styler: (css: any) => string; 3 | bind: (css: any) => any; 4 | } 5 | 6 | const Classes: S = { 7 | styler: (css, ...args: any[]): string => { 8 | const r: string[] = [] 9 | 10 | if (args.length) { 11 | Object.keys(args).map(id => { 12 | const item = args[id] || '' 13 | 14 | const type = typeof item 15 | 16 | if (['string', 'number'].indexOf(type) >= 0) { 17 | r.push(item) 18 | } else { 19 | Object.keys(item).map(name => { 20 | if (typeof item[name] !== 'undefined' && item[name]) { 21 | r.push(item.length ? css[item[name]] : css[name]) 22 | } 23 | }) 24 | } 25 | }) 26 | } 27 | 28 | return r.filter(i => i !== '').join(' ') 29 | }, 30 | 31 | bind: css => { 32 | return Classes.styler.bind(Classes, css) 33 | }, 34 | } 35 | 36 | export default Classes 37 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api' 2 | export { default as Classes } from './classes' 3 | export { default as browserHistory } from './browser-history' 4 | export { default as Storage } from './access-token' 5 | 6 | export * from './utils' 7 | export * from './predicts' 8 | -------------------------------------------------------------------------------- /src/helpers/predicts/comparison.ts: -------------------------------------------------------------------------------- 1 | export const compare = (a1: any[], a2: any[]): boolean => 2 | !!(a1 && a2 && a1.length === a2.length && a1.every((v, i) => v === a2[i])) 3 | 4 | export function shallowCompare (prev: any, next: any, uniq: string | number): boolean { 5 | const prevKeys = prev.map((item: any) => item[uniq]) 6 | const diffData = next.filter((item: any) => prevKeys.indexOf(item[uniq]) < 0) 7 | 8 | return diffData && diffData.length === 0 9 | } 10 | 11 | // const isEq = exports.isEq = function isEq(a, b) { 12 | // if (a == b) return true; 13 | 14 | // for (let i in a) { 15 | // if (!isEq(a[i], b[i])) return false 16 | // } 17 | 18 | // for (let i in b) { 19 | // if (!isEq(a[i], b[i])) return false 20 | // } 21 | 22 | // return true 23 | // } 24 | -------------------------------------------------------------------------------- /src/helpers/predicts/has-class.ts: -------------------------------------------------------------------------------- 1 | export const hasClass = (element: any, classList: string[]) => { 2 | let includes = false 3 | const { className } = element 4 | 5 | classList.map(cn => { 6 | if (!includes) { 7 | includes = cn && className && className.length && className.indexOf(cn) > -1 8 | } 9 | }) 10 | 11 | return element && includes 12 | } 13 | -------------------------------------------------------------------------------- /src/helpers/predicts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './is-int' 2 | export * from './is-null' 3 | export * from './is-float' 4 | export * from './is-phone' 5 | export * from './is-active' 6 | export * from './is-email' 7 | export * from './is-undefined' 8 | export * from './is-function' 9 | export * from './has-class' 10 | export * from './comparison' 11 | -------------------------------------------------------------------------------- /src/helpers/predicts/is-active.ts: -------------------------------------------------------------------------------- 1 | export const isActive = (match?: any, location?: Location) => { 2 | if (!match) { 3 | return false 4 | } 5 | 6 | return match && location && location.pathname === match.url 7 | } 8 | -------------------------------------------------------------------------------- /src/helpers/predicts/is-email.ts: -------------------------------------------------------------------------------- 1 | export const isEmail = (str: string) => { 2 | /* eslint-disable */ 3 | const re = new RegExp( 4 | // tslint:disable-next-line 5 | /^(([^\s"(),.:;<>@[\\\]]+(\.[^\s"(),.:;<>@[\\\]]+)*)|(".+"))@((\[(?:\d{1,3}\.){3}\d{1,3}])|(([\d-A-Za-z]+\.)+[A-Za-z]{2,}))$/ 6 | ) 7 | /* eslint-enable */ 8 | 9 | return str && !re.test(str[name]) 10 | } 11 | -------------------------------------------------------------------------------- /src/helpers/predicts/is-float.ts: -------------------------------------------------------------------------------- 1 | export const isFloat = (n: any): boolean => 2 | n && typeof n === 'number' && n % 1 !== 0 3 | -------------------------------------------------------------------------------- /src/helpers/predicts/is-function.ts: -------------------------------------------------------------------------------- 1 | export const isFunction = (fn: any): boolean => 2 | !!(fn && typeof fn === 'function') 3 | -------------------------------------------------------------------------------- /src/helpers/predicts/is-int.ts: -------------------------------------------------------------------------------- 1 | export const isInt = (n: any): boolean => 2 | n && typeof n === 'number' && n % 1 === 0 3 | -------------------------------------------------------------------------------- /src/helpers/predicts/is-null.ts: -------------------------------------------------------------------------------- 1 | export const isNull = (x: any): boolean => 2 | x && typeof x === 'object' && x === null 3 | -------------------------------------------------------------------------------- /src/helpers/predicts/is-phone.ts: -------------------------------------------------------------------------------- 1 | export const isPhone = (phone: string): boolean => 2 | !!(phone && phone.replace(/[\s()+\-A-Z_a-z]+/gm, '').length === 11) 3 | -------------------------------------------------------------------------------- /src/helpers/predicts/is-undefined.ts: -------------------------------------------------------------------------------- 1 | export const isUndefined = (x: any): boolean => typeof x === 'undefined' 2 | -------------------------------------------------------------------------------- /src/helpers/utils/animation.ts: -------------------------------------------------------------------------------- 1 | export const easeInOutQuad = (t: number, b: number, c: number, d: number): number => { 2 | t /= d / 2 3 | 4 | if (t < 1) { 5 | return (c / 2) * t * t + b 6 | } 7 | 8 | t-- 9 | 10 | return (-c / 2) * (t * (t - 2) - 1) + b 11 | } 12 | -------------------------------------------------------------------------------- /src/helpers/utils/concat.ts: -------------------------------------------------------------------------------- 1 | export const concat = (...rest: string[]) => rest.concat(' ') 2 | -------------------------------------------------------------------------------- /src/helpers/utils/create-markup.ts: -------------------------------------------------------------------------------- 1 | export interface MarkupProps { 2 | __html: any; 3 | } 4 | 5 | export const createMarkup = (data: any): MarkupProps => { 6 | return { __html: data } 7 | } 8 | -------------------------------------------------------------------------------- /src/helpers/utils/decl-of-num.ts: -------------------------------------------------------------------------------- 1 | export const formatDeclOfNum = (n: string | number, titles: string[]): string | number => { 2 | const cases = [2, 0, 1, 1, 1, 2] 3 | const x = Math.abs(parseInt(n.toString(), 10)) 4 | return x && titles && titles[x % 100 > 4 && x % 100 < 20 ? 2 : cases[x % 10 < 5 ? x % 10 : 5]] 5 | } 6 | 7 | export const declOfNum = (n: string | number, titles: string[]): string | number => 8 | n && titles && titles.length && [n, formatDeclOfNum(n, titles)].join(' ') 9 | -------------------------------------------------------------------------------- /src/helpers/utils/display-name.ts: -------------------------------------------------------------------------------- 1 | export const getDisplayName = (component: any): string => 2 | component && (component.displayName || component.name || 'Component') 3 | -------------------------------------------------------------------------------- /src/helpers/utils/format-date.ts: -------------------------------------------------------------------------------- 1 | import dateFormat from 'dateformat' 2 | 3 | export const formatDate = (date: string | number, format = 'dd.mm.yyyy'): string => 4 | date && dateFormat(date, format) 5 | -------------------------------------------------------------------------------- /src/helpers/utils/format-money-native.ts: -------------------------------------------------------------------------------- 1 | const formatMoneyNative = (num: number, currency: string = 'RUB'): number | string => { 2 | const formatter = new Intl.NumberFormat('ru-RU', { 3 | style: 'currency', 4 | currency, 5 | maximumSignificantDigits: 2, 6 | }) 7 | 8 | return num && formatter.format(num) 9 | } 10 | 11 | export default formatMoneyNative 12 | -------------------------------------------------------------------------------- /src/helpers/utils/format-money.ts: -------------------------------------------------------------------------------- 1 | const formatMoney = (num: number, penny: number = 2, breaking: boolean = true): number | string => { 2 | let value = 3 | num && 4 | parseFloat(num.toString()) 5 | .toFixed(penny) 6 | .replace(/\d(?=(\d{3})+\.)/g, '$& ') 7 | .replace(/\./g, ',') 8 | .replace(/,0{2}/g, '') 9 | 10 | if (!breaking) { 11 | value = value && value.replace(/\s/g, ' ') 12 | } 13 | 14 | return (num && value) || 0 15 | } 16 | 17 | export default formatMoney 18 | -------------------------------------------------------------------------------- /src/helpers/utils/formated-phone.ts: -------------------------------------------------------------------------------- 1 | export const formatedPhone = (phone: string): string => 2 | phone && phone.replace(/\+/g, '').replace(/(\d)(\d{3})(\d{3})(\d{2})(\d{2})/, '+$1 ($2) $3-$4-$5') 3 | -------------------------------------------------------------------------------- /src/helpers/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './range' 2 | export * from './preload' 3 | export * from './animation' 4 | export * from './make-query' 5 | export * from './concat' 6 | export * from './create-markup' 7 | export * from './decl-of-num' 8 | export * from './display-name' 9 | export * from './format-date' 10 | export { default as formatMoney } from './format-money' 11 | export { default as formatMoneyNative } from './format-money-native' 12 | export * from './formated-phone' 13 | export * from './kebab-case' 14 | export * from './logout' 15 | export * from './nl2br' 16 | export * from './non-breaking' 17 | export * from './object-sort' 18 | export * from './parse-errors' 19 | export * from './parse-query' 20 | export * from './prepare-field' 21 | export * from './prepare-phone' 22 | export * from './remove-spaces' 23 | export * from './replace-mask' 24 | export * from './request' 25 | export * from './timestamp-date' 26 | export * from './trim' 27 | export * from './uc-first' 28 | export * from './validate' 29 | -------------------------------------------------------------------------------- /src/helpers/utils/kebab-case.ts: -------------------------------------------------------------------------------- 1 | export const kebabCase = (str: string): string => 2 | str && str 3 | .replace(/([a-z])([A-Z])/g, '$1-$2') 4 | .replace(/\s+/g, '-') 5 | .toLowerCase() 6 | -------------------------------------------------------------------------------- /src/helpers/utils/logout.ts: -------------------------------------------------------------------------------- 1 | import { Storage } from '../access-token' 2 | 3 | export const logout = (reload?: boolean): void => { 4 | const AccessToken = new Storage() 5 | 6 | AccessToken.clear() 7 | 8 | if (reload) { 9 | location.href = '/auth/sign-in' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/helpers/utils/make-query.ts: -------------------------------------------------------------------------------- 1 | export const makeQuery = (options: any): string => { 2 | const stack: string[] = [] 3 | 4 | if (options && Object.keys(options).length) { 5 | Object.keys(options).map((name: string) => { 6 | if (name && options[name]) { 7 | stack.push(`${name}=${options[name]}`) 8 | } 9 | }) 10 | } 11 | 12 | return (options && (stack.length && `?${stack.join('&')}`)) || '' 13 | } 14 | -------------------------------------------------------------------------------- /src/helpers/utils/nl2br.ts: -------------------------------------------------------------------------------- 1 | export const nl2br = (str: string): string => 2 | str && str.replace(/([^>])\n/g, '$1
    ') 3 | -------------------------------------------------------------------------------- /src/helpers/utils/non-breaking.ts: -------------------------------------------------------------------------------- 1 | export const nonBreaking = (str: string): string => 2 | str && str.replace(/\s/g, ' ') 3 | -------------------------------------------------------------------------------- /src/helpers/utils/object-sort.ts: -------------------------------------------------------------------------------- 1 | export const objectSort = (v: string): any => 2 | (a: any, b: any) => a && b && a[v] - b[v] 3 | -------------------------------------------------------------------------------- /src/helpers/utils/parse-errors.ts: -------------------------------------------------------------------------------- 1 | export const parseError = (json: any): any => { 2 | const response = {} 3 | 4 | if (json && Object.keys(json).length) { 5 | Object.keys(json).map(name => { 6 | let error = json[name] 7 | 8 | if (error.length === 1) { 9 | error = error.shift() 10 | } 11 | 12 | response[name] = error 13 | }) 14 | } 15 | 16 | return response 17 | } 18 | -------------------------------------------------------------------------------- /src/helpers/utils/parse-query.ts: -------------------------------------------------------------------------------- 1 | export const parseQuery = (search: string) => { 2 | const params = {} 3 | 4 | if (search.substr(0, 1) === '?') { 5 | search = search.substr(1) 6 | } 7 | 8 | const temp = search.split('&') 9 | 10 | if (temp.length) { 11 | temp.map(item => { 12 | const cache = item.split('=') 13 | params[cache[0]] = cache[1] 14 | }) 15 | } 16 | 17 | return search && params 18 | } 19 | -------------------------------------------------------------------------------- /src/helpers/utils/preload.ts: -------------------------------------------------------------------------------- 1 | export const preload = (stack: any[]): void => { 2 | if (stack && stack.length) { 3 | const cb = (data: any) => { 4 | const fn = stack.shift() 5 | 6 | if (typeof fn === 'function') { 7 | fn(cb, data) 8 | } 9 | } 10 | 11 | cb(null) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/helpers/utils/prepare-field.ts: -------------------------------------------------------------------------------- 1 | export const prepareField = (str: string): string => 2 | str && str.replace(/\s/g, '') 3 | -------------------------------------------------------------------------------- /src/helpers/utils/prepare-phone.ts: -------------------------------------------------------------------------------- 1 | export const preparePhone = (str: string) => { 2 | let phone = str && str.replace(/\s/g, '') 3 | 4 | if (phone && phone.substring(0, 1) !== '+') { 5 | phone = `+${phone}` 6 | } 7 | 8 | return phone 9 | } 10 | -------------------------------------------------------------------------------- /src/helpers/utils/range.ts: -------------------------------------------------------------------------------- 1 | export const range = (start: number, end: number) => 2 | [ ...Array(1 + end - start).keys() ].map(v => start + v) 3 | -------------------------------------------------------------------------------- /src/helpers/utils/remove-spaces.ts: -------------------------------------------------------------------------------- 1 | export const removeSpaces = (str: string): string => 2 | str && str.replace(/\s/g, '') 3 | -------------------------------------------------------------------------------- /src/helpers/utils/replace-mask.ts: -------------------------------------------------------------------------------- 1 | export const replaceMask = (str: string, key: string | number, data: any): string => 2 | str && data && str.replace(new RegExp(`{${key}}`, 'g'), data) 3 | -------------------------------------------------------------------------------- /src/helpers/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | import { API_URL } from '../api' 4 | import { Storage } from '../access-token' 5 | 6 | export const refreshToken = () => ({}) 7 | 8 | export const request: any = (options: any = { noToken: false }) => { 9 | const noToken = Object.keys(options).length && options.hasOwnProperty('noToken') && options.noToken 10 | 11 | const headers: any = { 12 | 'Accept': 'application/json', 13 | 'Content-Type': 'application/json', 14 | } 15 | 16 | if (!noToken) { 17 | const AccessToken = new Storage() 18 | const authorizationToken = AccessToken.get() 19 | 20 | if (authorizationToken) { 21 | headers['x-access-token'] = `${authorizationToken}` 22 | } 23 | } 24 | 25 | return axios.create({ 26 | baseURL: API_URL, 27 | headers, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /src/helpers/utils/timestamp-date.ts: -------------------------------------------------------------------------------- 1 | export const timestampDate = (date: string): number => 2 | parseInt(date.replace(/\./g, ''), 10) / 1000 3 | -------------------------------------------------------------------------------- /src/helpers/utils/trim.ts: -------------------------------------------------------------------------------- 1 | import { isUndefined } from '../predicts/is-undefined' 2 | 3 | export const trim = (str: string): string => 4 | str && str.toString().replace(/^\s+|\s+$/g, '') 5 | 6 | export const clean = (str: string): string => 7 | ( 8 | str && 9 | str 10 | .toString() 11 | .replace(/\s+/gm, '') 12 | .replace(/_/gm, '') 13 | .replace(/-/gm, '') 14 | .replace(/[()]+/gm, '') 15 | ) || '' 16 | 17 | export const trimmed = (str: string): string => 18 | str && (!isUndefined(str) ? clean(str || '').toString() : '') 19 | -------------------------------------------------------------------------------- /src/helpers/utils/uc-first.ts: -------------------------------------------------------------------------------- 1 | export const ucFirst = (str: string) => { 2 | const f = str && str.charAt(0).toUpperCase() 3 | return f && `${f}${str.substr(1)}` 4 | } 5 | 6 | export const normalize = (str: string) => { 7 | return (str && ucFirst(str.replace(/-/g, ' '))) || str 8 | } 9 | -------------------------------------------------------------------------------- /src/helpers/utils/validate.ts: -------------------------------------------------------------------------------- 1 | import { trim } from '../utils/trim' 2 | import { isEmail } from '../predicts/is-email' 3 | import { isUndefined } from '../predicts/is-undefined' 4 | 5 | export const validateBlocks = (value: string, options: any): boolean => { 6 | const blocks = trim(value).split(' ') 7 | 8 | if (blocks.length !== options.length) { 9 | return false 10 | } 11 | 12 | const failed = blocks.filter((part: any, index: number) => { 13 | return !options[index] || part.length < options[index] 14 | }) 15 | 16 | return !failed.length 17 | } 18 | 19 | export const validate = (data: any, options: any): any => { 20 | const errors = {} 21 | 22 | Object.keys(options).map(name => { 23 | const check = options[name] 24 | 25 | if (check) { 26 | if (!data[name] && (isUndefined(check.required) || check.required)) { 27 | errors[name] = true 28 | } else if (check.hasOwnProperty('cyrillic') && !/[ЁА-яё]/.test(data[name])) { 29 | errors[name] = 'Можно использовать только кириллицу' 30 | } else if (check.hasOwnProperty('blocks') && !validateBlocks(data[name], check.blocks)) { 31 | errors[name] = 'Не правильный формат' 32 | } 33 | 34 | if (check.isEmail && (data[name] && !check.required && !isEmail(data[name]))) { 35 | errors[name] = 'Неправильный адрес электронной почты' 36 | } 37 | 38 | if (typeof data[name] === 'object') { 39 | Object.keys(check).map(k => { 40 | if (typeof data[name][k] === 'undefined' || data[name][k] === '') { 41 | errors[name] = true 42 | } 43 | }) 44 | } 45 | } 46 | }) 47 | 48 | return errors 49 | } 50 | -------------------------------------------------------------------------------- /src/layouts/core-layout/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | import { Header } from 'fragments' 7 | 8 | export interface P { 9 | children?: JSX.Element[] | JSX.Element | string; 10 | } 11 | 12 | export interface S { 13 | title: string; 14 | } 15 | 16 | const cx = Classes.bind(css) 17 | 18 | export class CoreLayout extends React.Component { 19 | 20 | static defaultProps = { 21 | children: '', 22 | } 23 | 24 | state = { 25 | title: 'React Starter App', 26 | } 27 | 28 | componentDidMount () { 29 | this.fixScroll() 30 | } 31 | 32 | componentDidUpdate () { 33 | this.fixScroll() 34 | } 35 | 36 | fixScroll = () => window.scrollTo(0, 0) 37 | 38 | render () { 39 | const { children } = this.props 40 | 41 | return ( 42 |
    43 |
    44 | 45 |
    {children}
    46 |
    47 | ) 48 | } 49 | 50 | } 51 | 52 | export default CoreLayout 53 | -------------------------------------------------------------------------------- /src/layouts/core-layout/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | $core-sidebar-width: $aside-width + $sidebar-width; 4 | 5 | .layout { 6 | min-height: 100%; 7 | // overflow: hidden; 8 | } 9 | 10 | .main { 11 | width: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /src/layouts/index.ts: -------------------------------------------------------------------------------- 1 | export { CoreLayout } from './core-layout' 2 | -------------------------------------------------------------------------------- /src/pages/example-page/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { 5 | // request, 6 | formatMoney, 7 | } from 'helpers' 8 | 9 | import { Group } from 'fragments' 10 | 11 | import { 12 | Input, 13 | Button, 14 | } from 'components' 15 | 16 | import { 17 | STORE_UI, 18 | STORE_APP, 19 | STORE_ROUTER, 20 | } from 'settings' 21 | 22 | import { inject, observer } from 'mobx-react' 23 | 24 | export interface Product { 25 | id: number; 26 | name: string; 27 | price: string; 28 | category: string; 29 | currency: string; 30 | } 31 | 32 | @inject(STORE_UI, STORE_APP, STORE_ROUTER) 33 | @observer 34 | class ExamplePage extends React.Component { 35 | 36 | state = { 37 | products: [], 38 | } 39 | 40 | // componentDidMount () { 41 | // this.loadData() 42 | // } 43 | 44 | // loadData = () => { 45 | // request.get('/data/products.json') 46 | // .then((res: any) => res.data) 47 | // .then((response: any) => this.setState({ products: response.splice(0, 40) })) 48 | // .catch((error: any) => console.log(error)) 49 | // } 50 | 51 | render () { 52 | const type = 'grid' 53 | const { products } = this.state 54 | 55 | return ( 56 |
    57 | 61 | 62 | 63 | {products && products.map((item: Product) => ( 64 |
    65 | 66 |
    {item.name}
    67 |
    {item.category}
    68 | 69 |
    70 |
    {formatMoney(item.price)} ₽
    71 | 72 |
    73 |
    74 | ))} 75 |
    76 |
    77 | ) 78 | } 79 | 80 | } 81 | 82 | export { ExamplePage } 83 | export default ExamplePage 84 | -------------------------------------------------------------------------------- /src/pages/example-page/route.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable: max-line-length */ 3 | 4 | import { loadComponent } from 'utils' 5 | 6 | const locale: string = '/:locale(ru|en|de)' 7 | 8 | export const routes: Route[] = [ 9 | { 10 | path: `${locale}/example`, 11 | component: loadComponent(() => 12 | import( 13 | /* webpackChunkName: "example-page", webpackMode: "lazy-once", webpackPrefetch: true */ 14 | './' 15 | ) 16 | ), 17 | title: 'Example Page', 18 | description: 'React starter kit', 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /src/pages/example-page/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .content { 4 | clear: both; 5 | padding: 20px; 6 | } 7 | 8 | .description { 9 | color: rgba(0, 0, 0, .75); 10 | font-size: 21px; 11 | font-weight: 300; 12 | line-height: 36px; 13 | text-align: center; 14 | } 15 | 16 | .product { 17 | height: 100%; 18 | 19 | &_image { 20 | max-width: 100%; 21 | margin-bottom: 10px; 22 | } 23 | 24 | &_title { 25 | color: $c-blue; 26 | font-size: 1.6rem; 27 | line-height: 1.4; 28 | font-weight: 700; 29 | margin-bottom: 10px; 30 | } 31 | 32 | &_category { 33 | font-size: 1.5rem; 34 | line-height: 1.4; 35 | color: $c-black; 36 | opacity: .5; 37 | margin-bottom: 10px; 38 | } 39 | 40 | &_price { 41 | font-size: 1.4rem; 42 | } 43 | 44 | &_footer { 45 | display: flex; 46 | align-items: center; 47 | justify-content: space-between; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/pages/index.ts: -------------------------------------------------------------------------------- 1 | export { routes as MainRoute } from './main-page/route' 2 | export { routes as PanelsRoute } from './panels-page/route' 3 | export { routes as ExampleRoute } from './example-page/route' 4 | 5 | // Should always be the last 6 | export { routes as NoMatchRoute } from './nomatch-page/route' 7 | -------------------------------------------------------------------------------- /src/pages/main-page/assets/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnked/react-starter/f44523a40e7d5d51a2b4a18d3449f012558cdd15/src/pages/main-page/assets/image.jpg -------------------------------------------------------------------------------- /src/pages/main-page/route.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable: max-line-length */ 3 | 4 | import { loadComponent } from 'utils' 5 | 6 | const locale: string = '/:locale(ru|en|de)' 7 | 8 | export const routes: Route[] = [ 9 | { 10 | exact: true, 11 | path: `${locale}?`, 12 | component: loadComponent(() => 13 | import( 14 | /* webpackChunkName: "main-page", webpackMode: "lazy-once", webpackPrefetch: true */ 15 | './' 16 | ), 17 | ), 18 | title: 'Main Page title', 19 | description: 'React starter kit', 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /src/pages/nomatch-page/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as css from './styles.scss' 3 | 4 | import { Classes } from 'helpers' 5 | 6 | export interface P { 7 | location: { 8 | pathname: string 9 | }; 10 | } 11 | 12 | const cx = Classes.bind(css) 13 | 14 | const NoMatchPage = ({ location }: P) => { 15 | return ( 16 |
    17 |
    18 | Error: 4xx 19 |
    20 | 21 |
    22 |

    Ошибка (4xx)

    23 |

    24 | Не удалось найти страницу {location.pathname}. 25 |

    26 |
    27 |
    28 | ) 29 | } 30 | 31 | export { NoMatchPage } 32 | export default NoMatchPage 33 | -------------------------------------------------------------------------------- /src/pages/nomatch-page/route.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable: max-line-length */ 3 | 4 | import { loadComponent } from 'utils' 5 | 6 | export const routes: Route[] = [ 7 | { 8 | path: '(.*)', 9 | status: 404, 10 | statusCode: 404, 11 | component: loadComponent(() => 12 | import(/* webpackPrefetch: -100 */ './') 13 | ), 14 | title: 'Error Page title', 15 | description: 'React starter kit', 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /src/pages/nomatch-page/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .error { 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | align-items: center; 8 | font-family: sans-serif; 9 | flex-direction: column; 10 | justify-content: center; 11 | } 12 | 13 | .figure { 14 | clear: both; 15 | width: 100%; 16 | max-width: 250px; 17 | user-select: none; 18 | text-align: center; 19 | margin-bottom: 30px; 20 | pointer-events: none; 21 | 22 | &__image { 23 | width: 100%; 24 | max-width: 400px; 25 | } 26 | } 27 | 28 | .content { 29 | clear: both; 30 | } 31 | 32 | .title { 33 | color: rgba(0, 0, 0, .75); 34 | font-size: 38px; 35 | font-weight: 300; 36 | line-height: 47px; 37 | text-align: center; 38 | margin-bottom: 10px; 39 | } 40 | 41 | .description { 42 | color: rgba(0, 0, 0, .75); 43 | font-size: 21px; 44 | font-weight: 300; 45 | line-height: 36px; 46 | text-align: center; 47 | } 48 | -------------------------------------------------------------------------------- /src/pages/panels-page/route.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable: max-line-length */ 3 | 4 | import { loadComponent } from 'utils' 5 | 6 | const locale: string = '/:locale(ru|en|de)' 7 | 8 | export const routes: Route[] = [ 9 | { 10 | path: `${locale}/panels`, 11 | component: loadComponent(() => 12 | import( 13 | /* webpackChunkName: "panels-page", webpackMode: "lazy-once", webpackPrefetch: true */ 14 | './' 15 | ) 16 | ), 17 | title: 'Panels Page title', 18 | description: 'React starter kit', 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /src/pages/panels-page/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/_global.scss'; 2 | 3 | .content { 4 | clear: both; 5 | padding: 20px; 6 | } 7 | 8 | .product { 9 | height: 100%; 10 | 11 | &_image { 12 | max-width: 100%; 13 | margin-bottom: 10px; 14 | } 15 | 16 | &_title { 17 | color: $c-blue; 18 | font-size: 1.6rem; 19 | line-height: 1.4; 20 | font-weight: 700; 21 | margin-bottom: 10px; 22 | } 23 | 24 | &_category { 25 | font-size: 1.5rem; 26 | line-height: 1.4; 27 | color: $c-black; 28 | opacity: .5; 29 | margin-bottom: 10px; 30 | } 31 | 32 | &_price { 33 | font-size: 1.4rem; 34 | } 35 | 36 | &_footer { 37 | display: flex; 38 | align-items: center; 39 | justify-content: space-between; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/routes.ts: -------------------------------------------------------------------------------- 1 | import * as pages from 'pages' 2 | 3 | const routes: any = [] 4 | 5 | Object.keys(pages).map((name: string) => routes.push(...pages[name])) 6 | 7 | export { routes } 8 | -------------------------------------------------------------------------------- /src/server.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { renderToString } from 'react-dom/server' 3 | // import { Provider } from 'react-redux' 4 | // import configureStore from './redux/configureStore' 5 | import App from './app' 6 | 7 | export const render = (initialState: any) => { 8 | // const store = configureStore(initialState) 9 | // 10 | 11 | console.log(initialState) 12 | 13 | const content = renderToString( 14 | 15 | ) 16 | 17 | // const preloadedState = store.getState() 18 | // return { content, preloadedState } 19 | return { content, {} } 20 | } 21 | -------------------------------------------------------------------------------- /src/settings/constants.ts: -------------------------------------------------------------------------------- 1 | export const STORE_UI = 'ui' 2 | export const STORE_APP = 'app' 3 | export const STORE_ROUTER = 'router' 4 | 5 | // export enum ActionType { 6 | // init = 'init', 7 | // buyGood = 'buyGood', 8 | // removeGood = 'removeGood', 9 | // deleteGood = 'deleteGood', 10 | // fillCat = 'fillCat', 11 | // fillGoods = 'fillGoods', 12 | // order = 'order', 13 | // emptyCart = 'emptyCart', 14 | // }; 15 | 16 | // enum A { 17 | // a = 'a', 18 | // b = 'b', 19 | // c = 'c' 20 | // } 21 | 22 | // trying to type generic vars 23 | // interface Entity { 24 | // id: number; 25 | // parentId: number; 26 | // state: string[] 27 | // path: string; 28 | // route: string; 29 | // lazyPath?: string; 30 | // } 31 | 32 | // type Entities = { [key in keyof T]: Entity } 33 | 34 | // type RoutesEntity = { [key in keyof T]: Entities } 35 | 36 | // interface Nums { 37 | // one; 38 | // two; 39 | // three; 40 | // } 41 | 42 | // interface Strs { 43 | // a; 44 | // b; 45 | // c; 46 | // } 47 | 48 | // interface MyEntity { 49 | // nums: Entities; 50 | // strs: Entities; 51 | // } 52 | -------------------------------------------------------------------------------- /src/settings/dictionaries.ts: -------------------------------------------------------------------------------- 1 | export const en = { 2 | open: 'open', 3 | close: 'close', 4 | } 5 | 6 | export const ru = { 7 | open: 'открыть', 8 | close: 'закрыть', 9 | } 10 | -------------------------------------------------------------------------------- /src/settings/environment.ts: -------------------------------------------------------------------------------- 1 | const { __DEV__, __PROD__, NODE_ENV = 'production' } = process.env 2 | 3 | export const environment = { 4 | production: __PROD__ || NODE_ENV === 'production', 5 | development: __DEV__ || NODE_ENV === 'development', 6 | } 7 | -------------------------------------------------------------------------------- /src/settings/firebase.ts: -------------------------------------------------------------------------------- 1 | import * as firebase from 'firebase' 2 | 3 | export const API_KEY = process.env.FIREBASE_API_KEY 4 | export const PROJECT_ID = process.env.FIREBASE_PROJECT_ID 5 | export const AUTH_DOMAIN = process.env.FIREBASE_AUTH_DOMAIN 6 | export const DATABASE_URL = process.env.FIREBASE_DATABASE_URL 7 | export const STORAGE_BUCKET = process.env.FIREBASE_STORAGE_BUCKET 8 | export const MESSAGING_SENDER_ID = process.env.FIREBASE_MESSAGING_SENDER_ID 9 | 10 | firebase.initializeApp({ 11 | apiKey: API_KEY, 12 | projectId: PROJECT_ID, 13 | authDomain: AUTH_DOMAIN, 14 | databaseURL: DATABASE_URL, 15 | storageBucket: STORAGE_BUCKET, 16 | messagingSenderId: MESSAGING_SENDER_ID, 17 | }) 18 | -------------------------------------------------------------------------------- /src/settings/index.ts: -------------------------------------------------------------------------------- 1 | export { environment } from './environment' 2 | export { STORE_UI, STORE_APP, STORE_ROUTER } from './constants' 3 | -------------------------------------------------------------------------------- /src/settings/setup.js: -------------------------------------------------------------------------------- 1 | // import { configure } from 'enzyme' 2 | // import Adapter from 'enzyme-adapter-react-16' 3 | 4 | // configure({ 5 | // adapter: new Adapter(), 6 | // }) 7 | -------------------------------------------------------------------------------- /src/settings/variables.ts: -------------------------------------------------------------------------------- 1 | export const variables = { 2 | grey: '#f2f2f2', 3 | fontSize: '10px', 4 | colorHover: '#f3f3f3', 5 | border: '1px solid #f6f8fa', 6 | borderInvalid: '2px solid #ff0000', 7 | } 8 | -------------------------------------------------------------------------------- /src/store/create-store.ts: -------------------------------------------------------------------------------- 1 | import { configure } from 'mobx' 2 | 3 | import { enableLogging } from 'mobx-logger' 4 | 5 | import { 6 | UiStore, 7 | AppStore, 8 | } from './providers' 9 | 10 | import { 11 | environment, 12 | STORE_UI, 13 | STORE_APP, 14 | STORE_ROUTER, 15 | } from 'settings' 16 | 17 | configure({ 18 | enforceActions: 'observed', // 'never' | 'always' | 'observed' 19 | }) 20 | 21 | enableLogging({ 22 | predicate: () => environment.development && Boolean(window.navigator.userAgent), 23 | action: true, 24 | reaction: false, 25 | transaction: false, 26 | compute: false, 27 | }) 28 | 29 | export const createStore = (routingStore: any) => { 30 | const initialState = (window && window.__INITIAL_STATE__) || {} 31 | 32 | const { ui, app } = initialState 33 | 34 | const uiStore = new UiStore(ui) 35 | const appStore = new AppStore(app) 36 | 37 | Reflect.deleteProperty(window, '__INITIAL_STATE__') 38 | 39 | return { 40 | [STORE_UI]: uiStore, 41 | [STORE_APP]: appStore, 42 | [STORE_ROUTER]: routingStore, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | export { createStore } from './create-store' 2 | -------------------------------------------------------------------------------- /src/store/providers/_/cart.ts: -------------------------------------------------------------------------------- 1 | import { 2 | // computed, 3 | observable, 4 | } from 'mobx' 5 | 6 | export interface ProductProps { 7 | id: number; 8 | name: string; 9 | price: number; 10 | count: number; 11 | } 12 | 13 | export const cart = observable({ 14 | products: observable.map(), 15 | 16 | addProduct (product: ProductProps) { 17 | if (this.Products.has(product.id)) { 18 | throw new Error('Product already exists') 19 | } else { 20 | this.Products.set(product.id, product) 21 | } 22 | }, 23 | 24 | updateProduct (product: ProductProps) { 25 | this.Products.set(product.id, product) 26 | }, 27 | 28 | removeProduct (id: any) { 29 | this.Products.delete(id) 30 | }, 31 | 32 | // unenrollProduct(courseId: number, ProductId: number) { 33 | // this.enrollment.get(courseId).remove(ProductId) 34 | // }, 35 | 36 | // enrolledProducts(courseId: number) { 37 | // return computed(() => this.enrollment.get(courseId).map((n: any) => this.Products.get(n))).get() 38 | // }, 39 | }) 40 | -------------------------------------------------------------------------------- /src/store/providers/_/todo-demo.ts: -------------------------------------------------------------------------------- 1 | // import { observable, action, reaction, computed } from 'mobx' 2 | 3 | // export interface Todo { 4 | // task: string 5 | // isComplete: boolean 6 | // } 7 | 8 | // class TodoStore { 9 | // @observable todoList: Todo[] = [] 10 | 11 | // constructor() { 12 | // reaction( 13 | // () => this.todoList.filter((todo) => !todo.isComplete), 14 | // (incompletedTasks) => { 15 | // if (incompletedTasks.length > 5) { 16 | // alert("Dude. You've got too much on your plate.") 17 | // } 18 | // } 19 | // ) 20 | // } 21 | 22 | // @computed 23 | // get completedTasks(): number { 24 | // return this.todoList.filter((todo) => todo.isComplete).length 25 | // } 26 | 27 | // @action 28 | // addTodo(task: string) { 29 | // this.todoList.push({ task, isComplete: false }) 30 | // } 31 | 32 | // @action 33 | // completeTodo(completedTodo: Todo) { 34 | // this.todoList.find((todo) => todo === completedTodo).isComplete = true 35 | // } 36 | // } 37 | 38 | // export const todoStore = new TodoStore() 39 | -------------------------------------------------------------------------------- /src/store/providers/_/todostore.ts: -------------------------------------------------------------------------------- 1 | // import { observable, computed, action } from 'mobx'; 2 | // import { TodoModel } from 'app/models'; 3 | 4 | // export class TodoStore { 5 | // constructor(fixtures: TodoModel[]) { 6 | // this.todos = fixtures; 7 | // } 8 | 9 | // @observable public todos: Array; 10 | 11 | // @computed 12 | // get activeTodos() { 13 | // return this.todos.filter((todo) => !todo.completed); 14 | // } 15 | 16 | // @computed 17 | // get completedTodos() { 18 | // return this.todos.filter((todo) => todo.completed); 19 | // } 20 | 21 | // @action 22 | // addTodo = (item: Partial): void => { 23 | // this.todos.push(new TodoModel(item.text, item.completed)); 24 | // }; 25 | 26 | // @action 27 | // editTodo = (id: number, data: Partial): void => { 28 | // this.todos = this.todos.map((todo) => { 29 | // if (todo.id === id) { 30 | // if (typeof data.completed == 'boolean') { 31 | // todo.completed = data.completed; 32 | // } 33 | // if (typeof data.text == 'string') { 34 | // todo.text = data.text; 35 | // } 36 | // } 37 | // return todo; 38 | // }); 39 | // }; 40 | 41 | // @action 42 | // deleteTodo = (id: number): void => { 43 | // this.todos = this.todos.filter((todo) => todo.id !== id); 44 | // }; 45 | 46 | // @action 47 | // completeAll = (): void => { 48 | // this.todos = this.todos.map((todo) => ({ ...todo, completed: true })); 49 | // }; 50 | 51 | // @action 52 | // clearCompleted = (): void => { 53 | // this.todos = this.todos.filter((todo) => !todo.completed); 54 | // }; 55 | // } 56 | 57 | // export default TodoStore; 58 | -------------------------------------------------------------------------------- /src/store/providers/app-store.ts: -------------------------------------------------------------------------------- 1 | import { observable, action, computed, extendObservable } from 'mobx' 2 | 3 | export class AppStore { 4 | 5 | static mobxLoggerConfig: { 6 | enabled: false, 7 | methods: { 8 | loadTags: true, 9 | }, 10 | } 11 | 12 | static defaultState = { 13 | query: '', 14 | isLoading: false, 15 | results: [], 16 | } 17 | 18 | // @observable results: string[] 19 | 20 | // @observable isLoading: boolean 21 | 22 | @observable 23 | query: string = '' 24 | 25 | constructor (initialState?: any) { 26 | // console.log({...AppStore.defaultState, ...initialState}) 27 | 28 | if (initialState && Object.keys(initialState).length) { 29 | // extendObservable(this, {...AppStore.defaultState, ...initialState}) 30 | extendObservable(this, initialState) 31 | } 32 | } 33 | 34 | @computed 35 | get getState () { 36 | return this.query 37 | } 38 | 39 | // async loadAll () { 40 | // this.isLoading = true; 41 | // await store.loadUsers();//вот первая 42 | // const { match } = this.props; 43 | // const userId = parseInt(match.params.id); 44 | // await this.user.loadStats(userId); 45 | // } 46 | 47 | @action 48 | loadTags = (query: string) => { 49 | this.query = query 50 | } 51 | 52 | } 53 | 54 | export default AppStore 55 | -------------------------------------------------------------------------------- /src/store/providers/index.ts: -------------------------------------------------------------------------------- 1 | export { UiStore } from './ui-store' 2 | export { AppStore } from './app-store' 3 | -------------------------------------------------------------------------------- /src/store/providers/ui-store.ts: -------------------------------------------------------------------------------- 1 | // import { AppStore } from './' 2 | import { observable, action, computed } from 'mobx' 3 | 4 | export class UiStore { 5 | 6 | @observable 7 | type: string = 'grid' 8 | 9 | constructor (initialState?: any) { 10 | console.log({ initialState }) 11 | } 12 | 13 | @computed 14 | get view_type () { 15 | return this.type 16 | } 17 | 18 | @computed 19 | get getState () { 20 | return this.type 21 | } 22 | 23 | @action 24 | set (type: string) { 25 | this.type = type 26 | } 27 | 28 | } 29 | 30 | export default UiStore 31 | -------------------------------------------------------------------------------- /src/theme/colors.ts: -------------------------------------------------------------------------------- 1 | export const Colors = { 2 | snow: '#fffbfa', 3 | mayaBlue: '#54defd', 4 | seaSerpent: '#49c6e5', 5 | caribbeanGreen: '#00bd9d', 6 | middleBlueGreen: '#8bd7d2', 7 | } 8 | -------------------------------------------------------------------------------- /src/theme/common-styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components' 2 | 3 | export const themeColor = css` 4 | color: green; 5 | ` 6 | 7 | export default { 8 | themeColor, 9 | } 10 | -------------------------------------------------------------------------------- /src/theme/index.ts: -------------------------------------------------------------------------------- 1 | export { Size } from './size' 2 | export { Colors } from './colors' 3 | export { CommonStyles } from './common-styles' 4 | -------------------------------------------------------------------------------- /src/theme/size.ts: -------------------------------------------------------------------------------- 1 | export const Size = { 2 | tiny: 10, 3 | small: 12, 4 | normal: 14, 5 | big: 16, 6 | large: 24, 7 | extraLarge: 32, 8 | } 9 | -------------------------------------------------------------------------------- /src/theme/theme.ts: -------------------------------------------------------------------------------- 1 | // import * as styledComponents from 'styled-components' 2 | // import { ThemedStyledComponentsModule } from 'styled-components' 3 | 4 | // export interface ThemeInterface { 5 | // primaryColor: string; 6 | // } 7 | 8 | // const { 9 | // default: styled, 10 | // css, 11 | // injectGlobal, 12 | // keyframes 13 | // } = styledComponents as ThemedStyledComponentsModule 14 | 15 | // export { css, injectGlobal, keyframes } 16 | 17 | // export default styled 18 | -------------------------------------------------------------------------------- /src/typings/declarations.d.ts: -------------------------------------------------------------------------------- 1 | // shared folder 2 | declare module 'pages' 3 | declare module 'fragments' 4 | declare module 'components' 5 | declare module 'helpers' 6 | declare module 'utils' 7 | declare module 'store' 8 | declare module 'hocs' 9 | declare module 'theme' 10 | declare module 'config' 11 | declare module 'assets' 12 | declare module 'typings' 13 | declare module 'layouts' 14 | declare module 'images' 15 | declare module 'styles' 16 | declare module 'scripts' 17 | declare module 'svgstore' 18 | declare module 'settings' 19 | declare module 'settings/*' 20 | 21 | declare module 'history'; 22 | 23 | declare module 'store2'; 24 | declare module 'storage.io'; 25 | 26 | declare module 'dateformat'; 27 | 28 | declare module 'firebase'; 29 | 30 | declare module 'classNames'; 31 | 32 | declare module 'react-hot-loader' 33 | 34 | declare module 'react-countup' 35 | declare module 'react-animate-number' 36 | declare module 'react-loading-skeleton' 37 | 38 | declare module '@loadable/component' 39 | 40 | declare module 'react-infinite-scroller'; 41 | 42 | declare module 'mobx'; 43 | declare module 'mobx-react'; 44 | declare module 'mobx-logger'; 45 | declare module 'mobx-react-router'; 46 | 47 | declare module 'jest'; 48 | 49 | declare module 'styled-components'; 50 | 51 | declare module 'jest-styled-components'; 52 | 53 | declare module 'enzyme'; 54 | 55 | declare module 'react-table'; 56 | 57 | declare module 'shallowequal'; 58 | 59 | declare module 'warning'; 60 | 61 | declare module 'css-animation*'; 62 | 63 | declare module 'omit.js'; 64 | 65 | declare module 'react-yandex-maps'; 66 | 67 | declare module 'react'; 68 | declare module 'react-dom'; 69 | declare module 'react-helmet'; 70 | declare module 'react-router'; 71 | declare module 'react-router-dom'; 72 | 73 | declare module 'react-lazy-load'; 74 | 75 | declare module 'dom-closest'; 76 | 77 | declare module 'lodash/debounce'; 78 | 79 | declare module 'lodash/uniqBy'; 80 | 81 | declare module 'intersperse'; 82 | 83 | declare module 'raf'; 84 | 85 | declare module 'react-lifecycles-compat'; 86 | 87 | declare module 'react-copy-to-clipboard'; 88 | -------------------------------------------------------------------------------- /src/typings/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: any; 3 | export default content; 4 | } 5 | 6 | declare module '*.png' { 7 | const content: any; 8 | export default content; 9 | } 10 | 11 | declare module '*.jpg'; 12 | declare module '*.webp'; 13 | -------------------------------------------------------------------------------- /src/typings/types.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | __INITIAL_STATE__: any; 3 | __DEVTOOLS_EXTENSION__: () => any; 4 | } 5 | 6 | declare module '*.json' { 7 | const value: any; 8 | export const version: string; 9 | export default value; 10 | } 11 | 12 | declare module 'json-loader!*'; 13 | 14 | declare module '*.css' { 15 | const content: any; 16 | export default content; 17 | } 18 | 19 | declare namespace Express { 20 | interface Request { 21 | todoId: number; 22 | } 23 | } 24 | 25 | declare module '*.scss' { 26 | const _: string; 27 | export default _; 28 | 29 | interface IClassNames { 30 | [className: string]: string 31 | } 32 | } 33 | 34 | declare module '*.woff'; 35 | declare module '*.woff2'; 36 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | // export { Omit } from './type' 2 | export { default as SvgFixer } from './svg-fixer' 3 | export { default as renderRoute } from './render-route' 4 | export { default as loadComponent } from './load-component' 5 | export { default as renderDevTools } from './render-dev-tools' 6 | export { default as OutsideClicked } from './outside-clicked' 7 | -------------------------------------------------------------------------------- /src/utils/load-component/error-display.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export const ErrorDisplay = ({ error }: any) =>
    Oups! {error.message}
    4 | -------------------------------------------------------------------------------- /src/utils/load-component/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import loadable from '@loadable/component' 3 | 4 | import { Loading } from 'fragments' 5 | import { ErrorDisplay } from './error-display' 6 | 7 | const loadComponent = (resolve: any): any => loadable(resolve, { 8 | LoadingComponent: , 9 | rorComponent: ErrorDisplay, 10 | }) 11 | 12 | export default loadComponent 13 | -------------------------------------------------------------------------------- /src/utils/outside-clicked/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export interface P { 4 | children: JSX.Element[] | JSX.Element | any; 5 | } 6 | 7 | export interface S { 8 | opened: null | string; 9 | } 10 | 11 | export default class OutsideClicked extends React.Component { 12 | 13 | wrapper: any = React.createRef() 14 | 15 | state = { 16 | opened: null, 17 | } 18 | 19 | componentDidMount () { 20 | document.addEventListener('mousedown', this.handleClickOutside) 21 | } 22 | 23 | componentWillUnmount () { 24 | document.removeEventListener('mousedown', this.handleClickOutside) 25 | } 26 | 27 | handleClickOutside = (e: Event) => { 28 | if (this.wrapper && !this.wrapper.contains(e.target)) { 29 | this.setState({ 30 | opened: null, 31 | }) 32 | } 33 | } 34 | 35 | onChildClick = (name: string) => { 36 | this.setState({ 37 | opened: name, 38 | }) 39 | } 40 | 41 | render () { 42 | const { opened } = this.state 43 | const { children } = this.props 44 | 45 | return ( 46 |
    47 | {React.cloneElement(children, { 48 | opened, 49 | onChildClick: this.onChildClick, 50 | })} 51 |
    52 | ) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/render-dev-tools/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { environment } from 'settings' 3 | 4 | export default function renderDevTools (noPanel = false) { 5 | if (environment.development) { 6 | const { configureDevtool } = require('mobx-react-devtools') 7 | 8 | configureDevtool({ 9 | logEnabled: true, 10 | updatesEnabled: false, 11 | graphEnabled: false, 12 | logFilter: (change: any) => change.type === 'reaction', 13 | }) 14 | 15 | const DevTools = require('mobx-react-devtools').default 16 | 17 | return 18 | } 19 | 20 | return null 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/render-route/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { Route } from 'react-router-dom' 4 | 5 | import { Helmet } from 'react-helmet' 6 | 7 | // const PrivateRoute = ({ component: Component, ...rest }) => ( 8 | // ( 9 | // fakeAuth.isAuthenticated === true 10 | // ? 11 | // : 12 | // )} /> 13 | // ) 14 | 15 | export default function renderRoute ({ title, keywords, description, component: Component, ...rest }: Route) { 16 | return ( 17 | ( 18 | 19 | 32 | 33 | 34 | 35 | )} /> 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/svg-fixer/index.tsx: -------------------------------------------------------------------------------- 1 | const SvgFixer = () => { 2 | const pattern = 'xlink:href' 3 | const baseUrl: string = window.location.origin || `${window.location.protocol}://${window.location.host}` 4 | const useList: any = document.querySelectorAll('use[*|href]') 5 | 6 | if (Object.keys(useList).length) { 7 | Object.keys(useList).map(id => { 8 | const use = useList[id] 9 | const attributes = use.getAttribute(pattern) 10 | 11 | if (attributes.indexOf('#') === 0) { 12 | use.setAttribute(pattern, baseUrl + attributes) 13 | } 14 | }) 15 | } 16 | } 17 | 18 | export default SvgFixer 19 | -------------------------------------------------------------------------------- /src/utils/type/index.ts: -------------------------------------------------------------------------------- 1 | // // export type Omit = Pick> 2 | // // export type Required = T & { 3 | // // [P in keyof T]: T[P]; 4 | // // } 5 | // // @ts-ignore 6 | // export type Diff = ({[P in T]: P} & {[P in U]: never} & {[x: string]: never})[T] 7 | // // @ts-ignore 8 | // export type Omit = Pick> 9 | // export type Overwrite = { [P in Diff]: T[P] } & U; 10 | // // export type Omit = Pick> 11 | // export type PartialProperties = Partial> & Omit 12 | // // -{ [P in keyof PartialProperties]: PartialProperties[P]; }; // Expected 13 | // // +{ [P in "prop" | Exclude]: PartialProperties[P]; }; // Actual 14 | 15 | // // export type Required = { 16 | // // [P in Purify]: NonNullable; 17 | // // }; 18 | // // export type Purify = { [P in T]: T; }[T]; 19 | // // export type NonNullable = T - 'undefined' - null; 20 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "axios": "registry:dt/axios#0.9.1+20161016142654", 4 | "core-js": "registry:dt/core-js#0.9.0+20170324193834" 5 | }, 6 | "globalDevDependencies": { 7 | "node": "registry:dt/node#7.0.0+20170322231424" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /wallaby.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = (wallaby) => { 2 | 3 | return { 4 | files: [ 5 | 'src/**/*.{js,jsx,ts,tsx}', 6 | ], 7 | tests: [ 8 | 'src/**/__tests__/**/*.(ts|tsx|js|jsx)', 9 | 'src/**/?(*.)(spec|test).(ts|tsx|js|jsx)', 10 | ], 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /webpack/aliases.js: -------------------------------------------------------------------------------- 1 | const define = require("./define"); 2 | const { join, resolve } = require("path"); 3 | 4 | const alias = { 5 | src: define.rs_root, 6 | hocs: resolve(define.rs_root, "hocs"), 7 | utils: resolve(define.rs_root, "utils"), 8 | store: resolve(define.rs_root, "store"), 9 | theme: resolve(define.rs_root, "theme"), 10 | pages: resolve(define.rs_root, "pages"), 11 | config: resolve(define.rs_root, "config"), 12 | typings: resolve(define.rs_root, "typings"), 13 | helpers: resolve(define.rs_root, "helpers"), 14 | layouts: resolve(define.rs_root, "layouts"), 15 | settings: resolve(define.rs_root, "settings"), 16 | fragments: resolve(define.rs_root, "fragments"), 17 | components: resolve(define.rs_root, "components"), 18 | assets: resolve(define.rs_root, "assets"), 19 | images: resolve(define.rs_root, "assets/images"), 20 | styles: resolve(define.rs_root, "assets/styles"), 21 | scripts: resolve(define.rs_root, "assets/scripts"), 22 | svgstore: resolve(define.rs_root, "assets/svgstore"), 23 | 'babel-core': resolve(join(define.rs_node, "/@babel/core")), 24 | }; 25 | 26 | module.exports.config = { 27 | ...alias, 28 | ...(define.rs_preact 29 | ? { 30 | react: "preact-compat", 31 | "react-dom": "preact-compat", 32 | "create-react-class": "preact-compat/lib/create-react-class", 33 | "react-dom-factories": "preact-compat/lib/react-dom-factories" 34 | } 35 | : []) 36 | }; 37 | -------------------------------------------------------------------------------- /webpack/config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | const aliases = require('./aliases') 4 | const rules = require('./rules') 5 | const define = require('./define') 6 | const plugins = require('./plugins') 7 | const entryPoint = require('./entry-point') 8 | 9 | // process.noDeprecation = true; 10 | // process.traceDeprecation = true; 11 | 12 | module.exports = { 13 | context: define.rs_root, 14 | 15 | target: define.rs_target, 16 | 17 | entry: entryPoint.config, 18 | 19 | output: { 20 | publicPath: define.rs_output_path, 21 | sourceMapFilename: '[name].js.map', 22 | jsonpFunction: 'WJ', 23 | hotUpdateFunction: 'UF', 24 | }, 25 | 26 | resolve: { 27 | symlinks: true, 28 | modules: ['node_modules', define.rs_root], 29 | mainFiles: ['index'], 30 | enforceExtension: false, 31 | enforceModuleExtension: false, 32 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.scss', '.css', '.json'], 33 | descriptionFiles: ['package.json', 'bower.json'], 34 | alias: aliases.config, 35 | }, 36 | 37 | resolveLoader: { 38 | modules: ['node_modules'], 39 | }, 40 | 41 | module: { 42 | wrappedContextCritical: true, 43 | strictExportPresence: true, 44 | exprContextCritical: false, 45 | noParse: /jquery|lodash/, 46 | rules: rules.config, 47 | }, 48 | 49 | plugins: plugins.config, 50 | 51 | node: false, 52 | // node: { 53 | // fs: 'empty', 54 | // net: 'empty', 55 | // tls: 'empty', 56 | // global: true, 57 | // crypto: 'empty', 58 | // process: true, 59 | // module: false, 60 | // clearImmediate: false, 61 | // setImmediate: false, 62 | // }, 63 | } 64 | -------------------------------------------------------------------------------- /webpack/dll.config.js: -------------------------------------------------------------------------------- 1 | const { join, resolve } = require('path'); 2 | 3 | const webpack = require('webpack') 4 | const webpackMerge = require('webpack-merge') 5 | const config = require('./config') 6 | 7 | const stats = require('./stats') 8 | const define = require('./define') 9 | const entryPoint = require('./entry-point') 10 | 11 | const optimization = require('./optimization') 12 | const minimizer = require('./minimizer') 13 | 14 | module.exports = webpackMerge(config, { 15 | mode: define.rs_mode, 16 | 17 | devtool: false, 18 | 19 | bail: false, 20 | 21 | stats: stats.config, 22 | 23 | entry: { 24 | bundle: entryPoint.config.bundle, 25 | }, 26 | 27 | output: { 28 | ...config.output, 29 | path: resolve(define.rs_dist, 'dll'), 30 | filename: 'dll.[name].js', 31 | library: '[name]', 32 | devtoolModuleFilenameTemplate: info => relative(define.rs_root, info.absoluteResourcePath).replace(/\\/g, '/'), 33 | }, 34 | 35 | optimization: webpackMerge(optimization, minimizer), 36 | 37 | plugins: [ 38 | ...config.plugins, 39 | new webpack.DllPlugin({ 40 | // context: './', 41 | context: __dirname, 42 | name: '[name]', 43 | path: join(resolve(define.rs_dist, 'dll'), '[name]-manifest.json'), 44 | }), 45 | ], 46 | }) 47 | 48 | // conf.entry = { 49 | // bundle: entryPoint.config.bundle, 50 | // }; 51 | 52 | // module.exports = conf; 53 | -------------------------------------------------------------------------------- /webpack/entry-point.js: -------------------------------------------------------------------------------- 1 | const define = require('./define') 2 | const { resolve } = require('path') 3 | const { polyfills } = require('./polyfills') 4 | 5 | const bundle = [ 6 | // ...polyfills, 7 | 'react', 8 | // 'mobx', 9 | // 'mobx-react' 10 | // 'react-router-dom', 11 | // 'axios' 12 | ] 13 | 14 | const client = [ 15 | ...(define.rs_development 16 | ? [ 17 | 'react-dev-utils/webpackHotDevClient', 18 | // `webpack-dev-server/client?http://${define.rs_host}:${define.rs_port}`, 19 | // 'webpack/hot/only-dev-server', 20 | ] 21 | : []), 22 | resolve(define.rs_root, 'client'), 23 | ] 24 | 25 | const entryPoint = { 26 | bundle, 27 | client, 28 | } 29 | 30 | module.exports.config = entryPoint 31 | -------------------------------------------------------------------------------- /webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const dotenv = require('dotenv') 3 | 4 | const define = require('./define') 5 | 6 | let file = '.env' 7 | 8 | if (define.rs_environment !== 'local') { 9 | file += `.${define.rs_environment}` 10 | } 11 | 12 | const config = dotenv.config({ 13 | path: resolve(process.cwd(), file), 14 | }) 15 | 16 | const formatter = (params, stringify = false) => { 17 | const length = Object.keys(params).length 18 | 19 | if (length && stringify) { 20 | for (const x in params) { 21 | params[x] = JSON.stringify(params[x]) 22 | } 23 | } 24 | 25 | return params 26 | } 27 | 28 | module.exports.config = config.parsed 29 | module.exports.formatter = formatter 30 | -------------------------------------------------------------------------------- /webpack/functions.js: -------------------------------------------------------------------------------- 1 | module.exports.randomInteger = (min, max) => { 2 | const rand = min + Math.random() * (max - min) 3 | return Math.round(rand) 4 | } 5 | 6 | module.exports.parseArguments = argv => { 7 | const data = {} 8 | 9 | argv.map((item, index) => { 10 | if (item.substring(0, 2) === '--') { 11 | const name = item.substring(2) 12 | const value = argv[index + 1] || '' 13 | 14 | data[name] = value 15 | } 16 | }) 17 | 18 | return data 19 | } 20 | -------------------------------------------------------------------------------- /webpack/helpers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const define = require('./define') 3 | const { resolve } = require('path') 4 | 5 | const config = { 6 | hash: false, 7 | cache: define.rs_production, 8 | inject: true, 9 | compile: false, 10 | filetype: 'pug', 11 | prefetch: ['**/*.min.js'], 12 | preload: ['**/*.min.js'], 13 | chunksSortMode: 'dependency', 14 | production: define.rs_production, 15 | minify: define.rs_release && { 16 | html5: true, 17 | caseSensitive: true, 18 | keepClosingSlash: true, 19 | removeComments: true, 20 | decodeEntities: true, 21 | customAttrAssign: true, 22 | collapseWhitespace: true, 23 | removeAttributeQuotes: true, 24 | removeEmptyAttributes: true, 25 | preventAttributesEscaping: true, 26 | processConditionalComments: true, 27 | removeRedundantAttributes: true, 28 | removeScriptTypeAttributes: true, 29 | removeStyleLinkTypeAttributes: true, 30 | collapseBooleanAttributes: true, 31 | collapseInlineTagWhitespace: true, 32 | trimCustomFragments: true, 33 | useShortDoctype: true, 34 | minifyJS: true, 35 | minifyCSS: true, 36 | minifyURLs: true, 37 | }, 38 | } 39 | 40 | const fileContent = function (filePath) { 41 | const content = fs.readFileSync(resolve(define.rs_base, filePath), 'utf8') 42 | return content.toString().replace(/|<\/svg>/gi, '') 43 | } 44 | 45 | module.exports.fileContent = fileContent 46 | 47 | module.exports.generateConfig = (script, template, chunksList) => { 48 | if (!template) { 49 | template = 'app' 50 | } 51 | 52 | config.alwaysWriteToDisk = true 53 | 54 | config.template = [template, 'pug'].join('.') 55 | config.filename = resolve(define.rs_dist, [script, 'html'].join('.')) 56 | // config.svgContext = fileContent('.cache/svgstore/svgstore.svg'); 57 | 58 | config.basePath = define.rs_base_path 59 | 60 | if (chunksList !== undefined) { 61 | config.chunks = chunksList 62 | } 63 | 64 | return config 65 | } 66 | -------------------------------------------------------------------------------- /webpack/jest/fileTransformer.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // This is a custom Jest transformer turning file imports into filenames. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | module.exports = { 6 | process (src, filename) { 7 | return `module.exports = ${JSON.stringify(path.basename(filename))};` 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /webpack/jest/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines the React 16 Adapter for Enzyme. 3 | * 4 | * @link http://airbnb.io/enzyme/docs/installation/#working-with-react-16 5 | * @copyright 2017 Airbnb, Inc. 6 | */ 7 | const enzyme = require('enzyme') 8 | const Adapter = require('enzyme-adapter-react-16') 9 | 10 | enzyme.configure({ adapter: new Adapter() }) 11 | -------------------------------------------------------------------------------- /webpack/jest/shim.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get rids of the missing requestAnimationFrame polyfill warning. 3 | * 4 | * @link https://reactjs.org/docs/javascript-environment-requirements.html 5 | * @copyright 2004-present Facebook. All Rights Reserved. 6 | */ 7 | global.requestAnimationFrame = function (callback) { 8 | setTimeout(callback, 0) 9 | } 10 | -------------------------------------------------------------------------------- /webpack/jest/tsPreprocessor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transpiles TypeScript to JavaScript code. 3 | * 4 | * @link https://github.com/facebook/jest/blob/master/examples/typescript/preprocessor.js 5 | * @copyright 2004-present Facebook. All Rights Reserved. 6 | */ 7 | const tsc = require('typescript') 8 | const tsConfig = require('../../tsconfig.json') 9 | 10 | module.exports = { 11 | process (src, path) { 12 | if (path.endsWith('.ts') || path.endsWith('.tsx') || path.endsWith('.js')) { 13 | return tsc.transpile(src, tsConfig.compilerOptions, path, []) 14 | } 15 | return src 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /webpack/log.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | 3 | // Combine styled and normal strings 4 | log(chalk.blue('Hello') + ' World' + chalk.red('!')); 5 | 6 | // Compose multiple styles using the chainable API 7 | log(chalk.blue.bgRed.bold('Hello world!')); 8 | 9 | // Pass in multiple arguments 10 | log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz')); 11 | 12 | // Nest styles 13 | log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!')); 14 | 15 | // Nest styles of the same type even (color, underline, background) 16 | log(chalk.green( 17 | 'I am a green line ' + 18 | chalk.blue.underline.bold('with a blue substring') + 19 | ' that becomes green again!' 20 | )); 21 | 22 | // ES2015 template literal 23 | log(` 24 | CPU: ${chalk.red('90%')} 25 | RAM: ${chalk.green('40%')} 26 | DISK: ${chalk.yellow('70%')} 27 | `); 28 | 29 | // // ES2015 tagged template literal 30 | // log(chalk` 31 | // CPU: {red ${cpu.totalPercent}%} 32 | // RAM: {green ${ram.used / ram.total * 100}%} 33 | // DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%} 34 | // `); 35 | 36 | // Use RGB colors in terminal emulators that support it. 37 | log(chalk.keyword('orange')('Yay for orange colored text!')); 38 | log(chalk.rgb(123, 45, 67).underline('Underlined reddish color')); 39 | log(chalk.hex('#DEADED').bold('Bold gray!')); 40 | 41 | export const log = (message) => console.log(message) 42 | -------------------------------------------------------------------------------- /webpack/minimizer.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | const webpack = require('webpack') 4 | const define = require('./define') 5 | 6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 7 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') 8 | const PostCssSafeParser = require('postcss-safe-parser'); 9 | 10 | const uglifyJsOptions = { 11 | ie8: false, 12 | ecma: 5, 13 | mangle: true, 14 | warnings: define.rs_development, 15 | toplevel: false, 16 | keep_fnames: false, 17 | parse: { 18 | html5_comments: false, 19 | }, 20 | compress: { 21 | inline: false, 22 | sequences: true, 23 | // properties: true, 24 | comparisons: true, 25 | // comparisons: false, 26 | conditionals: true, 27 | evaluate: true, 28 | booleans: true, 29 | loops: true, 30 | unused: true, 31 | unsafe: false, 32 | warnings: false, 33 | hoist_funs: true, 34 | if_return: true, 35 | join_vars: true, 36 | // negate_iife: true, 37 | dead_code: define.rs_release, 38 | drop_console: define.rs_release, 39 | drop_debugger: define.rs_release, 40 | global_defs: { 41 | DEBUG: false, 42 | }, 43 | }, 44 | output: { 45 | ascii_only: true, 46 | comments: false, 47 | beautify: false, 48 | indent_level: 0, 49 | }, 50 | } 51 | 52 | module.exports = { 53 | minimizer: [ 54 | new UglifyJsPlugin({ 55 | test: /\.m?js$/, 56 | cache: true, 57 | parallel: true, 58 | sourceMap: define.rs_sourceMap, 59 | extractComments: false, 60 | uglifyOptions: uglifyJsOptions, 61 | }), 62 | new OptimizeCSSAssetsPlugin({ 63 | cssProcessorOptions: { 64 | parser: PostCssSafeParser, 65 | map: { 66 | // `inline: false` forces the sourcemap to be output into a 67 | // separate file 68 | inline: false, 69 | // `annotation: true` appends the sourceMappingURL to the end of 70 | // the css file, helping the browser find the sourcemap 71 | annotation: true, 72 | } 73 | }, 74 | }), 75 | ], 76 | } 77 | -------------------------------------------------------------------------------- /webpack/optimization.js: -------------------------------------------------------------------------------- 1 | const define = require('./define') 2 | 3 | module.exports = { 4 | minimize: define.rs_release, 5 | chunkIds: 'named', 6 | nodeEnv: define.rs_mode, 7 | sideEffects: true, 8 | usedExports: true, 9 | providedExports: false, 10 | concatenateModules: true, 11 | runtimeChunk: { 12 | name: 'startup', 13 | }, 14 | splitChunks: { 15 | cacheGroups: { 16 | default: { 17 | minChunks: 2, 18 | priority: -20, 19 | reuseExistingChunk: true, 20 | }, 21 | bundle: { 22 | test: /[\\/]node_modules[\\/]/, 23 | chunks: 'all', 24 | name: 'bundle', 25 | priority: -10, 26 | enforce: true, 27 | }, 28 | }, 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /webpack/plugins.js: -------------------------------------------------------------------------------- 1 | const plugins = [] 2 | const define = require('./define') 3 | 4 | if (define.rs_development) { 5 | plugins.push(...require('./plugins/hard-cache').config) 6 | plugins.push(...require('./plugins/development').config) 7 | } 8 | 9 | plugins.push( 10 | ...require('./plugins/lint').config, 11 | ...require('./plugins/general').config 12 | ) 13 | 14 | if (define.rs_production) { 15 | plugins.push(...require('./plugins/production').config) 16 | } 17 | 18 | if (define.rs_release || define.rs_deploy) { 19 | plugins.push( 20 | ...require('./plugins/compression').config, 21 | ...require('./plugins/manifest').config, 22 | ...require('./plugins/precache').config 23 | ) 24 | } 25 | 26 | if (define.rs_analyzer) { 27 | plugins.push( 28 | ...require('./plugins/visualizer').config, 29 | ...require('./plugins/analyzer').config 30 | ) 31 | } 32 | 33 | module.exports.config = plugins 34 | -------------------------------------------------------------------------------- /webpack/plugins/analyzer.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const define = require('../define') 3 | const { resolve } = require('path') 4 | 5 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 6 | 7 | const plugins = [ 8 | new BundleAnalyzerPlugin({ 9 | // Can be `server`, `static` or `disabled`. 10 | // In `server` mode analyzer will start HTTP server to show bundle report. 11 | // In `static` mode single HTML file with bundle report will be generated. 12 | // In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting `generateStatsFile` to `true`. 13 | analyzerMode: 'static', 14 | // Path to bundle report file that will be generated in `static` mode. 15 | // Relative to bundles output directory. 16 | reportFilename: resolve(define.rs_stats, 'report.html'), 17 | // Automatically open report in default browser 18 | openAnalyzer: true, 19 | // If `true`, Webpack Stats JSON file will be generated in bundles output directory 20 | generateStatsFile: true, 21 | // Name of Webpack Stats JSON file that will be generated if `generateStatsFile` is `true`. 22 | // Relative to bundles output directory. 23 | statsFilename: resolve(define.rs_stats, 'stats.json'), 24 | // Options for `stats.toJson()` method. 25 | // For example you can exclude sources of your modules from stats file with `source: false` option. 26 | // See more options here: https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21 27 | statsOptions: null, 28 | // Log level. Can be 'info', 'warn', 'error' or 'silent'. 29 | logLevel: 'info', 30 | }), 31 | ] 32 | 33 | module.exports.config = plugins 34 | -------------------------------------------------------------------------------- /webpack/plugins/bower.js: -------------------------------------------------------------------------------- 1 | const define = require('../define') 2 | const BowerWebpackPlugin = require('bower-webpack-plugin') 3 | 4 | const plugins = [ 5 | new BowerWebpackPlugin({ 6 | modulesDirectories: ['bower_components'], 7 | manifestFiles: 'bower.json', 8 | includes: /.*/, 9 | excludes: [], 10 | searchResolveModulesDirectories: true, 11 | }), 12 | ] 13 | 14 | module.exports.config = plugins 15 | -------------------------------------------------------------------------------- /webpack/plugins/compression.js: -------------------------------------------------------------------------------- 1 | const BrotliGzipPlugin = require('brotli-gzip-webpack-plugin') 2 | // const CompressionPlugin = require("compression-webpack-plugin") 3 | 4 | const threshold = 0 5 | 6 | const plugins = [ 7 | new BrotliGzipPlugin({ 8 | test: /\.(js|cs{2}|html|svg)$/, 9 | asset: '[path].br[query]', 10 | algorithm: 'brotli', 11 | threshold, 12 | minRatio: 0.8, 13 | quality: 11, 14 | deleteOriginalAssets: true, 15 | }), 16 | new BrotliGzipPlugin({ 17 | test: /\.(js|cs{2}|html|svg)$/, 18 | asset: '[path].gz[query]', 19 | algorithm: 'gzip', 20 | threshold, 21 | minRatio: 0.8, 22 | deleteOriginalAssets: true, 23 | }), 24 | // new CompressionPlugin({ 25 | // test: /\.(js|css|html|svg)$/, 26 | // asset: '[path].gz[query]', 27 | // algorithm: 'gzip', 28 | // threshold: threshold, 29 | // minRatio: 0.8, 30 | // deleteOriginalAssets: true 31 | // }) 32 | ] 33 | 34 | module.exports.config = plugins 35 | -------------------------------------------------------------------------------- /webpack/plugins/development.js: -------------------------------------------------------------------------------- 1 | const define = require('../define') 2 | const webpack = require('webpack') 3 | const WebpackNotifierPlugin = require('webpack-notifier') 4 | const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin') 5 | 6 | const plugins = [ 7 | new webpack.WatchIgnorePlugin([/cs{2}\.d\.ts$/, /scs{2}\.d\.ts$/, /node_modules/]), 8 | 9 | new WebpackNotifierPlugin({ alwaysNotify: true }), 10 | 11 | new CaseSensitivePathsPlugin(), 12 | 13 | // make hot reloading work 14 | new webpack.HotModuleReplacementPlugin(), 15 | 16 | // show module names instead of numbers in webpack stats 17 | new webpack.NamedModulesPlugin(), 18 | 19 | // don't spit out any errors in compiled assets 20 | new webpack.NoEmitOnErrorsPlugin(), 21 | ] 22 | 23 | module.exports.config = plugins 24 | -------------------------------------------------------------------------------- /webpack/plugins/dll.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const webpack = require('webpack') 3 | 4 | const plugins = [ 5 | new webpack.DllReferencePlugin({ 6 | context: __dirname, 7 | manifest: require('./manifest.json'), 8 | // path: join(resolve(define.rs_dist, 'dll'), '[name]-manifest.json'), 9 | // name: './my-dll.js', 10 | // scope: 'xyz', 11 | sourceType: 'commonjs2' 12 | }) 13 | ] 14 | 15 | module.exports.config = plugins 16 | -------------------------------------------------------------------------------- /webpack/plugins/hard-cache.js: -------------------------------------------------------------------------------- 1 | const { join, resolve } = require('path') 2 | 3 | const HardSourceWebpackPlugin = require('hard-source-webpack-plugin') 4 | 5 | const define = require('../define') 6 | 7 | const cacheDirectory = join(define.rs_cachePath, `/hard-source/${define.rs_environment}/[confighash]`) 8 | 9 | const plugins = [ 10 | new HardSourceWebpackPlugin({ 11 | cacheDirectory, 12 | configHash: (webpackConfig) => ( 13 | require('node-object-hash')({ 14 | sort: false 15 | }).hash(webpackConfig) 16 | ), 17 | environmentHash: { 18 | root: process.cwd(), 19 | files: ['yarn.lock', 'package-lock.json', 'npm-shrinkwrap.json'], 20 | directories: ['node_modules'], 21 | }, 22 | // info: { 23 | // mode: 'none', 24 | // level: 'debug', 25 | // }, 26 | cachePrune: { 27 | // Caches younger than `maxAge` are not considered for deletion. They must 28 | // be at least this (default: 2 days) old in milliseconds. 29 | maxAge: 2 * 24 * 60 * 60 * 1000, 30 | // All caches together must be larger than `sizeThreshold` before any 31 | // caches will be deleted. Together they must be at least this 32 | // (default: 50 MB) big in bytes. 33 | sizeThreshold: 50 * 1024 * 1024 34 | }, 35 | }), 36 | ] 37 | 38 | module.exports.config = plugins 39 | -------------------------------------------------------------------------------- /webpack/plugins/lint.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | 3 | const plugins = [] 4 | 5 | module.exports.config = plugins 6 | -------------------------------------------------------------------------------- /webpack/plugins/prebuild.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | 3 | const plugins = [] 4 | 5 | module.exports.config = plugins 6 | -------------------------------------------------------------------------------- /webpack/plugins/production.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const define = require('../define') 3 | 4 | const Critters = require('critters-webpack-plugin') 5 | const WebpackChunkHash = require('webpack-chunk-hash') 6 | const WebpackManifestPlugin = require('webpack-manifest-plugin') 7 | const ReplacePlugin = require('replace-bundle-webpack-plugin') 8 | 9 | const plugins = [ 10 | new webpack.SourceMapDevToolPlugin({ 11 | filename: '[name].js.map' 12 | }), 13 | 14 | new webpack.debug.ProfilingPlugin({ 15 | outputPath: 'profiling/events.json' 16 | }), 17 | 18 | new WebpackChunkHash(), 19 | 20 | new webpack.optimize.ModuleConcatenationPlugin(), 21 | 22 | new webpack.optimize.AggressiveMergingPlugin({ 23 | minSizeReduce: 1.6, 24 | }), 25 | 26 | new webpack.IgnorePlugin(/^(react-dev-utils|mobx-react-devtools)$/), 27 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 28 | 29 | new WebpackManifestPlugin({ 30 | basePath: define.rs_output_path, 31 | fileName: '../webpack-manifest.json', 32 | }), 33 | 34 | new Critters({ 35 | fonts: true, 36 | preload: 'swap"', 37 | preloadFonts: true, 38 | mergeStylesheets: false, 39 | }), 40 | 41 | // /(en-gb|en|ru)/ 42 | new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /ru|en/), 43 | 44 | new ReplacePlugin([ 45 | { 46 | pattern: /\/users\/lnked\/web\/[\s\w\-.]+\/(src\/)?[\s\w\-./<>]+/gi, 47 | replacement: () => { 48 | return '' 49 | }, 50 | }, 51 | ]), 52 | 53 | new webpack.HashedModuleIdsPlugin(), 54 | ] 55 | 56 | module.exports.config = plugins 57 | -------------------------------------------------------------------------------- /webpack/plugins/static.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const ReactStaticPlugin = require('react-static-webpack-plugin') 3 | 4 | // https://www.npmjs.com/package/react-static-webpack-plugin 5 | 6 | const plugins = [ 7 | new ReactStaticPlugin({ 8 | routes: 'settings/routes.js', // Path to routes file 9 | template: 'app.pug', // Path to JSX template file 10 | }), 11 | ] 12 | 13 | module.exports.config = plugins 14 | -------------------------------------------------------------------------------- /webpack/plugins/visualizer.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | const define = require('../define') 3 | const Visualizer = require('webpack-visualizer-plugin') 4 | 5 | // const currentDateTime = new Date(); 6 | // const currentDate = currentDateTime.toLocaleDateString('en-GB').replace(/\//g, "-"); 7 | // const currentTime = currentDateTime.toLocaleTimeString('en-GB', { hour12: false }).replace(/:/g, "-"); 8 | // const filename = join('../stats', `statistics-${currentDate + '-' + currentTime}.html`); 9 | 10 | const filename = join('../stats', 'statistics.html') 11 | 12 | const plugins = [ 13 | new Visualizer({ 14 | filename, 15 | }), 16 | ] 17 | 18 | module.exports.config = plugins 19 | -------------------------------------------------------------------------------- /webpack/prebuild.js: -------------------------------------------------------------------------------- 1 | const { join, resolve } = require('path') 2 | 3 | const webpack = require('webpack') 4 | const define = require('./define') 5 | 6 | module.exports = { 7 | mode: 'production', 8 | 9 | context: define.rs_root, 10 | 11 | entry: resolve(define.rs_root, 'svgstore.jsx'), 12 | 13 | output: { 14 | path: define.rs_root, 15 | filename: join(define.rs_cachePath, '/svgstore/[name].js'), 16 | }, 17 | 18 | plugins: require('./plugins/prebuild').config, 19 | 20 | stats: require('./stats').config, 21 | } 22 | -------------------------------------------------------------------------------- /webpack/production.config.js: -------------------------------------------------------------------------------- 1 | const { resolve, relative } = require('path') 2 | 3 | const webpack = require('webpack') 4 | const webpackMerge = require('webpack-merge') 5 | const config = require('./config') 6 | 7 | const stats = require('./stats') 8 | const define = require('./define') 9 | 10 | const optimization = require('./optimization') 11 | const minimizer = require('./minimizer') 12 | 13 | module.exports = webpackMerge(config, { 14 | mode: 'production', 15 | 16 | devtool: 'cheap-module-source-map', 17 | 18 | bail: true, 19 | 20 | stats: stats.config, 21 | 22 | output: { 23 | ...config.output, 24 | pathinfo: false, 25 | path: resolve(define.rs_dist, 'assets'), 26 | filename: 'js/[name].[chunkhash:7].js', 27 | chunkFilename: 'js/[name].[chunkhash:7].min.js', 28 | devtoolModuleFilenameTemplate: info => relative(define.rs_root, info.absoluteResourcePath).replace(/\\/g, '/'), 29 | }, 30 | 31 | performance: define.rs_release && { 32 | hints: 'warning', 33 | maxAssetSize: 500000, 34 | maxEntrypointSize: 500000, 35 | assetFilter: assetFilename => !/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename), 36 | }, 37 | 38 | optimization: webpackMerge(optimization, minimizer), 39 | }) 40 | -------------------------------------------------------------------------------- /webpack/proxy.js: -------------------------------------------------------------------------------- 1 | module.exports.config = [ 2 | // { 3 | // path: '**', 4 | // target: 'http://localhost:3000', 5 | // }, 6 | // "/subject": "http://localhost:3000", 7 | // "/tag": "http://localhost:3000", 8 | // "/book": "http://localhost:3000", 9 | // "/static": "http://localhost:3000" 10 | // '/some/path': { 11 | // target: 'https://other-server.example.com', 12 | // secure: false, 13 | // pathRewrite: {'^/api' : ''}, 14 | // bypass: function(req, res, proxyOptions) { 15 | // if (req.headers.accept.indexOf('html') !== -1) { 16 | // console.log('Skipping proxy for browser request.'); 17 | // return '/index.html'; 18 | // } 19 | // } 20 | // }, 21 | // '/api': { 22 | // target: 'https://other-server.example.com', 23 | // secure: false 24 | // }, 25 | // { 26 | // context: ['/api-v1/**', '/api-v2/**'], 27 | // target: 'https://other-server.example.com', 28 | // secure: false 29 | // } 30 | ] 31 | -------------------------------------------------------------------------------- /webpack/rules.js: -------------------------------------------------------------------------------- 1 | module.exports.config = [ 2 | ...require('./rules/files').config, 3 | ...require('./rules/media').config, 4 | ...require('./rules/images').config, 5 | ...require('./rules/styles').config, 6 | ...require('./rules/fonts').config, 7 | ...require('./rules/scripts').config, 8 | ...require('./rules/template').config, 9 | ...require('./rules/markdown').config 10 | ] 11 | -------------------------------------------------------------------------------- /webpack/rules/files.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const define = require('../define') 3 | 4 | const rules = [ 5 | { 6 | test: define.rs_regexp_files, 7 | use: ['file-loader'], 8 | }, 9 | { 10 | test: /\.txt$/, 11 | use: ['raw-loader'], 12 | }, 13 | { 14 | test: /\.json$/, 15 | exclude: /(node_modules|bower_components)/, 16 | use: [ 17 | { 18 | loader: 'file-loader', 19 | options: { 20 | name: '[name].[ext]', 21 | }, 22 | }, 23 | { 24 | loader: 'json-loader', 25 | }, 26 | ], 27 | }, 28 | { 29 | test: /\.xml$/, 30 | use: [ 31 | { 32 | loader: 'xml-loader', 33 | options: { 34 | name: '[name].[ext]', 35 | explicitChildren: false, 36 | }, 37 | }, 38 | ], 39 | }, 40 | ] 41 | 42 | module.exports.config = rules 43 | -------------------------------------------------------------------------------- /webpack/rules/fonts.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const define = require('../define') 3 | const name = define.rs_development ? '[path][name].[ext]?[hash:8]' : '[hash:4].[ext]' 4 | 5 | const rules = [ 6 | { 7 | test: /\.(wof{2}(2)?)(\?v=(?:\d+\.){2}\d+)?$/, 8 | use: { 9 | loader: 'url-loader', 10 | options: { 11 | name, 12 | // Limit at 25k. Above that it emits separate files 13 | limit: 25 * 1024, 14 | // url-loader sets mimetype if it's passed. 15 | // Without this it derives it from the file extension 16 | mimetype: 'application/font-woff', 17 | // Output below fonts directory 18 | // name: "./fonts/[name].[ext]", 19 | outputPath: 'fonts/', 20 | }, 21 | }, 22 | include: resolve(define.rs_root, '../src/assets/fonts'), 23 | }, 24 | { 25 | test: /\.t{2}f(\?v=(?:\d+\.){2}\d+)?$/, 26 | loader: 'url-loader', 27 | options: { 28 | name, 29 | limit: 10 * 1024, 30 | mimetype: 'application/octet-stream', 31 | outputPath: 'fonts/', 32 | }, 33 | include: resolve(define.rs_root, '../src/assets/fonts'), 34 | }, 35 | { 36 | test: /\.(eot|otf)(\?v=(?:\d+\.){2}\d+)?$/, 37 | loader: 'file-loader', 38 | options: { 39 | name, 40 | outputPath: 'fonts/', 41 | }, 42 | include: resolve(define.rs_root, '../src/assets/fonts'), 43 | }, 44 | { 45 | test: /\.svg(\?v=(?:\d+\.){2}\d+)?$/, 46 | loader: 'url-loader', 47 | options: { 48 | name, 49 | limit: 10 * 1024, 50 | mimetype: 'image/svg+xml', 51 | outputPath: 'fonts/', 52 | }, 53 | include: resolve(define.rs_root, '../src/assets/fonts'), 54 | }, 55 | ] 56 | 57 | module.exports.config = rules 58 | -------------------------------------------------------------------------------- /webpack/rules/markdown.js: -------------------------------------------------------------------------------- 1 | const marked = require('marked') 2 | 3 | const rules = [ 4 | { 5 | test: /\.md$/, 6 | use: [ 7 | { 8 | loader: 'html-loader', 9 | }, 10 | { 11 | loader: 'markdown-loader', 12 | options: { 13 | pedantic: true, 14 | renderer: new marked.Renderer(), 15 | }, 16 | }, 17 | ], 18 | }, 19 | ] 20 | 21 | module.exports.config = rules 22 | -------------------------------------------------------------------------------- /webpack/rules/media.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const define = require('../define') 3 | 4 | const rules = [ 5 | { 6 | test: define.rs_regexp_medias, 7 | use: [ 8 | { 9 | loader: 'file-loader', 10 | options: { 11 | name: define.rs_development ? '[path][name].[ext]?[hash:8]' : 'media/[hash:5].[ext]', 12 | }, 13 | }, 14 | ], 15 | }, 16 | ] 17 | 18 | module.exports.config = rules 19 | -------------------------------------------------------------------------------- /webpack/rules/scripts.js: -------------------------------------------------------------------------------- 1 | const { join, resolve } = require('path') 2 | 3 | const define = require('../define') 4 | 5 | const tsConfig = [] 6 | const jsConfig = [] 7 | 8 | jsConfig.push({ 9 | loader: 'cache-loader', 10 | options: { 11 | cacheDirectory: join(define.rs_cachePath, '/cache-loader'), 12 | }, 13 | }) 14 | 15 | jsConfig.push({ 16 | loader: 'babel-loader', 17 | options: { 18 | envName: process.env.BABEL_ENV || process.env.NODE_ENV || 'development', 19 | configFile: resolve(define.rs_base, 'babel.config.js'), 20 | compact: define.rs_production, 21 | cacheDirectory: join(define.rs_cachePath, '/babel'), 22 | }, 23 | }) 24 | 25 | tsConfig.push({ 26 | loader: 'awesome-typescript-loader', 27 | options: { 28 | useBabel: false, 29 | useCache: false, 30 | forkChecker: true, 31 | happyPackMode: !!define.rs_parallel, 32 | transpileOnly: true, 33 | useWebpackText: true, 34 | errorsAsWarnings: true, 35 | useTranspileModule: true, 36 | forceIsolatedModules: false, 37 | configFileName: resolve(define.rs_base, 'tsconfig.json'), 38 | }, 39 | }) 40 | 41 | const rules = [ 42 | { 43 | enforce: 'pre', 44 | test: define.rs_sourceMap, 45 | test: define.rs_regexp_scripts, 46 | options: { 47 | fix: false, 48 | }, 49 | loader: 'eslint-loader', 50 | include: define.rs_root, 51 | }, 52 | { 53 | enforce: 'pre', 54 | test: /\.tsx?$/, 55 | options: { 56 | fix: false, 57 | }, 58 | loader: 'tslint-loader', 59 | include: define.rs_root, 60 | }, 61 | { 62 | enforce: 'pre', 63 | test: define.rs_regexp_scripts, 64 | loader: 'source-map-loader', 65 | exclude: /(node_modules|bower_components)/, 66 | }, 67 | { 68 | test: define.rs_regexp_scripts, 69 | include: resolve('src'), 70 | exclude: /(node_modules|bower_components)/, 71 | use: define.rs_parallel ? 'happypack/loader' : jsConfig, 72 | }, 73 | { 74 | test: /\.tsx?$/, 75 | // exclude: [/(node_modules|bower_components)/, /\.(spec|e2e)\.ts(x?)$/], 76 | // exclude: [/(node_modules|bower_components)/], 77 | use: [...jsConfig, ...tsConfig], 78 | }, 79 | ] 80 | 81 | module.exports.config = rules 82 | module.exports.loaders = jsConfig 83 | -------------------------------------------------------------------------------- /webpack/rules/template.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const define = require('../define') 3 | 4 | const rules = [ 5 | { 6 | test: /\.pug$/, 7 | use: [ 8 | { 9 | loader: 'pug-loader', 10 | options: { 11 | pretty: define.rs_development, 12 | }, 13 | }, 14 | ], 15 | include: define.rs_root, 16 | }, 17 | { 18 | test: /\.html$/, 19 | use: [ 20 | { 21 | loader: 'prerender-loader', 22 | options: { 23 | string: true, 24 | }, 25 | }, 26 | ], 27 | include: define.rs_root, 28 | }, 29 | ] 30 | 31 | module.exports.config = rules 32 | -------------------------------------------------------------------------------- /webpack/server.express.js: -------------------------------------------------------------------------------- 1 | // simple express server 2 | 3 | let app 4 | 5 | let server 6 | 7 | const express = require('express') 8 | 9 | const path = require('path') 10 | 11 | const host = process.env.HOST || '127.0.0.1' 12 | 13 | const port = process.env.PORT || 3000 14 | 15 | const root = path.resolve(__dirname) 16 | 17 | app = express() 18 | app.use((req, res, next) => { 19 | console.log(req.url) 20 | next() 21 | }) 22 | app.use(express.static(`${root}/dist`)) 23 | server = app.listen(port, host, serverStarted) 24 | 25 | function serverStarted () { 26 | console.log('Server started', `${host}:${port}`) 27 | console.log('Root directory', root) 28 | console.log('Press Ctrl+C to exit...\n') 29 | } 30 | -------------------------------------------------------------------------------- /webpack/server.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const WebpackDevServer = require('webpack-dev-server') 3 | const signale = require('signale') 4 | 5 | const stats = require('./stats') 6 | const define = require('./define') 7 | const config = require('./development.config') 8 | 9 | const compiler = webpack(config) 10 | 11 | const server = new WebpackDevServer(compiler, { 12 | contentBase: define.rs_contentBase, 13 | hot: true, 14 | open: true, 15 | inline: true, 16 | publicPath: config.output.publicPath, 17 | overlay: { 18 | errors: true, 19 | warnings: true, 20 | }, 21 | progress: false, 22 | compress: true, 23 | watchContentBase: false, 24 | disableHostCheck: true, 25 | historyApiFallback: { 26 | disableDotRule: true, 27 | }, 28 | watchOptions: { 29 | aggregateTimeout: 300, 30 | poll: 1000, 31 | ignored: /node_modules/, 32 | }, 33 | stats: stats.config, 34 | // proxy: proxy.config, 35 | }) 36 | 37 | server.listen(define.rs_port, define.rs_host, (err, result) => { 38 | if (err) { 39 | signale.debug(new Error(err)) 40 | return signale.fatal(new Error(err)) 41 | } 42 | 43 | signale.watch('Recursively watching build directory...') 44 | signale.success(`Listening at http://${define.rs_host}:${define.rs_port}/`) 45 | }) 46 | -------------------------------------------------------------------------------- /webpack/server.new.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const express = require('express') 3 | const webpack = require('webpack') 4 | const config = require('./webpack.config') 5 | 6 | const app = express() 7 | const compiler = webpack(config) 8 | 9 | app.use( 10 | require('webpack-dev-middleware')(compiler, { 11 | noInfo: true, 12 | publicPath: config.output.publicPath, 13 | }) 14 | ) 15 | 16 | app.use(require('webpack-hot-middleware')(compiler)) 17 | 18 | app.use('/assets', express.static(`${__dirname}/dist/assets`)) 19 | app.use('/static', express.static(`${__dirname}/dist/static`)) 20 | 21 | app.get('*', (req, res) => { 22 | res.sendFile(path.join(__dirname, 'dist/index.html')) 23 | }) 24 | 25 | app.listen(3030, 'localhost', (err) => { 26 | if (err) { 27 | console.log(err) 28 | return 29 | } 30 | 31 | console.log('Listening at http://localhost:3030') 32 | }) 33 | -------------------------------------------------------------------------------- /webpack/server0.js: -------------------------------------------------------------------------------- 1 | const hotClient = require('webpack-hot-client') 2 | const middleware = require('webpack-dev-middleware') 3 | const webpack = require('webpack') 4 | 5 | const stats = require('./stats') 6 | const define = require('./define') 7 | const config = require('./development.config') 8 | 9 | const compiler = webpack(config) 10 | const { publicPath } = config.output 11 | 12 | const options = { 13 | hmr: true, 14 | host: define.rs_host, 15 | port: define.rs_port, 16 | reload: true, 17 | https: false, 18 | autoConfigure: false, 19 | } 20 | 21 | const client = hotClient(compiler, options) 22 | const { server } = client 23 | 24 | const app = new (require('express'))() 25 | 26 | server.on('listening', () => { 27 | app.use( 28 | middleware(compiler, { 29 | publicPath, 30 | watchOptions: { 31 | aggregateTimeout: 300, 32 | }, 33 | serverSideRender: true, 34 | stats: stats.config, 35 | }) 36 | ) 37 | 38 | app.use( 39 | middleware(compiler, { 40 | log: console.log, 41 | path: '/__webpack_hmr', 42 | heartbeat: 10 * 1000, 43 | }) 44 | ) 45 | 46 | app.get('/', (req, res) => { 47 | res.sendFile(`${define.rs_contentBase}/index.html`) 48 | }) 49 | 50 | app.listen(define.rs_port, () => console.log('Example app listening on port 3000!')) 51 | }) 52 | -------------------------------------------------------------------------------- /webpack/server2.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const webpackDevMiddleware = require('webpack-dev-middleware') 3 | const webpackHotMiddleware = require('webpack-hot-middleware') 4 | 5 | const stats = require('./stats') 6 | const define = require('./define') 7 | const config = require('./development.config') 8 | 9 | const app = new (require('express'))() 10 | const port = 3000 11 | 12 | const compiler = webpack(config) 13 | 14 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })) 15 | app.use(webpackHotMiddleware(compiler)) 16 | 17 | app.get('/', (req, res) => { 18 | res.sendFile(`${define.rs_contentBase}/index.html`) 19 | }) 20 | 21 | app.listen(define.rs_port, error => { 22 | if (error) { 23 | console.error(error) 24 | } else { 25 | console.info( 26 | '==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.', 27 | define.rs_port, 28 | define.rs_port 29 | ) 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /webpack/stats.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | assets: true, 3 | children: false, 4 | chunks: false, 5 | hash: false, 6 | modules: false, 7 | publicPath: false, 8 | chunkGroups: true, 9 | timings: true, 10 | version: false, 11 | warnings: true, 12 | optimizationBailout: true, 13 | colors: { 14 | green: '\u001b[32m', 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /webpack/svgo.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | plugins: [ 3 | { removeTitle: true }, 4 | { removeDesc: true }, 5 | { removeViewBox: false }, 6 | { convertPathData: false }, 7 | { removeEmptyAttrs: false }, 8 | { removeDoctype: true }, 9 | { removeMetadata: true }, 10 | { removeComments: true }, 11 | { removeUselessDefs: true }, 12 | { removeXMLProcInst: true }, 13 | { removeDimensions: true }, 14 | { 15 | cleanupNumericValues: { 16 | floatPrecision: 2, 17 | }, 18 | }, 19 | { 20 | cleanupIDs: { 21 | prefix: '-', 22 | minify: false, 23 | }, 24 | }, 25 | { 26 | convertColors: { 27 | names2hex: true, 28 | shorthex: false, 29 | rgb2hex: true, 30 | }, 31 | }, 32 | { removeUselessStrokeAndFill: false }, 33 | ], 34 | } 35 | --------------------------------------------------------------------------------