├── .eslintrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── Makefile ├── assets ├── css │ └── style.css ├── img │ ├── featured-plugins │ │ ├── browsi-1x.png │ │ ├── browsi-2x.png │ │ ├── chartbeat-1x.png │ │ ├── chartbeat-2x.png │ │ ├── co-schedule-1x.png │ │ ├── co-schedule-2x.png │ │ ├── facebook-1x.png │ │ ├── facebook-2x.png │ │ ├── findthebest-1x.png │ │ ├── findthebest-2x.png │ │ ├── getty-images-1x.png │ │ ├── getty-images-2x.png │ │ ├── janrain-capture-1x.png │ │ ├── janrain-capture-2x.png │ │ ├── jwplayer-1x.png │ │ ├── jwplayer-2x.png │ │ ├── livefyre-apps-1x.png │ │ ├── livefyre-apps-2x.png │ │ ├── mediapass-1x.png │ │ ├── mediapass-2x.png │ │ ├── newscred-1x.png │ │ ├── newscred-2x.png │ │ ├── ooyala-1x.png │ │ ├── ooyala-2x.png │ │ ├── postrelease-vip-1x.png │ │ ├── postrelease-vip-2x.png │ │ ├── publishthis-1x.png │ │ ├── publishthis-2x.png │ │ ├── sailthru-1x.png │ │ ├── sailthru-2x.png │ │ ├── shoplocket-1x.png │ │ ├── shoplocket-2x.png │ │ ├── simple-reach-analytics-1x.png │ │ ├── simple-reach-analytics-2x.png │ │ ├── skyword-1x.png │ │ ├── skyword-2x.png │ │ ├── socialflow-1x.png │ │ ├── socialflow-2x.png │ │ ├── storify-1x.png │ │ ├── storify-2x.png │ │ ├── thePlatform-1x.png │ │ ├── thePlatform-2x.png │ │ ├── tinypass-1x.png │ │ ├── tinypass-2x.png │ │ ├── wp-parsely-1x.png │ │ ├── wp-parsely-2x.png │ │ ├── zemanta-1x.png │ │ └── zemanta-2x.png │ ├── vip-workshop-logo.svg │ └── wpcom-vip-logo.svg └── js │ └── vip-dashboard.js ├── ci └── prepare.sh ├── components ├── _shared │ ├── _colors.scss │ ├── _genericons.scss │ ├── _mixins.scss │ ├── _transitions.scss │ ├── _variables.scss │ ├── _wordpress.scss │ └── type │ │ ├── Genericons.eot │ │ ├── Genericons.svg │ │ ├── Genericons.ttf │ │ └── Genericons.woff ├── config.js ├── count │ └── index.jsx ├── forms │ ├── README.md │ ├── form-button │ │ ├── index.jsx │ │ └── style.scss │ ├── form-buttons-bar │ │ ├── index.jsx │ │ └── style.scss │ ├── form-checkbox │ │ └── index.jsx │ ├── form-country-select │ │ ├── index.jsx │ │ └── style.scss │ ├── form-fieldset │ │ ├── index.jsx │ │ └── style.scss │ ├── form-input-validation │ │ ├── index.jsx │ │ └── style.scss │ ├── form-label │ │ ├── index.jsx │ │ └── style.scss │ ├── form-legend │ │ ├── index.jsx │ │ └── style.scss │ ├── form-password-input │ │ ├── index.jsx │ │ └── style.scss │ ├── form-radio │ │ └── index.jsx │ ├── form-range │ │ ├── index.jsx │ │ └── style.scss │ ├── form-section-heading │ │ ├── index.jsx │ │ └── style.scss │ ├── form-select │ │ ├── index.jsx │ │ └── style.scss │ ├── form-setting-explanation │ │ ├── index.jsx │ │ └── style.scss │ ├── form-tel-input │ │ ├── index.jsx │ │ └── style.scss │ ├── form-text-input │ │ ├── index.jsx │ │ └── style.scss │ ├── form-textarea │ │ └── index.jsx │ ├── form-ul │ │ ├── index.jsx │ │ └── style.scss │ ├── multi-checkbox │ │ ├── Makefile │ │ ├── README.md │ │ ├── index.jsx │ │ └── test │ │ │ └── index.jsx │ ├── range │ │ ├── Makefile │ │ ├── README.md │ │ ├── index.jsx │ │ ├── style.scss │ │ └── test │ │ │ └── index.jsx │ ├── select-opt-groups.jsx │ └── sortable-list │ │ ├── README.md │ │ ├── index.jsx │ │ └── index.scss ├── header │ ├── index.jsx │ └── style.scss ├── main │ └── index.jsx ├── nav │ ├── index.jsx │ └── style.scss ├── stats-charts │ ├── index.jsx │ └── style.scss ├── stats-numbers │ ├── index.jsx │ └── style.scss ├── stats │ ├── index.jsx │ └── style.scss ├── style.scss ├── vip-dashboard.jsx ├── widget-contact │ ├── index.jsx │ └── style.scss ├── widget-editorial │ ├── index.jsx │ └── style.scss ├── widget-promo │ ├── index.jsx │ └── style.scss ├── widget-welcome │ ├── index.jsx │ └── style.scss └── widget │ ├── index.jsx │ └── style.scss ├── gulpfile.js ├── package-lock.json ├── package.json ├── readme.md ├── src └── img │ ├── vip-workshop-logo.svg │ └── wpcom-vip-logo.svg └── vip-dashboard.php /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "mocha": true, 7 | "node": true 8 | }, 9 | "ecmaFeatures": { 10 | "jsx": true, 11 | "modules": true 12 | }, 13 | "plugins": [ 14 | "eslint-plugin-react" 15 | ], 16 | "rules": { 17 | "brace-style": [ 1, "1tbs" ], 18 | "camelcase": 0, 19 | "comma-dangle": 0, 20 | "comma-spacing": 1, 21 | // Allows returning early as undefined 22 | "consistent-return": 0, 23 | "dot-notation": 1, 24 | "eqeqeq": [ 2, "allow-null" ], 25 | "eol-last": 1, 26 | "indent": [ 1, "tab", { "SwitchCase": 1 } ], 27 | "key-spacing": 1, 28 | // Most common is "Emitter", should be improved 29 | "new-cap": 1, 30 | "no-cond-assign": 2, 31 | "no-else-return": 1, 32 | "no-empty": 1, 33 | // Flux stores use switch case fallthrough 34 | "no-fallthrough": 0, 35 | "no-lonely-if": 1, 36 | "no-mixed-requires": 0, 37 | "no-mixed-spaces-and-tabs": 1, 38 | "no-multiple-empty-lines": [ 1, { max: 1 } ], 39 | "no-multi-spaces": [ 1, { "exceptions": { "VariableDeclarator": true } } ], 40 | "no-nested-ternary": 1, 41 | "no-new": 1, 42 | "no-process-exit": 1, 43 | "no-shadow": 1, 44 | "no-spaced-func": 1, 45 | "no-trailing-spaces": 1, 46 | "no-undef": 1, 47 | "no-underscore-dangle": 0, 48 | // Allows Chai `expect` expressions 49 | "no-unused-expressions": 0, 50 | "no-unused-vars": [ 1, { "args": "none" } ], 51 | // Teach eslint about React+JSX 52 | "react/jsx-uses-react": 1, 53 | "react/jsx-uses-vars": 1, 54 | // Allows function use before declaration 55 | "no-use-before-define": [ 2, "nofunc" ], 56 | // We split external, internal, module variables 57 | "one-var": 0, 58 | "operator-linebreak": [ 1, "after", { "overrides": { 59 | "?": "before", 60 | ":": "before" 61 | } } ], 62 | "padded-blocks": [ 1, "never" ], 63 | "quote-props": [ 1, "as-needed" ], 64 | "quotes": [ 1, "single", "avoid-escape" ], 65 | "semi-spacing": 1, 66 | "keyword-spacing": 1, 67 | "space-before-blocks": [ 1, "always" ], 68 | "space-before-function-paren": [ 1, "never" ], 69 | // Our array literal index exception violates this rule 70 | "space-in-brackets": 0, 71 | "space-in-parens": [ 1, "always" ], 72 | "space-infix-ops": [ 1, { "int32Hint": false } ], 73 | // Ideal for "!" but not for "++" 74 | "space-unary-ops": 0, 75 | // Assumed by default with Babel 76 | "strict": [ 2, "never" ], 77 | "valid-jsdoc": [ 1, { "requireReturn": false } ], 78 | // Common top-of-file requires, expressions between external, interal 79 | "vars-on-top": 0, 80 | "yoda": 0 81 | } 82 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | .sass-cache 5 | *.map 6 | .bowerrc 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "laxcomma": true, 3 | "curly": true 4 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.2 5 | 6 | env: 7 | - WP_VERSION=latest WP_MULTISITE=0 8 | 9 | addons: 10 | hosts: 11 | - local.wordpress.dev 12 | 13 | before_install: 14 | - sudo apt-get update > /dev/null 15 | # - ./ci/prepare.sh 16 | 17 | install: 18 | 19 | 20 | before_script: 21 | 22 | script: 23 | 24 | # @FIXME: Removing the JSHint linting as we cannot get the build to work 25 | # See: https://trello.com/c/8lUAvVsQ/193-jshint-testing-in-travis 26 | # Lint 'n 'hint 27 | # - make lint 28 | - find . -name \*.php -not -path "./vendor/*" -print0 | xargs -0 -n 1 -P 4 php -d display_errors=stderr -l > /dev/null 29 | 30 | notifications: 31 | slack: a8c:Hhd7rqdnXOFQgEU4HNZvkGs7 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lint 2 | 3 | lint: 4 | find . -name \*.php -not -path "./vendor/*" -print0 | xargs -0 -n 1 -P 4 php -d display_errors=stderr -l > /dev/null 5 | npm test 6 | -------------------------------------------------------------------------------- /assets/img/featured-plugins/browsi-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/browsi-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/browsi-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/browsi-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/chartbeat-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/chartbeat-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/chartbeat-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/chartbeat-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/co-schedule-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/co-schedule-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/co-schedule-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/co-schedule-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/facebook-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/facebook-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/facebook-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/facebook-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/findthebest-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/findthebest-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/findthebest-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/findthebest-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/getty-images-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/getty-images-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/getty-images-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/getty-images-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/janrain-capture-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/janrain-capture-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/janrain-capture-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/janrain-capture-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/jwplayer-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/jwplayer-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/jwplayer-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/jwplayer-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/livefyre-apps-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/livefyre-apps-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/livefyre-apps-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/livefyre-apps-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/mediapass-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/mediapass-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/mediapass-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/mediapass-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/newscred-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/newscred-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/newscred-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/newscred-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/ooyala-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/ooyala-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/ooyala-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/ooyala-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/postrelease-vip-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/postrelease-vip-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/postrelease-vip-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/postrelease-vip-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/publishthis-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/publishthis-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/publishthis-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/publishthis-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/sailthru-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/sailthru-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/sailthru-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/sailthru-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/shoplocket-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/shoplocket-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/shoplocket-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/shoplocket-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/simple-reach-analytics-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/simple-reach-analytics-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/simple-reach-analytics-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/simple-reach-analytics-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/skyword-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/skyword-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/skyword-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/skyword-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/socialflow-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/socialflow-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/socialflow-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/socialflow-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/storify-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/storify-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/storify-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/storify-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/thePlatform-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/thePlatform-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/thePlatform-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/thePlatform-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/tinypass-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/tinypass-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/tinypass-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/tinypass-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/wp-parsely-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/wp-parsely-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/wp-parsely-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/wp-parsely-2x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/zemanta-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/zemanta-1x.png -------------------------------------------------------------------------------- /assets/img/featured-plugins/zemanta-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/assets/img/featured-plugins/zemanta-2x.png -------------------------------------------------------------------------------- /assets/img/vip-workshop-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/wpcom-vip-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /ci/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # called by Travis CI 4 | 5 | # Exit if anything fails AND echo each command before executing 6 | # http://www.peterbe.com/plog/set-ex 7 | set -ex 8 | 9 | # Install NPM 10 | # ================== 11 | 12 | npm install 13 | -------------------------------------------------------------------------------- /components/_shared/_colors.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * VIP Colors 3 | * 4 | * https://vip.wordpress.com/vip-brand/#colors 5 | */ 6 | 7 | $vip-gold: #c29c69; 8 | $vip-dark-grey: #151e25; 9 | $vip-white: #ffffff; 10 | $vip-blue: #4f748e; 11 | $vip-grey: #62707a; 12 | $vip-grey-1: #a3aFB5; 13 | $vip-grey-2: #d7dee2; 14 | $vip-grey-3: #f3f6f8; 15 | 16 | /* 17 | * Graphite 18 | */ 19 | 20 | $graphite: #3b3e44; 21 | $graphite-var: #35383d; 22 | $graphite-dark: #24252b; 23 | $graphite-dark-var: #2f3136; 24 | $graphite-light: #67696e; 25 | 26 | 27 | /* 28 | * Azure/Blues 29 | */ 30 | 31 | $azure: #168cbf; 32 | $azure-dark: #0b75a3; 33 | $azure-light: #25a8dc; 34 | 35 | $blue: #15a7de; 36 | $blue-dark: #4f748e; 37 | $blue-light: #98aebd; 38 | $blue-washed: #edf3f7; 39 | 40 | $blue-background: #f5f8fb; 41 | $blue-border: #d8e2e9; 42 | 43 | /* 44 | * Essentials 45 | */ 46 | 47 | $black: #000000; 48 | $black-fade: #2f3136; 49 | $black-forms: #404040; 50 | 51 | $white: #ffffff; 52 | 53 | $grey-light: #e5e5e5; 54 | $grey-blue: #c8d7e2; 55 | $grey-blue-fade: rgba($grey-blue, .55); 56 | $grey-dark: #151E25; 57 | 58 | /* 59 | * Links 60 | */ 61 | 62 | $link-color: #1a99cf; 63 | 64 | /* 65 | * Graphs 66 | */ 67 | 68 | /*$blue: #2ea2cc; 69 | $blue-dark: #0074a2; 70 | $blue-faded: #f2fbff;*/ 71 | 72 | $graph-blue: #2dade3; 73 | $graph-orange: #f5a91c; 74 | 75 | $graph-dummy: #45484f; 76 | 77 | $trend-positive: #7ABC44; 78 | $trend-negative: #EB5C36; 79 | $trend-neutral: $grey-dark; 80 | 81 | 82 | /* 83 | * Alerts 84 | */ 85 | 86 | $alert-yellow: #f0b849; 87 | $alert-red: #d94f4f; 88 | $alert-green: #4ab866; -------------------------------------------------------------------------------- /components/_shared/_genericons.scss: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Genericons 4 | 5 | */ 6 | 7 | 8 | /* IE8 and below use EOT and allow cross-site embedding. 9 | IE9 uses WOFF which is base64 encoded to allow cross-site embedding. 10 | So unfortunately, IE9 will throw a console error, but it'll still work. 11 | When the font is base64 encoded, cross-site embedding works in Firefox */ 12 | 13 | @font-face { 14 | font-family: 'Genericons'; 15 | src: url('../type/Genericons.eot'); 16 | } 17 | 18 | @font-face { 19 | font-family: 'Genericons'; 20 | src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff'), 21 | url('../type/Genericons.ttf') format('truetype'), 22 | url('../type/Genericons.svg#genericonsregular') format('svg'); 23 | font-weight: normal; 24 | font-style: normal; 25 | } 26 | 27 | @media screen and (-webkit-min-device-pixel-ratio:0) { 28 | @font-face { 29 | font-family: "Genericons"; 30 | src: url("../type/Genericons.svg#Genericons") format("svg"); 31 | } 32 | } 33 | 34 | 35 | /** 36 | * All Genericons 37 | */ 38 | 39 | .genericon { 40 | font-size: 16px; 41 | vertical-align: top; 42 | text-align: center; 43 | -moz-transition: color .1s ease-in 0; 44 | -webkit-transition: color .1s ease-in 0; 45 | display: inline-block; 46 | font-family: "Genericons"; 47 | font-style: normal; 48 | font-weight: normal; 49 | font-variant: normal; 50 | line-height: 1; 51 | text-decoration: inherit; 52 | text-transform: none; 53 | -moz-osx-font-smoothing: grayscale; 54 | -webkit-font-smoothing: antialiased; 55 | speak: none; 56 | } 57 | 58 | 59 | /** 60 | * Individual icons 61 | */ 62 | 63 | .genericon-404:before { content: "\f423"; } 64 | .genericon-activity:before { content: "\f508"; } 65 | .genericon-anchor:before { content: "\f509"; } 66 | .genericon-aside:before { content: "\f101"; } 67 | .genericon-attachment:before { content: "\f416"; } 68 | .genericon-audio:before { content: "\f109"; } 69 | .genericon-bold:before { content: "\f471"; } 70 | .genericon-book:before { content: "\f444"; } 71 | .genericon-bug:before { content: "\f50a"; } 72 | .genericon-cart:before { content: "\f447"; } 73 | .genericon-category:before { content: "\f301"; } 74 | .genericon-chat:before { content: "\f108"; } 75 | .genericon-checkmark:before { content: "\f418"; } 76 | .genericon-close:before { content: "\f405"; } 77 | .genericon-close-alt:before { content: "\f406"; } 78 | .genericon-cloud:before { content: "\f426"; } 79 | .genericon-cloud-download:before { content: "\f440"; } 80 | .genericon-cloud-upload:before { content: "\f441"; } 81 | .genericon-code:before { content: "\f462"; } 82 | .genericon-codepen:before { content: "\f216"; } 83 | .genericon-cog:before { content: "\f445"; } 84 | .genericon-collapse:before { content: "\f432"; } 85 | .genericon-comment:before { content: "\f300"; } 86 | .genericon-day:before { content: "\f305"; } 87 | .genericon-digg:before { content: "\f221"; } 88 | .genericon-document:before { content: "\f443"; } 89 | .genericon-dot:before { content: "\f428"; } 90 | .genericon-downarrow:before { content: "\f502"; } 91 | .genericon-download:before { content: "\f50b"; } 92 | .genericon-draggable:before { content: "\f436"; } 93 | .genericon-dribbble:before { content: "\f201"; } 94 | .genericon-dropbox:before { content: "\f225"; } 95 | .genericon-dropdown:before { content: "\f433"; } 96 | .genericon-dropdown-left:before { content: "\f434"; } 97 | .genericon-edit:before { content: "\f411"; } 98 | .genericon-ellipsis:before { content: "\f476"; } 99 | .genericon-expand:before { content: "\f431"; } 100 | .genericon-external:before { content: "\f442"; } 101 | .genericon-facebook:before { content: "\f203"; } 102 | .genericon-facebook-alt:before { content: "\f204"; } 103 | .genericon-fastforward:before { content: "\f458"; } 104 | .genericon-feed:before { content: "\f413"; } 105 | .genericon-flag:before { content: "\f468"; } 106 | .genericon-flickr:before { content: "\f211"; } 107 | .genericon-foursquare:before { content: "\f226"; } 108 | .genericon-fullscreen:before { content: "\f474"; } 109 | .genericon-gallery:before { content: "\f103"; } 110 | .genericon-github:before { content: "\f200"; } 111 | .genericon-googleplus:before { content: "\f206"; } 112 | .genericon-googleplus-alt:before { content: "\f218"; } 113 | .genericon-handset:before { content: "\f50c"; } 114 | .genericon-heart:before { content: "\f461"; } 115 | .genericon-help:before { content: "\f457"; } 116 | .genericon-hide:before { content: "\f404"; } 117 | .genericon-hierarchy:before { content: "\f505"; } 118 | .genericon-home:before { content: "\f409"; } 119 | .genericon-image:before { content: "\f102"; } 120 | .genericon-info:before { content: "\f455"; } 121 | .genericon-instagram:before { content: "\f215"; } 122 | .genericon-italic:before { content: "\f472"; } 123 | .genericon-key:before { content: "\f427"; } 124 | .genericon-leftarrow:before { content: "\f503"; } 125 | .genericon-link:before { content: "\f107"; } 126 | .genericon-linkedin:before { content: "\f207"; } 127 | .genericon-linkedin-alt:before { content: "\f208"; } 128 | .genericon-location:before { content: "\f417"; } 129 | .genericon-lock:before { content: "\f470"; } 130 | .genericon-mail:before { content: "\f410"; } 131 | .genericon-maximize:before { content: "\f422"; } 132 | .genericon-menu:before { content: "\f419"; } 133 | .genericon-microphone:before { content: "\f50d"; } 134 | .genericon-minimize:before { content: "\f421"; } 135 | .genericon-minus:before { content: "\f50e"; } 136 | .genericon-month:before { content: "\f307"; } 137 | .genericon-move:before { content: "\f50f"; } 138 | .genericon-next:before { content: "\f429"; } 139 | .genericon-notice:before { content: "\f456"; } 140 | .genericon-paintbrush:before { content: "\f506"; } 141 | .genericon-path:before { content: "\f219"; } 142 | .genericon-pause:before { content: "\f448"; } 143 | .genericon-phone:before { content: "\f437"; } 144 | .genericon-picture:before { content: "\f473"; } 145 | .genericon-pinned:before { content: "\f308"; } 146 | .genericon-pinterest:before { content: "\f209"; } 147 | .genericon-pinterest-alt:before { content: "\f210"; } 148 | .genericon-play:before { content: "\f452"; } 149 | .genericon-plugin:before { content: "\f439"; } 150 | .genericon-plus:before { content: "\f510"; } 151 | .genericon-pocket:before { content: "\f224"; } 152 | .genericon-polldaddy:before { content: "\f217"; } 153 | .genericon-portfolio:before { content: "\f460"; } 154 | .genericon-previous:before { content: "\f430"; } 155 | .genericon-print:before { content: "\f469"; } 156 | .genericon-quote:before { content: "\f106"; } 157 | .genericon-rating-empty:before { content: "\f511"; } 158 | .genericon-rating-full:before { content: "\f512"; } 159 | .genericon-rating-half:before { content: "\f513"; } 160 | .genericon-reddit:before { content: "\f222"; } 161 | .genericon-refresh:before { content: "\f420"; } 162 | .genericon-reply:before { content: "\f412"; } 163 | .genericon-reply-alt:before { content: "\f466"; } 164 | .genericon-reply-single:before { content: "\f467"; } 165 | .genericon-rewind:before { content: "\f459"; } 166 | .genericon-rightarrow:before { content: "\f501"; } 167 | .genericon-search:before { content: "\f400"; } 168 | .genericon-send-to-phone:before { content: "\f438"; } 169 | .genericon-send-to-tablet:before { content: "\f454"; } 170 | .genericon-share:before { content: "\f415"; } 171 | .genericon-show:before { content: "\f403"; } 172 | .genericon-shuffle:before { content: "\f514"; } 173 | .genericon-sitemap:before { content: "\f507"; } 174 | .genericon-skip-ahead:before { content: "\f451"; } 175 | .genericon-skip-back:before { content: "\f450"; } 176 | .genericon-skype:before { content: "\f220"; } 177 | .genericon-spam:before { content: "\f424"; } 178 | .genericon-spotify:before { content: "\f515"; } 179 | .genericon-standard:before { content: "\f100"; } 180 | .genericon-star:before { content: "\f408"; } 181 | .genericon-status:before { content: "\f105"; } 182 | .genericon-stop:before { content: "\f449"; } 183 | .genericon-stumbleupon:before { content: "\f223"; } 184 | .genericon-subscribe:before { content: "\f463"; } 185 | .genericon-subscribed:before { content: "\f465"; } 186 | .genericon-summary:before { content: "\f425"; } 187 | .genericon-tablet:before { content: "\f453"; } 188 | .genericon-tag:before { content: "\f302"; } 189 | .genericon-time:before { content: "\f303"; } 190 | .genericon-top:before { content: "\f435"; } 191 | .genericon-trash:before { content: "\f407"; } 192 | .genericon-tumblr:before { content: "\f214"; } 193 | .genericon-twitch:before { content: "\f516"; } 194 | .genericon-twitter:before { content: "\f202"; } 195 | .genericon-unapprove:before { content: "\f446"; } 196 | .genericon-unsubscribe:before { content: "\f464"; } 197 | .genericon-unzoom:before { content: "\f401"; } 198 | .genericon-uparrow:before { content: "\f500"; } 199 | .genericon-user:before { content: "\f304"; } 200 | .genericon-video:before { content: "\f104"; } 201 | .genericon-videocamera:before { content: "\f517"; } 202 | .genericon-vimeo:before { content: "\f212"; } 203 | .genericon-warning:before { content: "\f414"; } 204 | .genericon-website:before { content: "\f475"; } 205 | .genericon-week:before { content: "\f306"; } 206 | .genericon-wordpress:before { content: "\f205"; } 207 | .genericon-xpost:before { content: "\f504"; } 208 | .genericon-youtube:before { content: "\f213"; } 209 | .genericon-zoom:before { content: "\f402"; } 210 | -------------------------------------------------------------------------------- /components/_shared/_mixins.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Mixins 3 | */ 4 | @mixin filter($filter-type,$filter-amount) { 5 | filter: $filter-type+unquote('(#{$filter-amount})'); 6 | } -------------------------------------------------------------------------------- /components/_shared/_transitions.scss: -------------------------------------------------------------------------------- 1 | a { 2 | transition: all 300ms cubic-bezier(.72,.2,.48,1.36); 3 | } 4 | 5 | a:after { 6 | transition: all 160ms cubic-bezier(.72,.2,.48,1.36); 7 | } -------------------------------------------------------------------------------- /components/_shared/_variables.scss: -------------------------------------------------------------------------------- 1 | $graph-size: 160px; 2 | $stats-height: 300px; -------------------------------------------------------------------------------- /components/_shared/_wordpress.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * wp-admin specific stlyes 3 | * to be removed when these tools get merged with calypso 4 | */ 5 | 6 | #wpbody-content, 7 | body.toplevel_page_vip-dashboard { 8 | background: $vip-grey-3; 9 | } 10 | 11 | body.toplevel_page_vip-dashboard #wpcontent { 12 | padding-left: 0; 13 | } 14 | 15 | body.toplevel_page_vip-dashboard #vp-notice { 16 | display: none; 17 | } 18 | 19 | @media screen and (max-width: 782px) { 20 | .auto-fold #wpcontent { 21 | padding-left: 0; 22 | } 23 | } -------------------------------------------------------------------------------- /components/_shared/type/Genericons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/components/_shared/type/Genericons.eot -------------------------------------------------------------------------------- /components/_shared/type/Genericons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/components/_shared/type/Genericons.ttf -------------------------------------------------------------------------------- /components/_shared/type/Genericons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/components/_shared/type/Genericons.woff -------------------------------------------------------------------------------- /components/config.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | // load certain settings from WordPress via data-attribtues on the #app div 4 | var app = document.getElementById( 'app' ), 5 | adminurl = app.getAttribute( 'data-adminurl' ), 6 | ajaxurl = app.getAttribute( 'data-ajaxurl' ), 7 | asseturl = app.getAttribute( 'data-asseturl' ), 8 | user = app.getAttribute( 'data-name' ), 9 | useremail = app.getAttribute( 'data-email' ); 10 | 11 | config.adminurl = adminurl; 12 | config.ajaxurl = ajaxurl; 13 | config.asseturl = asseturl; 14 | config.user = user; 15 | config.useremail = useremail; 16 | 17 | module.exports = config; 18 | -------------------------------------------------------------------------------- /components/count/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ); 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | 10 | /** 11 | * Counter component 12 | */ 13 | var CountTo = React.createClass( { 14 | propTypes: { 15 | from: React.PropTypes.number, 16 | to: React.PropTypes.number.isRequired, 17 | speed: React.PropTypes.number.isRequired, 18 | delay: React.PropTypes.number, 19 | onComplete: React.PropTypes.func 20 | }, 21 | 22 | getInitialState: function() { 23 | return { 24 | counter: this.props.from || 0 25 | }; 26 | }, 27 | 28 | componentDidMount: function() { 29 | var delay = this.props.delay || 100; 30 | this.loopsCounter = 0; 31 | this.loops = Math.ceil( this.props.speed / delay ); 32 | this.increment = ( this.props.to - this.state.counter ) / this.loops; 33 | this.interval = setInterval( this.next, delay ); 34 | }, 35 | 36 | componentWillUnmount: function() { 37 | this.clear(); 38 | }, 39 | 40 | componentWillUpdate: function() { 41 | //alert( this.state.counter ); 42 | //delay = this.props.delay || 100; 43 | //this.interval = setInterval(this.next, delay); 44 | }, 45 | 46 | next: function() { 47 | if ( this.loopsCounter < this.loops ) { 48 | this.loopsCounter++; 49 | this.setState( { 50 | counter: this.state.counter + this.increment 51 | } ); 52 | } else { 53 | this.clear(); 54 | if ( this.props.onComplete ) { 55 | this.props.onComplete(); 56 | } 57 | } 58 | }, 59 | 60 | clear: function() { 61 | clearInterval( this.interval ); 62 | }, 63 | 64 | render: function() { 65 | return ( 66 | {this.state.counter.toFixed()} 67 | ); 68 | } 69 | } ); 70 | 71 | module.exports = CountTo; 72 | -------------------------------------------------------------------------------- /components/forms/README.md: -------------------------------------------------------------------------------- 1 | Form Components 2 | =============== 3 | 4 | This is a directory of shared form components. 5 | 6 | ### Settings Form Fields 7 | The following form components were created as an effort to minimize duplication between site settings and me settings. 8 | 9 | - form-button 10 | - form-buttons-bar 11 | - form-checkbox 12 | - form-fieldset 13 | - form-label 14 | - form-legend 15 | - form-radio 16 | - form-select 17 | - form-setting-explanation 18 | - form-text-input 19 | - form-textarea 20 | - form-ul 21 | 22 | The component jsx files are wrappers that ensure our classes are added to each form field. Each form field component also contains a `style.scss` file in its directory for styling. These stylesheets are included in `/assets/stylesheets/_components.scss`. 23 | 24 | ### FormSectionHeading 25 | The `FormSectionHeading` component allows you to add a section header to your settings form. 26 | 27 | ### FormInputValidation 28 | The `FormInputValidation` component is used to display a validation notice to the user. You can use it like this: 29 | 30 | 31 | 32 | 33 | ### MultiCheckbox 34 | 35 | [See README.md for MultiCheckbox](multi-checkbox/README.md) 36 | 37 | ### SelectOptGroups 38 | `SelectOptGroups` allows you to pass structured data to render a select element with `` elements nested inside `` separators. You can use it like this: 39 | 40 | ``` 41 | var options = [ 42 | { 43 | label: 'Group 1', 44 | options: [ 45 | { 46 | label: 'Option 1', 47 | value: 1 48 | }, 49 | { 50 | label: 'Option 2', 51 | value: 2 52 | } 53 | ] 54 | }, 55 | { 56 | label: 'Group 2', 57 | options: [ 58 | { 59 | label: 'Option 3', 60 | value: 3 61 | }, 62 | { 63 | label: 'Option 4', 64 | value: 4 65 | } 66 | ] 67 | } 68 | ], 69 | initialSelected = 3; 70 | 71 | 72 | ``` 73 | 74 | And this would render: 75 | 76 | ``` 77 | 78 | 79 | Option 1 80 | Option 2 81 | 82 | 83 | Option 3 84 | Option 4 85 | 86 | 87 | ``` 88 | 89 | Any valid jsx attributes that are passed to `` will also get passed to the rendered `` element, so you can also pass in attributes like `className`, `onChange`, etc. 90 | 91 | ### FormUl 92 | 93 | The `FomrUl` component allows you to add a unordered list to a form. Nesting lists will give you more of a left margin. 94 | -------------------------------------------------------------------------------- /components/forms/form-button/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ), 7 | isEmpty = require( 'lodash/lang/isEmpty' ); 8 | 9 | module.exports = React.createClass( { 10 | 11 | displayName: 'FormsButton', 12 | 13 | getDefaultProps: function() { 14 | return { 15 | isSubmitting: false, 16 | isPrimary: true 17 | }; 18 | }, 19 | 20 | getDefaultButtonAction: function() { 21 | return this.props.isSubmitting ? this.translate( 'Saving…' ) : this.translate( 'Save Settings' ); 22 | }, 23 | 24 | render: function() { 25 | var buttonClasses = React.addons.classSet( { 26 | button: true, 27 | 'form-button': true, 28 | 'is-primary': this.props.isPrimary 29 | } ); 30 | 31 | return ( 32 | 35 | { isEmpty( this.props.children ) ? this.getDefaultButtonAction() : this.props.children } 36 | 37 | ); 38 | } 39 | } ); 40 | -------------------------------------------------------------------------------- /components/forms/form-button/style.scss: -------------------------------------------------------------------------------- 1 | .form-button { 2 | float: right; 3 | margin-left: 10px; 4 | } 5 | -------------------------------------------------------------------------------- /components/forms/form-buttons-bar/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormButtonsBar', 11 | 12 | render: function() { 13 | return ( 14 | 17 | { this.props.children } 18 | 19 | ); 20 | } 21 | } ); 22 | -------------------------------------------------------------------------------- /components/forms/form-buttons-bar/style.scss: -------------------------------------------------------------------------------- 1 | .form-buttons-bar { 2 | @include clear-fix; 3 | } 4 | -------------------------------------------------------------------------------- /components/forms/form-checkbox/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormInputCheckbox', 11 | 12 | render: function() { 13 | var otherProps = omit( this.props, [ 'className', 'type' ] ); 14 | 15 | return ( 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-country-select/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | isEmpty = require( 'lodash/lang/isEmpty' ), 6 | joinClasses = require( 'fbjs/lib/joinClasses' ), 7 | observe = require( 'lib/mixins/data-observe' ), 8 | omit = require( 'lodash/object/omit' ); 9 | 10 | module.exports = React.createClass( { 11 | 12 | displayName: 'FormCountrySelect', 13 | 14 | mixins: [ observe( 'countriesList' ) ], 15 | 16 | render: function() { 17 | var countriesList = this.props.countriesList.get(), 18 | options = []; 19 | 20 | if ( isEmpty( countriesList ) ) { 21 | options.push( { key: '', label: this.translate( 'Loading…' ), disabled: 'disabled' } ); 22 | } else { 23 | options = options.concat( countriesList.map( function( country ) { 24 | return { key: country.code, label: country.name }; 25 | } ) ); 26 | } 27 | 28 | return ( 29 | 34 | { options.map( function( option ) { 35 | return { option.label }; 36 | } ) } 37 | 38 | ); 39 | } 40 | } ); 41 | -------------------------------------------------------------------------------- /components/forms/form-country-select/style.scss: -------------------------------------------------------------------------------- 1 | .form-country-select { 2 | margin-bottom: 1em; 3 | } 4 | 5 | // According to CSS tricks, browser support is IE9+ 6 | .form-country-select:only-of-type, 7 | .form-country-select:last-of-type { 8 | margin-bottom: 0; 9 | } 10 | -------------------------------------------------------------------------------- /components/forms/form-fieldset/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormFieldset', 11 | 12 | render: function() { 13 | return ( 14 | 15 | { this.props.children } 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-fieldset/style.scss: -------------------------------------------------------------------------------- 1 | .form-fieldset { 2 | clear: both; 3 | margin-bottom: 20px; 4 | } 5 | -------------------------------------------------------------------------------- /components/forms/form-input-validation/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | classNames = require( 'classnames' ); 6 | 7 | module.exports = React.createClass( { 8 | 9 | displayName: 'FormInputValidation', 10 | 11 | getDefaultProps: function() { 12 | return { 13 | isError: false 14 | }; 15 | }, 16 | 17 | render: function() { 18 | var classes = classNames( { 19 | 'form-input-validation': true, 20 | 'is-error': this.props.isError 21 | } ); 22 | 23 | return ( 24 | 25 | { this.props.text } 26 | 27 | ); 28 | } 29 | } ); 30 | -------------------------------------------------------------------------------- /components/forms/form-input-validation/style.scss: -------------------------------------------------------------------------------- 1 | .form-input-validation { 2 | color: $alert-green; 3 | position: relative; 4 | padding: 6px 24px 11px 28px; 5 | border-radius: 1px; 6 | box-sizing: border-box; 7 | font-size: 14px; 8 | animation: appear .3s ease-in-out; 9 | 10 | &:before { 11 | @include noticon( '\f418', 16px ); 12 | position: absolute; 13 | left: 0; 14 | font-size: 24px; 15 | line-height: 1; 16 | } 17 | 18 | &.is-error { 19 | color: $alert-red; 20 | 21 | &:before { 22 | content: "\f424" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /components/forms/form-label/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormLabel', 11 | 12 | render: function() { 13 | return ( 14 | 15 | { this.props.children } 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-label/style.scss: -------------------------------------------------------------------------------- 1 | .form-label { 2 | display: block; 3 | font-size: 14px; 4 | font-size: 1.4rem; 5 | font-weight: 600; 6 | margin-bottom: 5px; 7 | } 8 | 9 | .form-label input[type="checkbox"] + span, 10 | .form-label input[type="radio"] + span { 11 | font-weight: normal; 12 | } 13 | -------------------------------------------------------------------------------- /components/forms/form-legend/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormLegend', 11 | 12 | render: function() { 13 | return ( 14 | 15 | { this.props.children } 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-legend/style.scss: -------------------------------------------------------------------------------- 1 | .form-legend { 2 | font-size: 14px; 3 | font-size: 1.4rem; 4 | font-weight: 600; 5 | margin-bottom: 5px; 6 | } 7 | li .form-legend { 8 | margin-top: 4px; 9 | } 10 | -------------------------------------------------------------------------------- /components/forms/form-password-input/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | classNames = require( 'classnames' ); 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | var FormTextInput = require( 'forms/form-text-input' ); 11 | 12 | module.exports = React.createClass( { 13 | 14 | displayName: 'FormPasswordInput', 15 | 16 | getInitialState: function() { 17 | return { 18 | hidePassword: false 19 | }; 20 | }, 21 | 22 | togglePasswordVisibility: function() { 23 | this.setState( { hidePassword: ! this.state.hidePassword } ); 24 | }, 25 | 26 | hidden: function() { 27 | return this.props.submitting || this.state.hidePassword; 28 | }, 29 | 30 | render: function() { 31 | var toggleVisibilityClasses = classNames( { 32 | 'form-password-input__toggle-visibility': true, 33 | 'is-hidden': this.props.submitting, 34 | 'is-visible': ! this.props.submitting 35 | } ); 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | } ); 45 | -------------------------------------------------------------------------------- /components/forms/form-password-input/style.scss: -------------------------------------------------------------------------------- 1 | .form-password-input { 2 | position: relative; 3 | } 4 | 5 | .form-password-input__toggle-visibility { 6 | color: lighten( $gray, 20 ); 7 | cursor: pointer; 8 | line-height: 40px; 9 | position: absolute; 10 | right: 8px; 11 | 12 | &.is-hidden:before { 13 | @include noticon( '\f403', 16px ); 14 | line-height: 40px; 15 | } 16 | 17 | &.is-visible:before { 18 | @include noticon( '\f404', 16px ); 19 | line-height: 40px; 20 | } 21 | } 22 | 23 | .form-password-input__toggle-visibility:hover { 24 | color: inherit; 25 | } 26 | -------------------------------------------------------------------------------- /components/forms/form-radio/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormRadio', 11 | 12 | render: function() { 13 | var otherProps = omit( this.props, [ 'className', 'type' ] ); 14 | 15 | return ( 16 | 20 | ); 21 | } 22 | } ); 23 | -------------------------------------------------------------------------------- /components/forms/form-range/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | omit = require( 'lodash/object/omit' ), 6 | classnames = require( 'classnames' ); 7 | 8 | module.exports = React.createClass( { 9 | displayName: 'FormRange', 10 | 11 | propTypes: { 12 | onChange: React.PropTypes.func 13 | }, 14 | 15 | getDefaultProps: function() { 16 | return { 17 | onChange: function() {} 18 | }; 19 | }, 20 | 21 | componentDidMount: function() { 22 | if ( this.shouldNormalizeChange() ) { 23 | this.refs.range.getDOMNode().addEventListener( 'change', this.onChange ); 24 | } 25 | }, 26 | 27 | componentWillUnmount: function() { 28 | this.refs.range.getDOMNode().removeEventListener( 'change', this.onChange ); 29 | }, 30 | 31 | shouldNormalizeChange: function() { 32 | var ua = window.navigator.userAgent; 33 | 34 | // Internet Explorer doesn't trigger the normal "input" event as the 35 | // user drags the thumb. Instead, it emits the equivalent event on 36 | // "change", so we watch the change event and emit a simulated event. 37 | return -1 !== ua.indexOf( 'MSIE' ) || -1 !== ua.indexOf( 'Trident/' ); 38 | }, 39 | 40 | onChange: function( event ) { 41 | this.props.onChange( event ); 42 | }, 43 | 44 | render: function() { 45 | var classes = classnames( this.props.className, 'form-range' ); 46 | 47 | return ; 48 | } 49 | } ); 50 | -------------------------------------------------------------------------------- /components/forms/form-range/style.scss: -------------------------------------------------------------------------------- 1 | .form-range { 2 | -webkit-appearance: none; 3 | display: block; 4 | width: 100%; 5 | height: 18px; 6 | margin: 0; 7 | padding: 0; 8 | background: lighten( $gray, 20% ); 9 | background: linear-gradient( 10 | to bottom, 11 | transparent, 12 | transparent 8px, 13 | lighten( $gray, 20% ) 8px, 14 | lighten( $gray, 20% ) 10px, 15 | transparent 10px 16 | ); 17 | } 18 | 19 | .form-range:focus { 20 | outline: none; 21 | } 22 | 23 | @mixin form-range-thumb() { 24 | height: 26px; 25 | width: 26px; 26 | border: none; 27 | background: radial-gradient( 28 | $blue-medium, 29 | $blue-medium 6px, 30 | $blue-dark 7px, 31 | transparent 8px, 32 | transparent 33 | ); 34 | cursor: pointer; 35 | } 36 | 37 | .form-range::-webkit-slider-thumb { 38 | @include form-range-thumb(); 39 | -webkit-appearance: none; 40 | } 41 | 42 | .form-range::-moz-range-track { 43 | background: transparent; 44 | border: none; 45 | } 46 | 47 | .form-range::-moz-range-thumb { 48 | @include form-range-thumb(); 49 | } 50 | 51 | .form-range::-ms-track { 52 | width: 100%; 53 | cursor: pointer; 54 | background: transparent; 55 | border-color: transparent; 56 | color: transparent; 57 | } 58 | 59 | .form-range::-ms-fill-lower, 60 | .form-range::-ms-fill-upper { 61 | background: transparent; 62 | } 63 | 64 | .form-range::-ms-thumb { 65 | @include form-range-thumb(); 66 | } 67 | -------------------------------------------------------------------------------- /components/forms/form-section-heading/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormSectionHeading', 11 | 12 | render: function() { 13 | return ( 14 | 15 | { this.props.children } 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-section-heading/style.scss: -------------------------------------------------------------------------------- 1 | .form-section-heading { 2 | font-size: 2.4rem; 3 | font-weight: 300; 4 | margin: 30px 0 20px 0; 5 | } 6 | 7 | .form-section-heading:first-child { 8 | margin-top: 0; 9 | } 10 | -------------------------------------------------------------------------------- /components/forms/form-select/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormSelect', 11 | 12 | render: function() { 13 | return ( 14 | 15 | { this.props.children } 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-select/style.scss: -------------------------------------------------------------------------------- 1 | .form-select { 2 | margin-bottom: 1em; 3 | } 4 | 5 | // According to CSS tricks, browser support is IE9+ 6 | .form-select:only-of-type, 7 | .form-select:last-of-type { 8 | margin-bottom: 0; 9 | } 10 | -------------------------------------------------------------------------------- /components/forms/form-setting-explanation/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormSettingExplanation', 11 | 12 | render: function() { 13 | return ( 14 | 15 | { this.props.children } 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-setting-explanation/style.scss: -------------------------------------------------------------------------------- 1 | .form-setting-explanation { 2 | color: $gray; 3 | display: block; 4 | font-size: 13px; 5 | font-size: 1.3rem; 6 | font-style: italic; 7 | font-weight: 400; 8 | margin: 5px 0 0 0; 9 | } 10 | -------------------------------------------------------------------------------- /components/forms/form-tel-input/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ), 7 | classNames = require( 'classnames' ); 8 | 9 | module.exports = React.createClass( { 10 | 11 | displayName: 'FormTelInput', 12 | 13 | getDefaultProps: function() { 14 | return { 15 | isError: false 16 | }; 17 | }, 18 | 19 | render: function() { 20 | var otherProps = omit( this.props, [ 'className', 'type' ] ), 21 | classes = classNames( { 22 | 'form-tel-input': true, 23 | 'is-error': this.props.isError 24 | } ); 25 | 26 | return ( 27 | 32 | ); 33 | } 34 | } ); 35 | -------------------------------------------------------------------------------- /components/forms/form-tel-input/style.scss: -------------------------------------------------------------------------------- 1 | .form-tel-input { 2 | -webkit-appearance: none; 3 | 4 | &:not( :focus ) { 5 | &.is-error { 6 | border-color: $alert-red; 7 | } 8 | 9 | &.is-error:hover { 10 | border-color: darken( $alert-red, 10 ); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /components/forms/form-text-input/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ), 7 | classNames = require( 'classnames' ); 8 | 9 | module.exports = React.createClass( { 10 | 11 | displayName: 'FormTextInput', 12 | 13 | getDefaultProps: function() { 14 | return { 15 | isPassword: false, 16 | isError: false, 17 | isValid: false 18 | }; 19 | }, 20 | 21 | render: function() { 22 | var otherProps = omit( this.props, [ 'className', 'type' ] ), 23 | classes = classNames( { 24 | 'form-text-input': true, 25 | 'is-error': this.props.isError, 26 | 'is-valid': this.props.isValid 27 | } ); 28 | 29 | return ( 30 | 34 | ); 35 | } 36 | } ); 37 | -------------------------------------------------------------------------------- /components/forms/form-text-input/style.scss: -------------------------------------------------------------------------------- 1 | .form-text-input { 2 | -webkit-appearance: none; 3 | 4 | &:not( :focus ) { 5 | &.is-valid { 6 | border-color: $alert-green; 7 | } 8 | 9 | &.is-valid:hover { 10 | border-color: darken( $alert-green, 10 ); 11 | } 12 | 13 | &.is-error { 14 | border-color: $alert-red; 15 | } 16 | 17 | &.is-error:hover { 18 | border-color: darken( $alert-red, 10 ); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/forms/form-textarea/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormTextarea', 11 | 12 | render: function() { 13 | return ( 14 | 15 | { this.props.children } 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-ul/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react/addons' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ), 6 | omit = require( 'lodash/object/omit' ); 7 | 8 | module.exports = React.createClass( { 9 | 10 | displayName: 'FormUl', 11 | 12 | render: function() { 13 | return ( 14 | 15 | { this.props.children } 16 | 17 | ); 18 | } 19 | } ); 20 | -------------------------------------------------------------------------------- /components/forms/form-ul/style.scss: -------------------------------------------------------------------------------- 1 | .form-ul .form-ul { 2 | margin-left: 3em; 3 | } 4 | -------------------------------------------------------------------------------- /components/forms/multi-checkbox/Makefile: -------------------------------------------------------------------------------- 1 | REPORTER ?= spec 2 | MOCHA ?= ../../../node_modules/.bin/mocha 3 | 4 | test: 5 | @NODE_ENV=test NODE_PATH=test:../../ $(MOCHA) --compilers jsx:jsx-require-extension --reporter $(REPORTER) 6 | 7 | .PHONY: test 8 | -------------------------------------------------------------------------------- /components/forms/multi-checkbox/README.md: -------------------------------------------------------------------------------- 1 | MultiCheckbox 2 | ============= 3 | 4 | MultiCheckbox is a React component that can be used in forms to simplify the creation of checkbox inputs where multiple values are possible. 5 | 6 | ## Example 7 | 8 | Below is an example use for the MultiCheckbox component: 9 | 10 | ``` 11 | var options = [ 12 | { value: 1, label: 'One' }, 13 | { value: 2, label: 'Two' } 14 | ]; 15 | 16 | 17 | ``` 18 | 19 | This code snippet will generate the following output: 20 | 21 | ```html 22 | 23 | One 24 | Two 25 | 26 | ``` 27 | 28 | ## Props 29 | 30 | ### `name` 31 | 32 | A name to be used as the name field for each checkbox generated. You do not need to suffix the name with "[]". 33 | 34 | ### `options` 35 | 36 | An array of options, of which each is an object containing a `value` and `label` string to be displayed alongside the checkbox. 37 | 38 | ### `checked` 39 | 40 | An array of option values to be checked in the rendered set of checkboxes. 41 | 42 | ### `defaultChecked` 43 | 44 | If any values should be checked by default, pass these as an array using the `defaultChecked` prop. 45 | 46 | ### `onChange` 47 | 48 | Behaves similarly to the equivalent function handler for standard input elements. This function is invoked when the set of selected checkboxes changes, and is passed a single object argument containing `value` as an array of the newly selected checkbox values. 49 | 50 | ### `disabled` 51 | 52 | Pass `true` to set each of the rendered checkboxes as disabled. 53 | -------------------------------------------------------------------------------- /components/forms/multi-checkbox/index.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | var React = require( 'react' ), 7 | omit = require( 'lodash/object/omit' ), 8 | debug = require( 'debug' )( 'calypso:forms:multi-checkbox' ); 9 | 10 | var MultiCheckbox = module.exports = React.createClass( { 11 | displayName: 'MultiCheckbox', 12 | 13 | propTypes: { 14 | defaultChecked: React.PropTypes.array, 15 | onChange: React.PropTypes.func, 16 | disabled: React.PropTypes.bool 17 | }, 18 | 19 | getInitialState: function() { 20 | return { initialChecked: this.props.defaultChecked }; 21 | }, 22 | 23 | getDefaultProps: function() { 24 | return { 25 | defaultChecked: Object.freeze( [] ), 26 | onChange: function() {}, 27 | disabled: false 28 | }; 29 | }, 30 | 31 | componentWillMount: function() { 32 | debug( 'Mounting ' + this.constructor.displayName + ' React component.' ); 33 | }, 34 | 35 | handleChange: function( event ) { 36 | var target = event.target, 37 | checked = this.props.checked || this.state.initialChecked; 38 | 39 | checked = checked.concat( [ target.value ] ).filter( function( currentValue ) { 40 | return currentValue !== target.value || target.checked; 41 | } ); 42 | 43 | this.props.onChange( { 44 | value: checked 45 | } ); 46 | 47 | event.stopPropagation(); 48 | }, 49 | 50 | getCheckboxElements: function() { 51 | var checked = this.props.checked || this.state.initialChecked; 52 | 53 | return this.props.options.map( function( option ) { 54 | var isChecked = checked.indexOf( option.value ) !== -1; 55 | 56 | return ( 57 | 58 | 59 | { option.label } 60 | 61 | ); 62 | }, this ); 63 | }, 64 | 65 | render: function() { 66 | return { this.getCheckboxElements() }; 67 | } 68 | } ); 69 | -------------------------------------------------------------------------------- /components/forms/multi-checkbox/test/index.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | var assert = require( 'assert' ), 7 | React = require( 'react/addons' ), 8 | TestUtils = React.addons.TestUtils, 9 | jsdom = require( 'jsdom' ); 10 | 11 | /** 12 | * Internal dependencies 13 | */ 14 | var MultiCheckbox = require( '../' ); 15 | 16 | describe( 'MultiCheckbox', function() { 17 | var options = [ 18 | { value: 1, label: 'One' }, 19 | { value: 2, label: 'Two' } 20 | ]; 21 | 22 | before( function( done ) { 23 | jsdom.env( '', function( error, window ) { 24 | global.window = window; 25 | global.document = window.document; 26 | done( error ); 27 | } ); 28 | } ); 29 | 30 | afterEach( function() { 31 | React.unmountComponentAtNode( document.body ); 32 | } ); 33 | 34 | describe( 'rendering', function() { 35 | it( 'should render a set of checkboxes', function() { 36 | var checkboxes = TestUtils.renderIntoDocument( ), 37 | labels = TestUtils.scryRenderedDOMComponentsWithTag( checkboxes, 'label' ); 38 | 39 | assert.equal( options.length, labels.length ); 40 | labels.forEach( function( label, i ) { 41 | var labelNode = label.getDOMNode(), 42 | inputNode = labelNode.querySelector( 'input' ); 43 | assert.equal( 'favorite_colors[]', inputNode.name ); 44 | assert.equal( options[ i ].value, inputNode.value ); 45 | assert.equal( options[ i ].label, labelNode.textContent ); 46 | } ); 47 | } ); 48 | 49 | it( 'should accept an array of checked values', function() { 50 | var checkboxes = TestUtils.renderIntoDocument( ), 51 | labels = TestUtils.scryRenderedDOMComponentsWithTag( checkboxes, 'label' ); 52 | 53 | assert.equal( true, labels[0].getDOMNode().querySelector( 'input' ).checked ); 54 | assert.equal( false, labels[1].getDOMNode().querySelector( 'input' ).checked ); 55 | } ); 56 | 57 | it( 'should accept an array of defaultChecked', function() { 58 | var checkboxes = TestUtils.renderIntoDocument( ), 59 | labels = TestUtils.scryRenderedDOMComponentsWithTag( checkboxes, 'label' ); 60 | 61 | assert.equal( true, labels[0].getDOMNode().querySelector( 'input' ).checked ); 62 | assert.equal( false, labels[1].getDOMNode().querySelector( 'input' ).checked ); 63 | } ); 64 | 65 | it( 'should accept an onChange event handler', function( done ) { 66 | var checkboxes = TestUtils.renderIntoDocument( ), 67 | labels = TestUtils.scryRenderedDOMComponentsWithTag( checkboxes, 'label' ); 68 | 69 | TestUtils.Simulate.change( labels[0].getDOMNode().querySelector( 'input' ), { 70 | target: { 71 | value: options[0].value, 72 | checked: true 73 | } 74 | } ); 75 | 76 | function finishTest( event ) { 77 | assert.deepEqual( [ options[0].value ], event.value ); 78 | done(); 79 | } 80 | } ); 81 | 82 | it( 'should accept a disabled boolean', function() { 83 | var checkboxes = TestUtils.renderIntoDocument( ), 84 | labels = TestUtils.scryRenderedDOMComponentsWithTag( checkboxes, 'label' ); 85 | 86 | assert.ok( labels[0].getDOMNode().querySelector( 'input' ).disabled ); 87 | assert.ok( labels[1].getDOMNode().querySelector( 'input' ).disabled ); 88 | } ); 89 | 90 | it( 'should transfer props to the rendered element', function() { 91 | var className = 'transferred-class', 92 | checkboxes = TestUtils.renderIntoDocument( ), 93 | div = TestUtils.findRenderedDOMComponentWithTag( checkboxes, 'div' ); 94 | 95 | assert.notEqual( -1, div.getDOMNode().className.indexOf( className ) ); 96 | } ); 97 | } ); 98 | } ); 99 | -------------------------------------------------------------------------------- /components/forms/range/Makefile: -------------------------------------------------------------------------------- 1 | REPORTER ?= spec 2 | MOCHA ?= ../../../node_modules/.bin/mocha 3 | 4 | test: 5 | @NODE_ENV=test NODE_PATH=test:../../ $(MOCHA) --compilers jsx:jsx-require-extension --reporter $(REPORTER) 6 | 7 | .PHONY: test 8 | -------------------------------------------------------------------------------- /components/forms/range/README.md: -------------------------------------------------------------------------------- 1 | Range 2 | ===== 3 | 4 | Range is a React component used to render a range input field. It is essentially an enhanced version of ``, enabling support for a value tooltip and content to be shown at the ends of the range field. 5 | 6 |  7 | 8 | ## Usage 9 | 10 | Refer to the following code snippet for a typical usage example: 11 | 12 | ```jsx 13 | } 15 | maxContent={ } 16 | max="100" 17 | value={ this.state.rangeValue } 18 | onChange={ this.onChange } 19 | showValueLabel={ true } /> 20 | ``` 21 | 22 | The Range component does not track its own value state, much like any other form input in React. Refer to the React Forms documentation for more guidance on tracking form value state. 23 | 24 | ## Props 25 | 26 | Props not listed below will be passed automatically to the rendered range input element. 27 | 28 | ### `minContent` (`string` or `Element`) 29 | 30 | Content to be shown preceding the range input. 31 | 32 | ### `maxContent` (`string` or `Element`) 33 | 34 | Content to be shown following the range input. 35 | 36 | ### `showValueLabel` (`boolean`) 37 | 38 | A boolean indicating whether a tooltip is to be shown with the current range value. 39 | -------------------------------------------------------------------------------- /components/forms/range/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | omit = require( 'lodash/object/omit' ), 6 | classnames = require( 'classnames' ), 7 | uniqueId = require( 'lodash/utility/uniqueId' ); 8 | 9 | /** 10 | * External dependencies 11 | */ 12 | var FormRange = require( 'forms/form-range' ); 13 | 14 | module.exports = React.createClass( { 15 | displayName: 'Range', 16 | 17 | propTypes: { 18 | minContent: React.PropTypes.oneOfType( [ React.PropTypes.element, React.PropTypes.string ] ), 19 | maxContent: React.PropTypes.oneOfType( [ React.PropTypes.element, React.PropTypes.string ] ), 20 | min: React.PropTypes.oneOfType( [ React.PropTypes.string, React.PropTypes.number ] ), 21 | max: React.PropTypes.oneOfType( [ React.PropTypes.string, React.PropTypes.number ] ), 22 | value: React.PropTypes.oneOfType( [ React.PropTypes.string, React.PropTypes.number ] ), 23 | showValueLabel: React.PropTypes.bool 24 | }, 25 | 26 | getInitialState: function() { 27 | return { 28 | id: uniqueId( 'range' ) 29 | }; 30 | }, 31 | 32 | getDefaultProps: function() { 33 | return { 34 | min: 0, 35 | max: 10, 36 | value: 0, 37 | showValueLabel: false 38 | }; 39 | }, 40 | 41 | getMinContentElement: function() { 42 | if ( this.props.minContent ) { 43 | return { this.props.minContent }; 44 | } 45 | }, 46 | 47 | getMaxContentElement: function() { 48 | if ( this.props.maxContent ) { 49 | return { this.props.maxContent }; 50 | } 51 | }, 52 | 53 | getValueLabelElement: function() { 54 | var left, offset; 55 | 56 | if ( this.props.showValueLabel ) { 57 | left = 100 * ( this.props.value - this.props.min ) / ( this.props.max - this.props.min ); 58 | 59 | // The center of the slider thumb is not aligned to the same 60 | // percentage stops as an absolute positioned element will be. 61 | // Therefore, we adjust based on the thumb's position relative to 62 | // its own size. Ideally, we would use `getComputedStyle` here, 63 | // but this method doesn't support the thumb pseudo-element in all 64 | // browsers. The multiplier is equal to half of the thumb's width. 65 | // 66 | // Normal: 67 | // v v v 68 | // |( )----( )----( )| 69 | // 70 | // Adjusted: 71 | // v v v 72 | // |( )----( )----( )| 73 | offset = Math.floor( 13 * ( ( 50 - left ) / 50 ) ); // 26px / 2 = 13px 74 | 75 | return ( 76 | 77 | { this.props.value } 78 | 79 | ); 80 | } 81 | }, 82 | 83 | render: function() { 84 | var classes = classnames( this.props.className, 'range', { 85 | 'has-min-content': !! this.props.minContent, 86 | 'has-max-content': !! this.props.maxContent 87 | } ); 88 | 89 | return ( 90 | 91 | { this.getMinContentElement() } 92 | 93 | { this.getMaxContentElement() } 94 | { this.getValueLabelElement() } 95 | 96 | ); 97 | } 98 | } ); 99 | -------------------------------------------------------------------------------- /components/forms/range/style.scss: -------------------------------------------------------------------------------- 1 | $range-content-padding: 24px; 2 | 3 | .range { 4 | position: relative; 5 | 6 | &.has-min-content { 7 | margin-left: $range-content-padding; 8 | } 9 | 10 | &.has-max-content { 11 | margin-right: $range-content-padding; 12 | } 13 | } 14 | 15 | .range__content { 16 | position: absolute; 17 | top: 50%; 18 | transform: translateY( -50% ); 19 | color: $gray; 20 | 21 | &.is-min { 22 | left: ( -1 * $range-content-padding ); 23 | } 24 | 25 | &.is-max { 26 | right: ( -1 * $range-content-padding ); 27 | } 28 | } 29 | 30 | .range__content > * { 31 | display: block; 32 | } 33 | 34 | .range__label { 35 | position: absolute; 36 | bottom: 100%; 37 | z-index: 10; 38 | transform: translateX( -50% ); 39 | padding-bottom: 5px; 40 | pointer-events: none; 41 | 42 | &::before { 43 | content: ""; 44 | position: absolute; 45 | bottom: 1px; 46 | left: 50%; 47 | display: block; 48 | width: 8px; 49 | height: 8px; 50 | margin-left: -4px; 51 | transform: rotate( 45deg ); 52 | background-color: $white; 53 | /* @noflip */ 54 | border-right: 1px solid lighten( $gray,20 ); 55 | /* @noflip */ 56 | border-bottom: 1px solid lighten( $gray,20 ); 57 | } 58 | } 59 | 60 | .range__label-inner { 61 | display: block; 62 | padding: 8px 12px; 63 | border: 1px solid lighten( $gray,20 ); 64 | border-radius: 2px; 65 | background-color: $white; 66 | box-shadow: 0px 5px 20px rgba( 0, 0, 0, .2 ); 67 | } 68 | -------------------------------------------------------------------------------- /components/forms/range/test/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var expect = require( 'chai' ).expect, 5 | React = require( 'react/addons' ), 6 | TestUtils = React.addons.TestUtils, 7 | jsdom = require( 'jsdom' ).jsdom; 8 | 9 | /** 10 | * Internal dependencies 11 | */ 12 | var Range = require( '../' ); 13 | 14 | describe( 'Range', function() { 15 | before( function() { 16 | global.window = jsdom().defaultView; 17 | global.document = window.document; 18 | } ); 19 | 20 | after( function() { 21 | delete global.window; 22 | delete global.document; 23 | } ); 24 | 25 | afterEach( function() { 26 | React.unmountComponentAtNode( document.body ); 27 | } ); 28 | 29 | it( 'should render beginning content if passed a `minContent` prop', function() { 30 | var range = TestUtils.renderIntoDocument( } /> ); 31 | TestUtils.findRenderedDOMComponentWithClass( range, 'noticon-minus' ); 32 | } ); 33 | 34 | it( 'should not render ending content if not passed a `maxContent` prop', function() { 35 | var range = TestUtils.renderIntoDocument( } /> ), 36 | content = TestUtils.scryRenderedDOMComponentsWithClass( range, 'range__content' ); 37 | 38 | expect( content ).to.have.length( 1 ); 39 | expect( content[0].props.className ).to.contain( 'is-min' ); 40 | } ); 41 | 42 | it( 'should render ending content if passed a `maxContent` prop', function() { 43 | var range = TestUtils.renderIntoDocument( } /> ); 44 | TestUtils.findRenderedDOMComponentWithClass( range, 'noticon-plus' ); 45 | } ); 46 | 47 | it( 'should not render beginning content if not passed a `minContent` prop', function() { 48 | var range = TestUtils.renderIntoDocument( } /> ), 49 | content = TestUtils.scryRenderedDOMComponentsWithClass( range, 'range__content' ); 50 | 51 | expect( content ).to.have.length( 1 ); 52 | expect( content[0].props.className ).to.contain( 'is-max' ); 53 | } ); 54 | 55 | it( 'should render a value label if passed a truthy `showValueLabel` prop', function() { 56 | var range = TestUtils.renderIntoDocument( ), 57 | label = TestUtils.findRenderedDOMComponentWithClass( range, 'range__label' ); 58 | 59 | expect( label.getDOMNode().textContent ).to.equal( '8' ); 60 | } ); 61 | } ); 62 | -------------------------------------------------------------------------------- /components/forms/select-opt-groups.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | var React = require( 'react' ), 7 | debug = require( 'debug' )( 'calypso:forms:select-opt-groups' ); 8 | 9 | var SelectOptGroups = React.createClass( { 10 | 11 | displayName: 'SelectOptGroups', 12 | 13 | componentWillMount: function() { 14 | debug( 'Mounting SelectOptGroups React component.' ); 15 | }, 16 | 17 | render: function() { 18 | return ( 19 | 20 | { this.props.optGroups.map( function( optGroup ) { 21 | return ( 22 | 23 | { optGroup.options.map( function( option ) { 24 | return { option.label }; 25 | } ) } 26 | 27 | ); 28 | } ) } 29 | 30 | ); 31 | } 32 | } ); 33 | 34 | module.exports = SelectOptGroups; 35 | -------------------------------------------------------------------------------- /components/forms/sortable-list/README.md: -------------------------------------------------------------------------------- 1 | SortableList 2 | =============== 3 | 4 | SortableList is a React component to enable device-friendly item rearranging. For non-touch devices, child elements of SortableList can be rearranged by drag-and-drop. On touch devices, the user must tap an item to activate it before rearranging via one of two directional button controls. 5 | 6 | *Desktop* 7 | 8 |  9 | 10 | *Touch* 11 | 12 |  13 | 14 | ## Usage 15 | 16 | Below is example usage for rendering an SortableList: 17 | 18 | ```jsx 19 | 20 | First 21 | Second 22 | 23 | ``` 24 | 25 | In traditional React fashion, a SortableList does not track its own state, but instead expects you as the developer to track changes through an `onChange` handler, re-rendering the component with the updated element ordering. Refer to the following example: 26 | 27 | ```jsx 28 | var SortableList = require( 'forms/sortable-list' ); 29 | 30 | module.exports = React.createClass( { 31 | getInitialState: function() { 32 | return { 33 | items: [ 'First', 'Second', 'Third' ]; 34 | }; 35 | }, 36 | 37 | onChange: function( order ) { 38 | var items = []; 39 | 40 | this.state.items.forEach( function( item, i ) { 41 | items[ order[ i ] ] = item; 42 | }, this ); 43 | 44 | this.setState( { 45 | items: items 46 | } ); 47 | }, 48 | 49 | render: function() { 50 | var items = this.items.map( function( item ) { 51 | return { item }; 52 | } ); 53 | 54 | return { items }; 55 | } 56 | } ); 57 | ``` 58 | 59 | ## Props 60 | 61 | ### `direction` 62 | 63 | Accepts either "horizontal" (default) or "vertical". A horizontal SortableList is rendered from left to right and can wrap. A vertical SortableList is rendered from top to bottom. 64 | 65 | ### `allowDrag` 66 | 67 | If dragging is not desired in any device context, pass an `allowDrag` value of `false`. Defaults to `true`. 68 | 69 | ### `onChange` 70 | 71 | A change handler to invoke when the user has modified the ordering of elements. This function is passed a single array argument. Each index of the array aligns the original element ordering, and the value represents the element's new position on a zero-based index. Using the example above as a reference, if a user were to move "First" to be the second element in the list, you could expect an `onChange` argument of `[ 1, 0, 2 ]`. 72 | -------------------------------------------------------------------------------- /components/forms/sortable-list/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | zipObject = require( 'lodash/array/zipObject' ), 6 | findIndex = require( 'lodash/array/findIndex' ), 7 | assign = require( 'lodash/object/assign' ), 8 | debug = require( 'debug' )( 'calypso:forms:sortable-list' ); 9 | 10 | /** 11 | * Internal dependencies 12 | */ 13 | var touchDetect = require( 'touch-detect' ); 14 | 15 | module.exports = React.createClass( { 16 | displayName: 'SortableList', 17 | 18 | propTypes: { 19 | direction: React.PropTypes.oneOf( [ 'horizontal', 'vertical' ] ), 20 | allowDrag: React.PropTypes.bool, 21 | onChange: React.PropTypes.func 22 | }, 23 | 24 | getInitialState: function() { 25 | return { 26 | activeIndex: null, 27 | activeOrder: null, 28 | position: null 29 | }; 30 | }, 31 | 32 | getDefaultProps: function() { 33 | return { 34 | direction: 'horizontal', 35 | allowDrag: true, 36 | onChange: function() {} 37 | }; 38 | }, 39 | 40 | componentWillMount: function() { 41 | debug( 'Mounting ' + this.constructor.displayName + ' React component.' ); 42 | }, 43 | 44 | componentDidMount: function() { 45 | document.addEventListener( 'mousemove', this.onMouseMove ); 46 | }, 47 | 48 | componentWillUnmount: function() { 49 | document.removeEventListener( 'mousemove', this.onMouseMove ); 50 | }, 51 | 52 | getPositionForCursorElement: function( element, event ) { 53 | return { 54 | top: event.clientY - ( element.clientHeight / 2 ), 55 | left: event.clientX - ( element.clientWidth / 2 ) 56 | }; 57 | }, 58 | 59 | compareCursorVerticalToElement: function( element, event ) { 60 | var rect = element.getBoundingClientRect(); 61 | 62 | if ( event.clientY < rect.top ) { 63 | return -1; 64 | } else if ( event.clientY > rect.bottom ) { 65 | return 1; 66 | } else { 67 | return 0; 68 | } 69 | }, 70 | 71 | isCursorBeyondElementThreshold: function( element, direction, permittedVertical, event ) { 72 | var rect = element.getBoundingClientRect(); 73 | 74 | // We check for Y bounds on right and left and not X bounds for top 75 | // and bottom because horizontal lists can have line breaks, so we 76 | // should be careful to consider vertical position in those cases 77 | switch ( direction ) { 78 | case 'top': 79 | return event.clientY <= rect.top + ( rect.height / 2 ); 80 | case 'right': 81 | return event.clientX >= rect.left + ( rect.width / 2 ) && 82 | ( 'top' === permittedVertical || event.clientY >= rect.top ) && 83 | ( 'bottom' === permittedVertical || event.clientY <= rect.bottom ); 84 | case 'bottom': 85 | return event.clientY >= rect.top + ( rect.height / 2 ); 86 | case 'left': 87 | return event.clientX <= rect.left + ( rect.width / 2 ) && 88 | ( 'top' === permittedVertical || event.clientY >= rect.top ) && 89 | ( 'bottom' === permittedVertical || event.clientY <= rect.bottom ); 90 | default: 91 | return false; 92 | } 93 | }, 94 | 95 | getAdjustedElementIndex: function( index ) { 96 | // The actie order array is used as an array where each index matches 97 | // the original prop children indices, but the values correspond to 98 | // their visible position index 99 | if ( this.state.activeOrder ) { 100 | return this.state.activeOrder[ index ]; 101 | } else { 102 | return index; 103 | } 104 | }, 105 | 106 | getCursorElementIndex: function( event ) { 107 | var cursorCompare = this.compareCursorVerticalToElement( this.refs.list.getDOMNode(), event ), 108 | adjustedActiveIndex = this.getAdjustedElementIndex( this.state.activeIndex ), 109 | shadowRect = this.refs[ 'wrap-shadow-' + this.state.activeIndex ].getDOMNode().getBoundingClientRect(), 110 | index; 111 | 112 | index = findIndex( this.props.children, function( child, i ) { 113 | var isBeyond, adjustedElementIndex, permittedVertical; 114 | 115 | // Avoid self-comparisons for the active item 116 | if ( i === this.state.activeIndex ) { 117 | return false; 118 | } 119 | 120 | // Since elements are now shifted around, we want to find their 121 | // visible position to make accurate comparisons 122 | adjustedElementIndex = this.getAdjustedElementIndex( i ); 123 | 124 | // When rearranging on a horizontal plane, permit breaking of 125 | // vertical if the cursor is outside the list element on the 126 | // same vertical, and only if the element is on the same line as 127 | // the active item's shadow element 128 | if ( 'horizontal' === this.props.direction ) { 129 | if ( 1 === cursorCompare && this.refs[ 'wrap-' + i ].getDOMNode().getBoundingClientRect().top >= shadowRect.top ) { 130 | permittedVertical = 'bottom'; 131 | } else if ( -1 === cursorCompare && this.refs[ 'wrap-' + i ].getDOMNode().getBoundingClientRect().bottom <= shadowRect.bottom ) { 132 | permittedVertical = 'top'; 133 | } 134 | } 135 | 136 | if ( adjustedElementIndex < adjustedActiveIndex ) { 137 | // If the item which is currently before the active item is 138 | // suddenly after, return this item's index 139 | isBeyond = this.isCursorBeyondElementThreshold( 140 | this.refs[ 'wrap-' + i ].getDOMNode(), 141 | 'horizontal' === this.props.direction ? 'left' : 'top', 142 | permittedVertical, 143 | event 144 | ); 145 | } else if ( adjustedElementIndex > adjustedActiveIndex ) { 146 | // If the item which is currently after the active item is 147 | // suddenly before, return this item's index 148 | isBeyond = isBeyond || this.isCursorBeyondElementThreshold( 149 | this.refs[ 'wrap-' + i ].getDOMNode(), 150 | 'horizontal' === this.props.direction ? 'right' : 'bottom', 151 | permittedVertical, 152 | event 153 | ); 154 | } 155 | 156 | return isBeyond; 157 | }.bind( this ) ); 158 | 159 | return this.getAdjustedElementIndex( index ); 160 | }, 161 | 162 | moveItem: function( direction ) { 163 | var increment = 'previous' === direction ? -1 : 1, 164 | activeOrder = Object.keys( this.props.children ).map( Number ); 165 | 166 | activeOrder[ this.state.activeIndex + increment ] = this.state.activeIndex; 167 | activeOrder[ this.state.activeIndex ] = this.state.activeIndex + increment; 168 | 169 | this.props.onChange( activeOrder ); 170 | 171 | this.setState( { 172 | activeIndex: activeOrder[ this.state.activeIndex ] 173 | } ); 174 | }, 175 | 176 | onMouseDown: function( index, event ) { 177 | this.setState( { 178 | activeIndex: index, 179 | position: this.getPositionForCursorElement( event.currentTarget.firstChild, event ) 180 | } ); 181 | }, 182 | 183 | onMouseMove: function( event ) { 184 | var activeOrder, newIndex; 185 | if ( null === this.state.activeIndex || ! this.props.allowDrag || touchDetect.hasTouch() ) { 186 | return; 187 | } 188 | 189 | activeOrder = this.state.activeOrder; 190 | 191 | // Find the new cursor location 192 | newIndex = this.getCursorElementIndex( event ); 193 | if ( newIndex >= 0 ) { 194 | if ( this.state.activeIndex === newIndex ) { 195 | // If we're changing the index back to the active item's 196 | // original position, we can shortcut this by simply 197 | // setting the order back to default 198 | activeOrder = null; 199 | } else { 200 | // Create an ordered array of items using the index from 201 | // the child props array 202 | activeOrder = Object.keys( this.props.children ).map( Number ); 203 | 204 | for ( var i = 0, il = activeOrder.length; i < il; i++ ) { 205 | if ( i >= newIndex && i < this.state.activeIndex ) { 206 | // Bump up any item below the active index and 207 | // above the new index 208 | activeOrder[ i ] = i + 1; 209 | } else if ( i <= newIndex && i > this.state.activeIndex ) { 210 | // Bump down any item above the active index 211 | // and below the new index 212 | activeOrder[ i ] = i - 1; 213 | } 214 | } 215 | 216 | // Set the new index for the active item 217 | activeOrder[ this.state.activeIndex ] = newIndex; 218 | } 219 | } 220 | 221 | this.setState( { 222 | position: this.getPositionForCursorElement( this.refs[ 'wrap-' + this.state.activeIndex ].getDOMNode().firstChild, event ), 223 | activeOrder: activeOrder 224 | } ); 225 | }, 226 | 227 | onMouseUp: function() { 228 | if ( this.state.activeOrder ) { 229 | this.props.onChange( this.state.activeOrder ); 230 | } 231 | 232 | this.setState( { 233 | activeIndex: null, 234 | activeOrder: null, 235 | position: null 236 | } ); 237 | }, 238 | 239 | onClick: function( index ) { 240 | this.setState( { 241 | activeIndex: index 242 | } ); 243 | }, 244 | 245 | getOrderedListItemElements: function() { 246 | return React.Children.map( this.props.children, function( child, index ) { 247 | var isActive = this.state.activeIndex === index, 248 | isDraggable = this.props.allowDrag && ! touchDetect.hasTouch(), 249 | events = isDraggable ? [ 'onMouseDown', 'onMouseUp' ] : [ 'onClick' ], 250 | style = { order: this.getAdjustedElementIndex( index ) }, 251 | classes = React.addons.classSet( { 252 | 'sortable-list__item': true, 253 | 'is-active': isActive, 254 | 'is-draggable': isDraggable 255 | } ), item; 256 | 257 | events = zipObject( events.map( function( event ) { 258 | return [ event, this[ event ].bind( null, index ) ]; 259 | }, this ) ); 260 | 261 | if ( isActive ) { 262 | assign( style, this.state.position ); 263 | } 264 | 265 | item = { child }; 266 | 267 | if ( isActive && isDraggable ) { 268 | return [ 269 | { child }, 270 | item 271 | ]; 272 | } else { 273 | return item; 274 | } 275 | }, this ); 276 | }, 277 | 278 | getNavigationElement: function() { 279 | if ( this.props.allowDrag && ! touchDetect.hasTouch() ) { 280 | return; 281 | } 282 | 283 | return ( 284 | 285 | 290 | { this.translate( 'Move previous' ) } 291 | 292 | 293 | 298 | { this.translate( 'Move next' ) } 299 | 300 | 301 | 302 | ); 303 | }, 304 | 305 | render: function() { 306 | var classes = React.addons.classSet( { 307 | 'sortable-list': true, 308 | 'is-horizontal': 'horizontal' === this.props.direction, 309 | 'is-vertical': 'vertical' === this.props.direction 310 | } ); 311 | 312 | return ( 313 | 314 | { this.getOrderedListItemElements() } 315 | { this.getNavigationElement() } 316 | 317 | ); 318 | } 319 | } ); 320 | -------------------------------------------------------------------------------- /components/forms/sortable-list/index.scss: -------------------------------------------------------------------------------- 1 | .sortable-list__list { 2 | display: flex; 3 | margin: 0; 4 | user-select: none; 5 | } 6 | 7 | .sortable-list.is-horizontal .sortable-list__list { 8 | flex-direction: row; 9 | flex-wrap: wrap; 10 | } 11 | 12 | .sortable-list.is-vertical .sortable-list__list { 13 | flex-direction: column; 14 | } 15 | 16 | .sortable-list__item { 17 | display: inline-block; 18 | 19 | &.is-active > * { 20 | box-shadow: 0 0 0 2px white, 0 0 0 4px $blue-medium; 21 | } 22 | 23 | &.is-draggable.is-active { 24 | position: fixed; 25 | z-index: 1000; 26 | } 27 | 28 | &.is-shadow { 29 | filter: url( "data:image/svg+xml;utf8,#grayscale" ); 30 | filter: grayscale( 100% ); 31 | opacity: 0.5; 32 | } 33 | 34 | &.is-draggable > * { 35 | cursor: move; 36 | box-shadow: none; 37 | } 38 | } 39 | 40 | .sortable-list__navigation { 41 | margin-top: 18px; 42 | text-align: right; 43 | } 44 | 45 | .sortable-list__navigation-button { 46 | padding: 8px; 47 | background-color: lighten( $gray, 33% ); 48 | border: 1px solid lighten( $gray, 20% ); 49 | color: $gray; 50 | 51 | &:not( :disabled ):hover { 52 | cursor: pointer; 53 | color: $blue-medium; 54 | } 55 | 56 | &:disabled { 57 | cursor: not-allowed; 58 | opacity: 0.4; 59 | } 60 | } 61 | 62 | .sortable-list.is-horizontal .sortable-list__navigation-button { 63 | display: inline-block; 64 | 65 | &.is-previous { 66 | padding-left: 12px; 67 | border-top-left-radius: 50%; 68 | border-bottom-left-radius: 50%; 69 | } 70 | 71 | &.is-next { 72 | margin-left: -1px; 73 | padding-right: 12px; 74 | border-top-right-radius: 50%; 75 | border-bottom-right-radius: 50%; 76 | } 77 | } 78 | 79 | .sortable-list.is-horizontal .sortable-list__navigation-button .noticon { 80 | transform: rotate( 90deg ) translateX( 1px ); 81 | } 82 | 83 | .sortable-list.is-vertical .sortable-list__navigation-button { 84 | display: block; 85 | margin-left: auto; 86 | 87 | &.is-previous { 88 | border-top-right-radius: 50%; 89 | border-top-left-radius: 50%; 90 | } 91 | 92 | &.is-next { 93 | margin-top: -1px; 94 | border-bottom-right-radius: 50%; 95 | border-bottom-left-radius: 50%; 96 | } 97 | } 98 | 99 | .sortable-list.is-vertical .sortable-list__navigation-button .noticon { 100 | transform: rotate( 180deg ) translateX( 1px ); 101 | } 102 | 103 | .sortable-list__navigation-button .noticon { 104 | font-size: 24px; 105 | font-weight: bold; 106 | } 107 | -------------------------------------------------------------------------------- /components/header/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ); 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | var Config = require( '../config.js' ), 11 | Nav = require( '../nav' ); 12 | 13 | /** 14 | * Header Component 15 | */ 16 | var Header = React.createClass( { 17 | getInitialState: function() { 18 | { 19 | // to-do: 'User Management', 'SVN Access', 'Revisions', 'Support', 'Billing' 20 | } 21 | return { 22 | nav: [ 23 | { 24 | title: 'Dashboard', 25 | url: 'vip-dashboard' 26 | } 27 | ] 28 | }; 29 | }, 30 | 31 | render: function() { 32 | return ( 33 | 34 | 35 | 36 | , 37 | 38 | { this.props.children } 39 | 40 | 41 | ); 42 | } 43 | } ); 44 | 45 | module.exports = Header; 46 | -------------------------------------------------------------------------------- /components/header/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Top Header 3 | */ 4 | .top-header { 5 | padding: 0.5em 3em; 6 | background: $grey-dark; 7 | } 8 | 9 | .top-header h1 { 10 | margin: 0.5em 0 0 0; 11 | } 12 | 13 | .top-header__logo { 14 | width: 82px; 15 | height: auto; 16 | margin: 0; 17 | } -------------------------------------------------------------------------------- /components/main/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ); 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | 11 | /** 12 | * Widget Component 13 | */ 14 | var Main = React.createClass( { 15 | render: function() { 16 | return ( 17 | 18 | { this.props.children } 19 | 20 | ); 21 | } 22 | } ); 23 | 24 | module.exports = Main; 25 | -------------------------------------------------------------------------------- /components/nav/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ); 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | var Config = require( '../config.js' ); 10 | 11 | /** 12 | * Navigation component 13 | */ 14 | var Nav = React.createClass( { 15 | getInitialState: function() { 16 | return { 17 | focused: 0 18 | }; 19 | }, 20 | 21 | clicked: function( index ) { 22 | this.setState( { focused: index } ); 23 | }, 24 | 25 | render: function() { 26 | var self = this; 27 | 28 | // loop over the array of menu entries, 29 | return ( 30 | 31 | { this.props.items.map( function( m, index ) { 32 | var style = ''; 33 | 34 | if ( self.state.focused === index ) { 35 | style = 'active'; 36 | } 37 | 38 | return 39 | { m.title } 40 | ; 41 | } ) } 42 | 43 | 44 | ); 45 | } 46 | } ); 47 | 48 | module.exports = Nav; 49 | -------------------------------------------------------------------------------- /components/nav/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Nav 3 | */ 4 | .top-header__menu { 5 | margin: 0; 6 | font-size: .9em; 7 | text-transform: uppercase; 8 | display: none; 9 | 10 | ul { 11 | margin: 1.5em 0 0 0; 12 | padding: 0; 13 | list-style: none; 14 | } 15 | 16 | li { 17 | display: inline-block; 18 | margin: 0 2em 0 0; 19 | } 20 | 21 | a { 22 | position: relative; 23 | padding: 1.125em .25em 1.75em .25em; 24 | color: rgba($white, .68); 25 | text-decoration: none; 26 | 27 | &:hover, 28 | &.active { 29 | color: rgba($white, 1); 30 | } 31 | 32 | &:after { 33 | content: ""; 34 | position: absolute; 35 | bottom: 0; 36 | left: 50%; 37 | width: 0; 38 | border-bottom: 4px solid rgba($blue, 0); 39 | } 40 | 41 | &:hover:after, 42 | &.active:after { 43 | width: 100%; 44 | left: 0%; 45 | border-bottom: 4px solid rgba($blue, 1); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /components/stats-charts/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ); 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | 10 | /** 11 | * Stats Charts Component 12 | */ 13 | var Stats_Charts = React.createClass( { 14 | chartsAnim: function( selector, percent ) { 15 | var selectorDiv = document.getElementById( selector ); 16 | var path = selectorDiv; 17 | var pathLen = path.getTotalLength(); 18 | var adjustedLen = ( 100 - percent ) * pathLen / 100; 19 | selectorDiv.style['stroke-dashoffset'] = adjustedLen; 20 | }, 21 | 22 | getInitialState: function() { 23 | return { 24 | value: this.props.value || 43 25 | }; 26 | }, 27 | 28 | componentDidMount: function() { 29 | // @todo: fetch value automatically 30 | // this.chartsAnim( 'chart-views-desktop', this.state.value ); 31 | 32 | this.chartsAnim( 'chart-views-desktop', 43 ); 33 | this.chartsAnim( 'chart-views-mobile', 82 ); 34 | }, 35 | 36 | render: function() { 37 | return ( 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | } 60 | } ); 61 | 62 | module.exports = Stats_Charts; 63 | -------------------------------------------------------------------------------- /components/stats-charts/style.scss: -------------------------------------------------------------------------------- 1 | .chart-circular__block { 2 | display: inline-block; 3 | width: $graph-size; 4 | height: $graph-size; 5 | margin: 30px 20px 20px; 6 | 7 | @media screen and (max-width: 1024px) { 8 | display: none; 9 | } 10 | } 11 | 12 | .chart-circular__data { 13 | position: absolute; 14 | width: $graph-size; 15 | text-align: center; 16 | 17 | transform: translateY(46%); 18 | 19 | span { 20 | display: block; 21 | } 22 | 23 | .numbers__value { 24 | margin-bottom: .25em; 25 | font-size: 36px; 26 | } 27 | 28 | } 29 | 30 | .chart-circular__one { 31 | 32 | .numbers__value { 33 | color: $graph-blue; 34 | } 35 | 36 | path.chart-graph { 37 | stroke: $graph-blue; 38 | } 39 | } 40 | .chart-circular__two { 41 | 42 | .numbers__value { 43 | color: $graph-orange; 44 | } 45 | 46 | path.chart-graph { 47 | stroke: $graph-orange; 48 | } 49 | } 50 | 51 | .chart-graph { 52 | stroke-dasharray: 482; 53 | stroke-dashoffset: 482; 54 | transition: all 1.4s ease; 55 | } 56 | .chart-graph-dummy { 57 | stroke: $graph-dummy; 58 | } -------------------------------------------------------------------------------- /components/stats-numbers/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ); 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | //var CounTo = require( '../count' ); 11 | 12 | /** 13 | * Stats Number Component 14 | */ 15 | var Stats_Numbers = React.createClass( { 16 | getInitialState: function() { 17 | return { 18 | value: this.props.value, 19 | trend: this.props.trend, 20 | type: this.props.type, 21 | }; 22 | }, 23 | 24 | spin: function( e ) { 25 | this.setState( { 26 | value: ( Math.floor( Math.random() * 10000 ) + 1 ), 27 | trend: Math.floor( Math.random() * 20 ) - 10 28 | } ); 29 | }, 30 | render: function() { 31 | var trend = ''; 32 | 33 | if ( this.state.trend > 0 ) { 34 | trend = 'trend-positive'; 35 | } else if ( this.state.trend < 0 ) { 36 | trend = 'trend-negative'; 37 | } else { 38 | trend = 'trend-neutral'; 39 | } 40 | 41 | if ( this.state.type === 'chart' ) { 42 | return ( 43 | 44 | { this.state.value + '%' } 45 | { this.props.description } 46 | { this.state.trend + '%' } 47 | 48 | ); 49 | } else { 50 | return ( 51 | 52 | 53 | { this.state.trend + '%' } 54 | { this.props.description } 55 | 56 | ); 57 | } 58 | } 59 | } ); 60 | 61 | module.exports = Stats_Numbers; 62 | -------------------------------------------------------------------------------- /components/stats-numbers/style.scss: -------------------------------------------------------------------------------- 1 | .numbers__value { 2 | display: inline-block; 3 | margin-bottom: 6px; 4 | font-size: 44px; 5 | font-weight: 300; 6 | line-height: 1em; 7 | color: $grey-blue; 8 | 9 | &.value-primary { 10 | color: $graph-blue; 11 | } 12 | 13 | &.value-secondary { 14 | color: $graph-orange; 15 | } 16 | } 17 | 18 | .numbers__description { 19 | margin-bottom: 1.15em; 20 | font-size: 12px; 21 | font-weight: 400; 22 | line-height: 1em; 23 | color: $grey-blue-fade; 24 | display: block; 25 | } 26 | 27 | .numbers__trend { 28 | position: relative; 29 | display: inline-block; 30 | font-size: 12px; 31 | font-weight: 600; 32 | line-height: 1em; 33 | 34 | &.trend-positive, 35 | &.trend-negative, 36 | &.trend-neutral { 37 | margin-left: 16px; 38 | 39 | &:before { 40 | content: ""; 41 | position: absolute; 42 | top: 2px; 43 | left: -12px; 44 | width: 0; 45 | height: 0; 46 | border-style: solid; 47 | } 48 | } 49 | 50 | &.trend-positive { 51 | color: $trend-positive; 52 | 53 | &:before { 54 | border-width: 0 4px 7px 4px; 55 | border-color: transparent transparent $trend-positive transparent; 56 | } 57 | } 58 | &.trend-negative { 59 | color: $trend-negative; 60 | 61 | &:before { 62 | border-width: 7px 4px 0 4px; 63 | border-color: $trend-negative transparent transparent transparent; 64 | } 65 | } 66 | &.trend-neutral { 67 | color: $trend-neutral; 68 | 69 | &:before { 70 | border-width: 4px 0 4px 7px; 71 | border-color: transparent transparent transparent $trend-neutral; 72 | } 73 | &:after { 74 | content: ""; 75 | position: absolute; 76 | top: 2px; 77 | left: -12px; 78 | width: 0; 79 | height: 0; 80 | border-style: solid; 81 | border-width: 4px 7px 4px 0; 82 | border-color: transparent $trend-neutral transparent transparent; 83 | } 84 | } 85 | 86 | &.trend-center { 87 | 88 | &.trend-positive, 89 | &.trend-negative { 90 | margin-left: 10px; 91 | } 92 | 93 | &:before { 94 | bottom: 25%; 95 | left: 34%; 96 | } 97 | } 98 | } 99 | 100 | 101 | .stats__posts { 102 | 103 | .numbers__value { 104 | color: $graph-blue; 105 | 106 | } 107 | } 108 | 109 | .stats__comments { 110 | 111 | .numbers__value { 112 | color: $graph-orange; 113 | 114 | } 115 | } 116 | 117 | .stats__total-posts, 118 | .stats__total-users, 119 | .stats__total-media, 120 | .stats__total-loc { 121 | position: relative; 122 | min-width: 180px; 123 | 124 | &:before { 125 | display: inline-block; 126 | position: absolute; 127 | top: 14px; 128 | left: -24px; 129 | font-size: 32px; 130 | font-family: "Genericons"; 131 | font-style: normal; 132 | font-weight: normal; 133 | font-variant: normal; 134 | line-height: 1; 135 | vertical-align: top; 136 | text-align: center; 137 | text-decoration: inherit; 138 | text-transform: none; 139 | -moz-osx-font-smoothing: grayscale; 140 | -webkit-font-smoothing: antialiased; 141 | speak: none; 142 | } 143 | } 144 | 145 | .stats__total-posts { 146 | 147 | &:before { 148 | content: '\f100'; 149 | } 150 | } 151 | 152 | .stats__total-users { 153 | 154 | &:before { 155 | content: '\f304'; 156 | } 157 | } 158 | 159 | .stats__total-media { 160 | 161 | &:before { 162 | content: '\f473'; 163 | } 164 | } 165 | 166 | .stats__total-loc { 167 | 168 | &:before { 169 | content: '\f462'; 170 | } 171 | } -------------------------------------------------------------------------------- /components/stats/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ); 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | 11 | /** 12 | * Widget Component 13 | */ 14 | var Stats = React.createClass( { 15 | render: function() { 16 | return ( 17 | 18 | { this.props.children } 19 | 20 | ); 21 | } 22 | } ); 23 | 24 | module.exports = Stats; 25 | -------------------------------------------------------------------------------- /components/stats/style.scss: -------------------------------------------------------------------------------- 1 | .stats { 2 | height: $stats-height; 3 | color: $grey-blue; 4 | background: $graphite-var; 5 | } 6 | 7 | .stats__module { 8 | float: left; 9 | position: relative; 10 | display: inline-block; 11 | width: 33%; 12 | height: $stats-height; 13 | overflow: hidden; 14 | } 15 | 16 | .stats__canvas { 17 | width: 400px; 18 | height: 100px; 19 | } 20 | 21 | .stats__graphs { 22 | position: relative; 23 | /*width: 480px;*/ 24 | /*padding: 20px 10px;*/ 25 | } 26 | 27 | .stats__numbers { 28 | position: absolute; 29 | bottom: 0px; 30 | width: 100%; 31 | 32 | div { 33 | float: left; 34 | display: block; 35 | margin-right: 1em; 36 | padding: .5em 1em; 37 | 38 | &:last-of-type { 39 | margin: 0; 40 | } 41 | } 42 | 43 | &.numbers-data { 44 | bottom: 18%; 45 | padding-left: 2em; 46 | } 47 | } 48 | 49 | .stats__total-posts, 50 | .stats__total-users, 51 | .stats__total-media, 52 | .stats__total-loc { 53 | position: relative; 54 | min-width: 180px; 55 | 56 | &:before { 57 | display: inline-block; 58 | position: absolute; 59 | top: 14px; 60 | left: -24px; 61 | font-size: 32px; 62 | font-family: "Genericons"; 63 | font-style: normal; 64 | font-weight: normal; 65 | font-variant: normal; 66 | line-height: 1; 67 | vertical-align: top; 68 | text-align: center; 69 | text-decoration: inherit; 70 | text-transform: none; 71 | -moz-osx-font-smoothing: grayscale; 72 | -webkit-font-smoothing: antialiased; 73 | speak: none; 74 | } 75 | } 76 | 77 | .stats__total-posts { 78 | 79 | &:before { 80 | content: '\f100'; 81 | } 82 | } 83 | 84 | .stats__total-users { 85 | 86 | &:before { 87 | content: '\f304'; 88 | } 89 | } 90 | 91 | .stats__total-media { 92 | 93 | &:before { 94 | content: '\f473'; 95 | } 96 | } 97 | 98 | .stats__total-loc { 99 | 100 | &:before { 101 | content: '\f462'; 102 | } 103 | } -------------------------------------------------------------------------------- /components/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * VIP Dashboard SCSS 4 | * 5 | * This is an import of all component CSS that is bundled with each component. 6 | * Please keep these imports sorted and use specificity to ensure that 7 | * stylesheet order does not matter. 8 | * 9 | * The stylesheets are compiled automatically with gulp. 10 | * 11 | * CSS Coding Guidelines: https://wpcalypso.wordpress.com/devdocs/docs/coding-guidelines/css.md 12 | * 13 | */ 14 | 15 | // shared styles 16 | @import '_shared/_colors'; 17 | @import '_shared/_variables'; 18 | @import '_shared/_mixins'; 19 | @import '_shared/_genericons'; 20 | @import '_shared/_transitions'; 21 | 22 | // to be deprecated 23 | @import '_shared/_wordpress'; 24 | 25 | // @import '_shared/item'; 26 | // @import '_shared/item'; 27 | // @import '_shared/item'; 28 | 29 | // charts 30 | @import 'stats/style'; 31 | @import 'stats-charts/style'; 32 | @import 'stats-numbers/style'; 33 | 34 | // forms 35 | 36 | // header 37 | @import 'header/style'; 38 | @import 'nav/style'; 39 | 40 | // widgets 41 | @import 'widget/style'; 42 | @import 'widget-contact/style'; 43 | @import 'widget-welcome/style'; 44 | @import 'widget-promo/style'; 45 | -------------------------------------------------------------------------------- /components/vip-dashboard.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | ReactDOM = require( 'react-dom' ), 6 | // debug = require( 'debug' )( 'vip-dashboard' ), 7 | Chart = require( 'chart.js' ); 8 | // LineChart = require( 'react-chartjs' ).Line; 9 | 10 | /** 11 | * Internal dependencies 12 | */ 13 | var Main = require( './main' ), 14 | Header = require( './header' ), 15 | // Stats = require( './stats' ), 16 | // Stats_Charts = require( './stats-charts' ), 17 | // Stats_Numbers = require( './stats-numbers' ), 18 | Widget_Contact = require( './widget-contact' ), 19 | Widget_Welcome = require( './widget-welcome' ); 20 | // Widget_Editorial = require( './widget-editorial' ), 21 | // Widget_Promo = require( './widget-promo' ); 22 | 23 | /** 24 | * Settings 25 | */ 26 | Chart.defaults.global.responsive = true; 27 | 28 | var VIPdashboard = React.createClass( { 29 | getInitialState: function() { 30 | return { 31 | lineChartData: { 32 | labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 33 | datasets: [ 34 | { 35 | label: 'Posts', 36 | fillColor: 'rgba(45,173,227,0.2)', 37 | strokeColor: 'rgba(45,173,227,1)', 38 | pointColor: 'rgba(220,220,220,1)', 39 | pointStrokeColor: '#fff', 40 | pointHighlightFill: '#fff', 41 | pointHighlightStroke: 'rgba(220,220,220,1)', 42 | data: [10, 35, 28, 50, 20, 50, 42] 43 | }, 44 | { 45 | label: 'Comments', 46 | fillColor: 'rgba(245,169,28,0.2)', 47 | strokeColor: 'rgba(245,169,28,1)', 48 | pointColor: 'rgba(151,187,205,1)', 49 | pointStrokeColor: '#fff', 50 | pointHighlightFill: '#fff', 51 | pointHighlightStroke: 'rgba(151,187,205,1)', 52 | data: [16, 25, 22, 38, 46, 24, 50] 53 | } 54 | ] 55 | } 56 | }; 57 | }, 58 | render: function() { 59 | return ( 60 | 61 | 62 | 63 | 64 | {/** disabled for first version 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | **/} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | {/* 94 | 95 | 96 | 97 | */} 98 | 99 | 100 | 101 | ); 102 | } 103 | } ); 104 | 105 | ReactDOM.render( , document.getElementById( 'app' ) ); 106 | -------------------------------------------------------------------------------- /components/widget-contact/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | ReactDOM = require( 'react-dom' ), 6 | joinClasses = require( 'fbjs/lib/joinClasses' ); 7 | 8 | /** 9 | * Internal dependencies 10 | */ 11 | var Config = require( '../config.js' ), 12 | Widget = require( '../widget' ); 13 | 14 | /** 15 | * Contact Widget Component 16 | */ 17 | var Widget_Contact = React.createClass( { 18 | getInitialState: function() { 19 | return { 20 | user: Config.user, 21 | useremail: Config.useremail, 22 | message: '', 23 | status: '', 24 | formclass: '', 25 | cansubmit: true, 26 | cc: '' 27 | }; 28 | }, 29 | 30 | handleSubmit: function( e ) { 31 | e.preventDefault(); 32 | 33 | this.setState( { 34 | formclass: 'sending', 35 | cansubmit: false 36 | } ); 37 | 38 | var name = ReactDOM.findDOMNode( this.refs.user ).value.trim(); 39 | var email = ReactDOM.findDOMNode( this.refs.email ).value.trim(); 40 | var subject = ReactDOM.findDOMNode( this.refs.subject ).value.trim(); 41 | var type = ReactDOM.findDOMNode( this.refs.type ).value.trim(); 42 | var body = ReactDOM.findDOMNode( this.refs.body ).value.trim(); 43 | var priority = ReactDOM.findDOMNode( this.refs.priority ).value.trim(); 44 | var cc = ReactDOM.findDOMNode( this.refs.cc ).value.trim(); 45 | 46 | var data = { 47 | name: name, 48 | email: email, 49 | subject: subject, 50 | type: type, 51 | body: body, 52 | priority: priority, 53 | cc: cc, 54 | action: 'vip_contact' 55 | }; 56 | 57 | jQuery.ajax( { 58 | type: 'POST', 59 | url: Config.ajaxurl, 60 | data: data, 61 | success: function( data, textStatus, jqXHR ) { 62 | if ( textStatus === 'success' ) { 63 | var result = jQuery.parseJSON( data ); 64 | 65 | this.setState( { 66 | message: result.message, 67 | status: result.status, 68 | formclass: 'form-' + result.status, 69 | cansubmit: true 70 | } ); 71 | 72 | // reset the form 73 | if ( result.status === 'success' ) { 74 | ReactDOM.findDOMNode( this.refs.subject ).value = ''; 75 | ReactDOM.findDOMNode( this.refs.body ).value = ''; 76 | ReactDOM.findDOMNode( this.refs.cc ).value = ''; 77 | ReactDOM.findDOMNode( this.refs.type ).value = 'Technical'; 78 | ReactDOM.findDOMNode( this.refs.priority ).value = 'Medium'; 79 | } 80 | } else { 81 | this.setState( { 82 | message: 'Your message could not be sent, please try again.', 83 | status: 'error', 84 | cansubmit: true 85 | } ); 86 | } 87 | }.bind( this ) 88 | } ); 89 | 90 | return; 91 | }, 92 | 93 | maybeRenderFeedback: function() { 94 | if ( this.state.message ) { 95 | return ; 96 | } 97 | }, 98 | 99 | render: function() { 100 | return ( 101 | 102 | 103 | { this.maybeRenderFeedback() } 104 | 105 | 106 | 107 | 108 | Name 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | Email 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Subject 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | Type 133 | 134 | 135 | 136 | Technical 137 | Business/Project Management 138 | Theme/Plugin Review 139 | 140 | 141 | 142 | 143 | 144 | Details 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | Priority 153 | 154 | 155 | 156 | 157 | Low 158 | Normal 159 | High 160 | 161 | 162 | Emergency (Outage, Security, Revert, etc...) 163 | 164 | 165 | 166 | 167 | 168 | 169 | CC: 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | ); 186 | } 187 | } ); 188 | 189 | module.exports = Widget_Contact; 190 | -------------------------------------------------------------------------------- /components/widget-contact/style.scss: -------------------------------------------------------------------------------- 1 | /* Widget Contact Form */ 2 | .widget__contact-form { 3 | font-weight: 600; 4 | color: $vip-grey; 5 | 6 | &.sending { 7 | animation-duration: .5s; 8 | animation-name: sending; 9 | animation-iteration-count: infinite; 10 | animation-direction: alternate; 11 | } 12 | 13 | &.form-error { 14 | animation-duration: 8s; 15 | animation-name: error; 16 | animation-iteration-count: 1; 17 | } 18 | 19 | &.form-success { 20 | animation-duration: 8s; 21 | animation-name: success; 22 | animation-iteration-count: 1; 23 | } 24 | 25 | label { 26 | display: inline-block; 27 | padding: 1em 0; 28 | font-size: 12px; 29 | color: $vip-grey; 30 | vertical-align: top; 31 | } 32 | 33 | input, 34 | textarea, 35 | select, 36 | label { 37 | box-sizing: border-box; 38 | } 39 | 40 | input, 41 | textarea, 42 | select { 43 | width: 100%; 44 | margin: 0; 45 | padding: 10px 12px; 46 | font-size: 13px; 47 | line-height: 1.5em; 48 | background: $vip-grey-3; 49 | border: 1px solid $vip-grey-2; 50 | font-weight: normal; 51 | 52 | &:focus { 53 | border-color: $vip-gold; 54 | box-shadow: 0 0 4px 2px rgba($vip-gold, .3); 55 | } 56 | } 57 | 58 | textarea { 59 | resize: vertical; 60 | } 61 | 62 | select { 63 | cursor: pointer; 64 | height: auto; 65 | background: $vip-grey-3 url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDIwIDIwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I0Q4RTJFOTsiIGQ9Ik0xMy41IDcuMjVsMSAxbC00LjUgNC41bC00LjUtNC41bDEtMWwzLjUgMy41TDEzLjUgNy4yNXoiLz48L3N2Zz4=) no-repeat center right 10px; 66 | -webkit-appearance: none; 67 | -moz-appearance: none; 68 | appearance: none; 69 | font-weight: normal; 70 | 71 | &:hover { 72 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDIwIDIwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6Izk4QUVCRDsiIGQ9Ik0xMy41IDcuMjVsMSAxbC00LjUgNC41bC00LjUtNC41bDEtMWwzLjUgMy41TDEzLjUgNy4yNXoiLz48L3N2Zz4=); 73 | } 74 | 75 | &:focus { 76 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDIwIDIwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6IzQwNDA0MDsiIGQ9Ik0xMy41IDcuMjVsMSAxbC00LjUgNC41bC00LjUtNC41bDEtMWwzLjUgMy41TDEzLjUgNy4yNXoiLz48L3N2Zz4=); 77 | } 78 | } 79 | 80 | input[type="submit"] { 81 | cursor: pointer; 82 | position: relative; 83 | width: auto; 84 | padding: 10px 20px; 85 | color: $vip-dark-grey; 86 | background: $vip-white; 87 | border: 2px solid $vip-gold; 88 | font-weight: bold; 89 | outline: none; 90 | 91 | &:hover { 92 | background: $vip-gold; 93 | } 94 | 95 | &:active { 96 | top: 1px; 97 | } 98 | 99 | &:disabled { 100 | background: $vip-white; 101 | color: $vip-grey-2; 102 | border-color: $vip-grey-2; 103 | } 104 | } 105 | 106 | ::-webkit-input-placeholder { 107 | color: $blue-light; 108 | } 109 | :-moz-placeholder { 110 | color: $blue-light; 111 | opacity: 1; 112 | } 113 | ::-moz-placeholder { 114 | color: $blue-light; 115 | opacity: 1; 116 | } 117 | :-ms-input-placeholder { 118 | color: $blue-light; 119 | } 120 | } 121 | 122 | // Override wp-admin's default styles 123 | @media screen and (max-width: 782px) { 124 | #wpbody .widget__contact-form select { 125 | height: auto; 126 | font-size: 13px; 127 | } 128 | } 129 | 130 | .contact-form__row { 131 | clear: both; 132 | 133 | &.submit-button { 134 | padding-top: 12px; 135 | } 136 | } 137 | 138 | .contact-form__label, 139 | .contact-form__input { 140 | float: left; 141 | display: block; 142 | margin-bottom: 8px; 143 | } 144 | 145 | .contact-form__label { 146 | width: 30%; 147 | } 148 | 149 | .contact-form__input { 150 | width: 69.9%; 151 | } 152 | 153 | .contact-form__error, 154 | .contact-form__success { 155 | padding: 10px; 156 | margin-bottom: 1.3em; 157 | font-weight: normal; 158 | border: 1px solid $vip-grey-2; 159 | color: $vip-grey; 160 | background: $white; 161 | } 162 | 163 | .contact-form__error { 164 | border-left: 3px solid $alert-red; 165 | } 166 | 167 | .contact-form__success { 168 | border-left: 3px solid $alert-green; 169 | } 170 | 171 | @keyframes sending { 172 | from { 173 | border-color: $vip-grey-2; 174 | } 175 | to { 176 | border-color: $vip-blue; 177 | } 178 | } 179 | 180 | @keyframes error { 181 | from { 182 | border-color: $alert-red; 183 | background: lighten($alert-red, 41%); 184 | } 185 | to { 186 | border-color: $blue-border; 187 | background: $white; 188 | } 189 | } 190 | 191 | @keyframes success { 192 | from { 193 | border-color: $alert-green; 194 | background: lighten($alert-green, 47%); 195 | } 196 | to { 197 | border-color: $blue-border; 198 | background: $white; 199 | } 200 | } -------------------------------------------------------------------------------- /components/widget-editorial/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ); 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | var Widget = require( '../widget' ); 10 | 11 | /** 12 | * Editorial Widget Component 13 | */ 14 | var Widget_Editorial = React.createClass( { 15 | render: function() { 16 | return ( 17 | 18 | 19 | Placeholder 20 | 21 | 22 | 23 | ); 24 | } 25 | } ); 26 | 27 | module.exports = Widget_Editorial; 28 | -------------------------------------------------------------------------------- /components/widget-editorial/style.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/components/widget-editorial/style.scss -------------------------------------------------------------------------------- /components/widget-promo/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ); 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | var Config = require( '../config.js' ), 10 | Widget = require( '../widget' ); 11 | 12 | /** 13 | * Promo Widget Component 14 | */ 15 | var Widget_Promo = React.createClass( { 16 | render: function() { 17 | return ( 18 | 19 | 20 | 21 | 22 | WordPress.com VIP Training Days 23 | 24 | 25 | 26 | ); 27 | } 28 | } ); 29 | 30 | module.exports = Widget_Promo; 31 | -------------------------------------------------------------------------------- /components/widget-promo/style.scss: -------------------------------------------------------------------------------- 1 | .widget__promo { 2 | display: table; 3 | overflow: hidden; 4 | background: $graphite url(data:image/jpeg;base64,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) no-repeat 50% 50%; 5 | background-size: cover; 6 | 7 | a:hover { 8 | text-decoration: none; 9 | } 10 | 11 | .widget__content { 12 | display: table-cell; 13 | vertical-align: middle; 14 | } 15 | 16 | .promo-logo { 17 | display: block; 18 | width: 50px; 19 | height: auto; 20 | margin: 0 auto 12px; 21 | } 22 | 23 | .promo-text { 24 | margin: 0; 25 | text-align: center; 26 | font-size: 26px; 27 | font-weight: 300; 28 | line-height: 1.2em; 29 | color: $white; 30 | } 31 | } -------------------------------------------------------------------------------- /components/widget-welcome/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ); 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | var Widget = require( '../widget' ); 10 | 11 | /** 12 | * Welcome Widget Component 13 | */ 14 | var Widget_Welcome = React.createClass( { 15 | render: function() { 16 | return ( 17 | 18 | WordPress.com VIP is a partnership between WordPress.com and the most high-profile, innovative and smart WordPress websites out there. We’re excited to have you here. 19 | 20 | Helpful Links 21 | 22 | 23 | 24 | 25 | VIP Lobby 26 | Important service updates 27 | 28 | 29 | VIP Documentation 30 | Launching and developing with VIP 31 | 32 | 33 | VIP Support Portal 34 | Your organization’s tickets 35 | 36 | 37 | Ticket guidelines 38 | How to open the perfect ticket 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Guidebook: Launching with VIP 47 | Steps to launch 48 | 49 | 50 | Guidebook: Developing with VIP 51 | An overview of VIP development 52 | 53 | 54 | VIP News 55 | New features, case studies 56 | 57 | 58 | Featured Partners 59 | Agencies and technology partners 60 | 61 | 62 | 63 | 64 | ); 65 | } 66 | } ); 67 | 68 | module.exports = Widget_Welcome; 69 | -------------------------------------------------------------------------------- /components/widget-welcome/style.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-wp-admin-dashboard/3807e0e4a8c5135437d91b9ced6d3bfeb1a09e81/components/widget-welcome/style.scss -------------------------------------------------------------------------------- /components/widget/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | var React = require( 'react' ), 5 | joinClasses = require( 'fbjs/lib/joinClasses' ); 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | 11 | /** 12 | * Widget Component 13 | */ 14 | var Widget = React.createClass( { 15 | maybeRenderTitle: function() { 16 | if ( this.props.title ) { 17 | return {this.props.title}; 18 | } 19 | }, 20 | render: function() { 21 | return ( 22 | 23 | { this.maybeRenderTitle() } 24 | { this.props.children } 25 | 26 | ); 27 | } 28 | } ); 29 | 30 | module.exports = Widget; 31 | -------------------------------------------------------------------------------- /components/widget/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Widgets 3 | */ 4 | 5 | .widgets-area { 6 | padding: 1%; 7 | max-width: 1250px; 8 | margin: 0 auto; 9 | 10 | @media screen and (max-width: 600px) { 11 | padding: 2%; 12 | } 13 | } 14 | 15 | .widget { 16 | display: block; 17 | float: left; 18 | width: 48%; 19 | margin: 1%; 20 | padding: 2em; 21 | background: $vip-white; 22 | border: 1px solid $vip-grey-2; 23 | 24 | @media screen and (max-width: 600px) { 25 | width: 100%; 26 | margin: 0 0 2%; 27 | } 28 | 29 | a { 30 | color: $vip-gold; 31 | text-decoration: underline; 32 | 33 | &:hover { 34 | text-decoration: underline; 35 | color: $vip-grey; 36 | } 37 | } 38 | 39 | p { 40 | color: $vip-grey; 41 | } 42 | 43 | &.widget-small { 44 | width: 22%; 45 | min-height: 200px; 46 | padding: 0; 47 | 48 | .widget__content { 49 | padding: 0 2em; 50 | } 51 | 52 | @media screen and (max-width: 768px) { 53 | width: 48%; 54 | } 55 | 56 | @media screen and (max-width: 600px) { 57 | width: 100%; 58 | margin: 0 0 2%; 59 | } 60 | } 61 | } 62 | 63 | .widget__title, 64 | .widget__subtitle { 65 | margin-top: 0; 66 | font-weight: 600; 67 | } 68 | 69 | .widget__subtitle { 70 | margin-top: 3em; 71 | font-size: 1.2em; 72 | } 73 | 74 | .widget__col-2 { 75 | float: left; 76 | width: 45%; 77 | margin-right: 10%; 78 | 79 | @media screen and (max-width: 768px) { 80 | float: none; 81 | width: 100%; 82 | margin: 0; 83 | } 84 | 85 | &:last-of-type { 86 | margin: 0; 87 | } 88 | } 89 | 90 | /* Widget List */ 91 | .widget__list { 92 | margin: 0; 93 | padding: 0; 94 | list-style: none; 95 | 96 | li { 97 | margin-bottom: 1.5em; 98 | } 99 | 100 | a, 101 | span { 102 | display: block; 103 | } 104 | 105 | a { 106 | margin-bottom: .2em; 107 | font-size: 1.1em; 108 | } 109 | 110 | span { 111 | font-size: .9em; 112 | color: $vip-grey-1; 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Settings 3 | * 4 | * Setup your project paths and requirements here 5 | */ 6 | var settings = { 7 | 8 | // react 9 | componentpath: ['components/**/*.jsx', 'components/**/*.js'], 10 | js: './components/vip-dashboard.jsx', 11 | jspath: 'assets/js/', 12 | 13 | // path to main scss file 14 | scss: 'components/style.scss', 15 | 16 | // path to output css file 17 | css: 'assets/css/style.css', 18 | 19 | // path to watch for changed scss files 20 | scsswatch: 'components/**/*.scss', 21 | 22 | // path to output css folder 23 | csspath: 'assets/css/', 24 | 25 | // path to images 26 | imagespath: 'src/img/', 27 | imagesdistpath: 'assets/img/', 28 | 29 | // path to base 30 | basepath: './', 31 | 32 | // path to html 33 | htmlpath: ['./*.html', './*.php'], 34 | 35 | // enable the static file server and browsersync 36 | // check for unused styles in static html? - seems buggy, requires html 37 | staticserver: false, 38 | checkunusedcss: false, 39 | 40 | // enable the proxied local server for browsersync 41 | // static above server must be disabled 42 | proxyserver: true, 43 | proxylocation: 'vip.w.dev' 44 | 45 | }; 46 | 47 | /** 48 | * Load node modules 49 | */ 50 | var gulp = require( 'gulp' ), 51 | 52 | // Plugins 53 | assign = require( 'lodash.assign' ), 54 | autoprefixer = require( 'gulp-autoprefixer' ), 55 | browserify = require( 'browserify' ), 56 | browsersync = require( 'browser-sync' ), 57 | buffer = require( 'vinyl-buffer' ), 58 | checkcss = require( 'gulp-check-unused-css' ), 59 | concat = require( 'gulp-concat' ), 60 | csscomb = require( 'gulp-csscomb' ), 61 | eslint = require( 'gulp-eslint' ), 62 | filter = require( 'gulp-filter' ), 63 | imagemin = require( 'gulp-imagemin' ), 64 | install = require( 'gulp-install' ), 65 | minifycss = require( 'gulp-minify-css' ), 66 | parker = require( 'gulp-parker' ), 67 | plumber = require( 'gulp-plumber' ), 68 | react = require( 'gulp-react' ), 69 | sass = require( 'gulp-sass' ), 70 | source = require( 'vinyl-source-stream' ), 71 | reactify = require( 'reactify' ), 72 | sourcemaps = require( 'gulp-sourcemaps' ), 73 | sync = require( 'gulp-config-sync' ), 74 | uglify = require( 'gulp-uglify' ), 75 | util = require( 'gulp-util' ), 76 | watch = require( 'gulp-watch' ), 77 | watchify = require( 'watchify' ); 78 | 79 | /** 80 | * Generic error handler used by plumber 81 | * 82 | * Display an OS notification and sound with error message 83 | */ 84 | var onError = function( err ) { 85 | if ( err.lineNumber ) { 86 | util.log( util.colors.red( 'Error: (Line: ' + err.lineNumber + ') ' + err.message ) ); 87 | } else { 88 | util.log( util.colors.red( 'Error: ' + err.message ) ); 89 | } 90 | this.emit( 'end' ); 91 | }; 92 | 93 | /** 94 | * Default Task 95 | * 96 | * Watch for changes and run tasks 97 | */ 98 | gulp.task( 'default', function() { 99 | // Install 100 | gulp.start( 'install' ); 101 | 102 | // Compile Styles on start 103 | gulp.start( 'styles' ); 104 | 105 | // Process Images on start 106 | gulp.start( 'images' ); 107 | 108 | // Process react on start 109 | gulp.start( 'react' ); 110 | 111 | // Browsersync and local server 112 | // Options: http://www.browsersync.io/docs/options/ 113 | if ( settings.staticserver ) { 114 | browsersync( { 115 | server: settings.basepath 116 | } ); 117 | 118 | // Check to see if the CSS is being used 119 | if ( settings.checkunusedcss ) { 120 | gulp.watch( settings.css, ['checkcss'] ); 121 | } 122 | } 123 | 124 | if ( settings.proxyserver ) { 125 | browsersync( { 126 | proxy: settings.proxylocation 127 | } ); 128 | } 129 | 130 | // Watch for SCSS changes 131 | gulp.watch( settings.scsswatch, ['styles'] ); 132 | 133 | // Watch for image changes 134 | gulp.watch( settings.imagespath, ['images'] ); 135 | 136 | // Watch for HTML changes 137 | gulp.watch( settings.htmlpath, ['markup'] ); 138 | 139 | // Watch for react components 140 | gulp.watch( settings.componentpath, ['react'] ); 141 | } ); 142 | 143 | /** 144 | * Install Task 145 | * Ensure our packages are upto date 146 | */ 147 | gulp.task( 'install', function() { 148 | gulp.src( ['./package.json'] ) 149 | .pipe( install() ); 150 | } ); 151 | 152 | /** 153 | * Stylesheet Task 154 | * 155 | * SCSS -> CSS 156 | * Autoprefix 157 | * CSSComb 158 | * Sourcemaps 159 | * Minify 160 | * Report 161 | */ 162 | gulp.task( 'styles', function() { 163 | return gulp.src( settings.scss ) 164 | .pipe( plumber( {errorHandler: onError} ) ) 165 | .pipe( sass( { 166 | style: 'expanded', 167 | errLogToConsole: false 168 | } ) ) 169 | .pipe( sourcemaps.init() ) 170 | .pipe( autoprefixer( 'last 2 versions', 'ie 8', 'ie 9' ) ) 171 | .pipe( csscomb() ) 172 | .pipe( sourcemaps.write( './' ) ) 173 | .pipe( minifycss() ) 174 | .pipe( gulp.dest( settings.csspath ) ) 175 | .pipe( filter( '**/*.css' ) ) 176 | .pipe( browsersync.reload( {stream: true} ) ) 177 | .pipe( parker() ); 178 | } ); 179 | 180 | /** 181 | * React Tast 182 | * 183 | * Compile JSX etc 184 | */ 185 | var reactopts = { 186 | entries: [settings.js], 187 | debug: true, 188 | extensions: ['.jsx'] 189 | }; 190 | var opts = assign( {}, watchify.args, reactopts ); 191 | var b = watchify( browserify( opts ) ); 192 | b.transform( reactify ); 193 | //b.on('log', util.log); // output build logs to terminal 194 | 195 | gulp.task( 'react', ['lint', 'set-node-env'], function() { 196 | return b.bundle() 197 | .on( 'error', onError ) 198 | .pipe( source( 'vip-dashboard.js' ) ) 199 | .pipe( buffer() ) 200 | .pipe( sourcemaps.init( {loadMaps: true} ) ) 201 | .pipe( uglify() ) 202 | .pipe( sourcemaps.write( './' ) ) 203 | .pipe( gulp.dest( settings.jspath ) ) 204 | .pipe( browsersync.reload( {stream: true} ) ); 205 | } ); 206 | 207 | /** 208 | * Set env variable for production 209 | */ 210 | gulp.task( 'set-node-env', function() { 211 | return process.env.NODE_ENV = 'production'; 212 | } ); 213 | 214 | /** 215 | * Compress the JS 216 | */ 217 | gulp.task( 'compress', function() { 218 | return gulp.src( settings.jspath + 'vip-dashboard.js' ) 219 | .pipe( uglify() ) 220 | .pipe( gulp.dest( settings.jspath ) ); 221 | } ); 222 | 223 | /** 224 | * Lint Task 225 | * 226 | * Run before react task above to check for errors 227 | */ 228 | gulp.task( 'lint', function() { 229 | return gulp.src( settings.componentpath ) 230 | .pipe( eslint( { 231 | baseConfig: { 232 | ecmaFeatures: { 233 | jsx: true 234 | } 235 | } 236 | } ) ) 237 | .pipe( eslint.format( ) ) 238 | .pipe( eslint.failAfterError( ) ) 239 | .on( 'error', onError ); 240 | } ); 241 | 242 | /** 243 | * Images Task 244 | * 245 | * Run independantly when you want to optimise image assets 246 | */ 247 | gulp.task( 'images', function() { 248 | return gulp.src( settings.imagespath + '**/*.{gif,jpg,png}' ) 249 | .pipe( plumber( {errorHandler: onError} ) ) 250 | .pipe( imagemin( { 251 | progressive: true, 252 | interlaced: true, 253 | //svgoPlugins: [ {removeViewBox:false}, {removeUselessStrokeAndFill:false} ] 254 | } ) ) 255 | .pipe( gulp.dest( settings.imagesdistpath ) ) 256 | .pipe( browsersync.reload( {stream: true} ) ); 257 | } ); 258 | 259 | /** 260 | * CheckCSS Task 261 | * 262 | * Are all our styles being used correctly? 263 | */ 264 | gulp.task( 'checkcss', function() { 265 | return gulp.src( [ settings.css, settings.staticlocation + '*.html' ] ) 266 | .pipe( plumber( {errorHandler: onError} ) ) 267 | .pipe( checkcss() ); 268 | } ); 269 | 270 | /** 271 | * Reload HTML files 272 | * 273 | * If modified, refreshes HTML files 274 | */ 275 | gulp.task( 'markup', function() { 276 | return gulp.src( settings.htmlpath ) 277 | .pipe( browsersync.reload( {stream: true} ) ); 278 | } ); 279 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@automattic/vip-dashboard", 3 | "version": "2.0.4", 4 | "homepage": "http://vip.wordpress.com", 5 | "description": "WordPress.com VIP Go Dashboard", 6 | "license": "GPL-2.0+", 7 | "private": true, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/Automattic/vip-go-admin-console.git" 11 | }, 12 | "scripts": { 13 | "test": "NODE_ENV=test npm run-script lint", 14 | "lint": "node_modules/.bin/eslint components" 15 | }, 16 | "engines": { 17 | "node": ">= 0.10.0" 18 | }, 19 | "devDependencies": { 20 | "babel-eslint": "^7.0.0", 21 | "browser-sync": "^2.17.2", 22 | "browserify": "^13.1.0", 23 | "eslint": "^3.7.1", 24 | "eslint-plugin-react": "^6.4.1", 25 | "gulp": "^3.9.1", 26 | "gulp-autoprefixer": "^3.1.1", 27 | "gulp-check-unused-css": "^2.1.3", 28 | "gulp-concat": "^2.6.0", 29 | "gulp-config-sync": "^1.0.2", 30 | "gulp-csscomb": "^3.0.8", 31 | "gulp-eslint": "^3.0.1", 32 | "gulp-filter": "^4.0.0", 33 | "gulp-imagemin": "^4.1.0", 34 | "gulp-install": "^0.6.0", 35 | "gulp-minify-css": "^1.2.4", 36 | "gulp-parker": "^0.1.4", 37 | "gulp-plumber": "^1.1.0", 38 | "gulp-react": "^3.1.0", 39 | "gulp-sass": "^2.3.2", 40 | "gulp-sourcemaps": "^2.0.0", 41 | "gulp-uglify": "1.5.4", 42 | "gulp-util": "^3.0.7", 43 | "gulp-watch": "^4.3.10", 44 | "lodash.assign": "^4.2.0", 45 | "reactify": "^1.1.1", 46 | "should": "11.1.1", 47 | "should-http": "0.0.4", 48 | "supertest": "^3.1.0", 49 | "vinyl-buffer": "^1.0.0", 50 | "vinyl-source-stream": "^1.1.0", 51 | "watchify": "^3.7.0" 52 | }, 53 | "dependencies": { 54 | "chart.js": "^1.1.1", 55 | "classnames": "^2.2.5", 56 | "debug": "^2.2.0", 57 | "fbjs": "^0.8.5", 58 | "lodash": "^4.17.19", 59 | "react": "^15.3.2", 60 | "react-chartjs": "^0.8.0", 61 | "react-dom": "^15.3.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # VIP Dashboard 2 | 3 | ## This repository has been archived and its code now lives in the `vip-dashboard` folder within [vip-go-mu-plugins](https://github.com/Automattic/vip-go-mu-plugins). 4 | 5 | WordPress plugin that provides a new dashboard for VIP Go clients. 6 | 7 | The interface is built with [React.js](https://facebook.github.io/react/). 8 | 9 | ## Getting Started 10 | 11 | ### Prerequisites 12 | 13 | Make sure you have [Node.js](https://nodejs.org/) and [NPM](https://docs.npmjs.com/getting-started/what-is-npm) installed. Here's a [handy installer](https://nodejs.org/download/) for Windows, Mac, and Linux. 14 | 15 | The repository is a sub-module of the [mu-plugins](https://github.com/Automattic/vip-go-mu-plugins) directory. 16 | 17 | ### Gulp 18 | 19 | [Gulp](http://gulpjs.com/) is required to work on this repository. We use Gulp to compile JSX into valid JavaScript and manage other assets such as CSS and images. 20 | 21 | To get setup run the following command in the `vip-dashboard` directory: 22 | 23 | ``` 24 | npm install 25 | ``` 26 | 27 | Once node has completed the install you should set the URL to your local development site in `gulpfile.js`. Line 50: 28 | 29 | ``` 30 | proxylocation: 'vip.w.dev' 31 | ``` 32 | 33 | You can then run the default gulp task by running: 34 | 35 | ``` 36 | gulp 37 | ``` 38 | 39 | The default task watches for changes to files and re-compiles assets when a change is detected. Your browser window will also automatically be refreshed with each change. We also check for JS errors so keep an eye on your console and fix any reported issues. 40 | 41 | Before deploying you may wish to run: 42 | 43 | ``` 44 | gulp compress 45 | ``` 46 | 47 | This will generate minified versions of the JavaScript ready for production. 48 | 49 | ## Testing 50 | 51 | Run 52 | 53 | ``` 54 | make lint 55 | ``` 56 | 57 | To test your JavaScript for errors. 58 | 59 | ## Directory Structure 60 | 61 | ``` 62 | ├── readme.md 63 | ├── gulpfile.js 64 | ├── package.json 65 | ├── Makefile 66 | ├── vip-dashboard.php 67 | ├── .travis.yml 68 | ├── assets 69 | │ └── css 70 | │ └── img 71 | │ └── js 72 | ├── components 73 | │ └── ... react components 74 | 75 | ``` 76 | 77 | ### assets 78 | 79 | Compiled assets, do not edit anything here. 80 | 81 | ### components 82 | 83 | Where each react component lives with the relevent JSX and SCSS files. 84 | 85 | ## Git Workflow 86 | 87 | * The Master branch is production code (i.e. completely deployable by the time it gets merged) 88 | * All branches except Master and Develop get prefixed with something/ 89 | * New features get a add/ prefix 90 | * Fixes get a fix/ prefix, and have an issue number: e.g. fix/999-fix-fatal-errors where issue 999 describes the bug being fixed 91 | * All branches get deleted once merged 92 | * No development takes place on Master or Develop (if Develop exists) 93 | * Nobody should merge code they’ve written, instead create a Pull Request and ask another colleague to merge it 94 | * Pull Requests should not be monstrous quantities of code, or they’ll be too daunting to review 95 | -------------------------------------------------------------------------------- /src/img/vip-workshop-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/wpcom-vip-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /vip-dashboard.php: -------------------------------------------------------------------------------- 1 | display_name; 62 | $email = $current_user->user_email; 63 | $ajaxurl = add_query_arg( array( '_wpnonce' => wp_create_nonce( 'vip-dashboard' ) ), untrailingslashit( admin_url( 'admin-ajax.php' ) ) ); 64 | ?> 65 | 72 | 'error', 85 | 'message' => __( 'Please complete all required fields.', 'vip-dashboard' ), 86 | ); 87 | echo wp_json_encode( $return ); 88 | die(); 89 | } 90 | 91 | if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'vip-dashboard' ) ) { 92 | $return = array( 93 | 'status' => 'error', 94 | 'message' => __( 'Security check failed. Make sure you should be doing this, and try again.', 'vip-dashboard' ), 95 | ); 96 | echo wp_json_encode( $return ); 97 | die(); 98 | } 99 | 100 | $vipsupportemailaddy = 'vip-support@wordpress.com'; 101 | $cc_headers_to_kayako = ''; 102 | 103 | $sendemail = true; 104 | $emailsent = false; 105 | $current_user = wp_get_current_user(); 106 | 107 | $name = ( ! empty( $_POST['name'] ) ) ? strip_tags( stripslashes( $_POST['name'] ) ) : $current_user->display_name; 108 | $email = ( ! empty( $_POST['email'] ) ) ? strip_tags( stripslashes( $_POST['email'] ) ) : $current_user->user_email; 109 | 110 | if ( ! is_email( $email ) ) { 111 | $return = array( 112 | 'status' => 'error', 113 | 'message' => __( 'Please enter a valid email for your ticket.', 'vip-dashboard' ), 114 | ); 115 | echo wp_json_encode( $return ); 116 | die(); 117 | } 118 | 119 | $subject = ( ! empty( $_POST['subject'] ) ) ? strip_tags( stripslashes( $_POST['subject'] ) ) : ''; 120 | $group = ( ! empty( $_POST['type'] ) ) ? strip_tags( stripslashes( $_POST['type'] ) ) : 'Technical'; 121 | $priority = ( ! empty( $_POST['priority'] ) ) ? strip_tags( stripslashes( $_POST['priority'] ) ) : 'Medium'; 122 | 123 | $ccemail = ( ! empty( $_POST['cc'] ) ) ? strip_tags( stripslashes( $_POST['cc'] ) ) : ''; 124 | $temp_ccemails = explode( ',', $ccemail ); 125 | $temp_ccemails = array_filter( array_map( 'trim', $temp_ccemails ) ); 126 | $ccemails = array(); 127 | 128 | if ( ! empty( $temp_ccemails ) ) { 129 | foreach ( array_values( $temp_ccemails ) as $value ) { 130 | if ( is_email( $value ) ) { 131 | $ccemails[] = $value; 132 | } 133 | } 134 | } 135 | $ccemails = apply_filters( 'vip_contact_form_cc', $ccemails ); 136 | 137 | if ( count( $ccemails ) ) { 138 | $cc_headers_to_kayako .= 'CC: ' . implode( ',', $ccemails ) . "\r\n"; 139 | } 140 | 141 | if ( empty( $subject ) ) { 142 | $return = array( 143 | 'status' => 'error', 144 | 'message' => __( 'Please enter a descriptive subject for your ticket.', 'vip-dashboard' ), 145 | ); 146 | echo wp_json_encode( $return ); 147 | die(); 148 | } 149 | 150 | if ( '' === $_POST['body'] ) { 151 | $return = array( 152 | 'status' => 'error', 153 | 'message' => __( 'Please enter a detailed description of your issue.', 'vip-dashboard' ), 154 | ); 155 | echo wp_json_encode( $return ); 156 | die(); 157 | } 158 | 159 | if ( 'Emergency' === $priority ) { 160 | $subject = sprintf( '[%s] %s', $priority, $subject ); 161 | } 162 | $content = stripslashes( $_POST['body'] ) . "\n\n--- Ticket Details --- \n"; 163 | 164 | if ( $priority ) { 165 | $content .= "\nPriority: " . $priority; 166 | } 167 | $content .= "\nUser: " . $current_user->user_login . ' | ' . $current_user->display_name; 168 | 169 | // VIP DB. 170 | $theme = wp_get_theme(); 171 | $content .= "\nSite Name: " . get_bloginfo( 'name' ); 172 | $content .= "\nSite URLs: " . site_url() . ' | ' . admin_url(); 173 | $content .= "\nTheme: " . get_option( 'stylesheet' ) . ' | ' . $theme->get( 'Name' ); 174 | 175 | // added for VIPv2. 176 | $content .= "\nPlatform: VIP Go"; 177 | 178 | // send date and time. 179 | $content .= sprintf( "\n\nSent from %s on %s", home_url(), date( 'c', current_time( 'timestamp', 1 ) ) ); 180 | 181 | // Filter from name/email. NOTE - not un-hooking the filter because we die() immediately after wp_mail() 182 | add_filter( 'wp_mail_from', function() use ( $email ) { 183 | return $email; 184 | }); 185 | 186 | add_filter( 'wp_mail_from_name', function() use ( $name ) { 187 | return $name; 188 | }); 189 | 190 | $headers = "From: \"$name\" <$email>\r\n"; 191 | if ( wp_mail( $vipsupportemailaddy, $subject, $content, $headers . $cc_headers_to_kayako ) ) { 192 | $return = array( 193 | 'status' => 'success', 194 | 'message' => __( 'Your support request is on its way, we will be in touch soon.', 'vip-dashboard' ), 195 | ); 196 | 197 | echo wp_json_encode( $return ); 198 | die(); 199 | 200 | } else { 201 | $manual_link = vip_echo_mailto_vip_hosting( __( 'Please send in a request manually.', 'vip-dashboard' ), false ); 202 | $return = array( 203 | 'status' => 'error', 204 | 'message' => sprintf( __( 'There was an error sending the support request. %1$s', 'vip-dashboard' ), $manual_link ), 205 | ); 206 | 207 | echo wp_json_encode( $return ); 208 | die(); 209 | } 210 | 211 | die(); 212 | } 213 | add_action( 'wp_ajax_vip_contact', 'vip_contact_form_handler' ); 214 | 215 | /** 216 | * Generate a manual email link if the send fails 217 | * 218 | * @param string $linktext the text for the link. 219 | * @param bool $echo echo or return. 220 | * @return html 221 | */ 222 | function vip_echo_mailto_vip_hosting( $linktext = 'Send an email to VIP Hosting.', $echo = true ) { 223 | 224 | $current_user = get_currentuserinfo(); 225 | 226 | $name = ''; 227 | if ( isset( $_POST['name'] ) ) { 228 | $name = sanitize_text_field( $_POST['name'] ); 229 | } elseif ( isset( $current_user->display_name ) ) { 230 | $name = $current_user->display_name; 231 | } 232 | 233 | $useremail = ''; 234 | if ( isset( $_POST['email'] ) && is_email( $_POST['email'] ) ) { 235 | $useremail = sanitize_email( $_POST['email'] ); 236 | } elseif ( isset( $current_user->user_email ) ) { 237 | $name = $current_user->user_email; 238 | } 239 | 240 | $email = "\n\n--\n"; 241 | $email .= 'Name: ' . $name . "\n"; 242 | $email .= 'Email: ' . $useremail . "\n"; 243 | $email .= 'URL: ' . home_url() . "\n"; 244 | $email .= 'IP Address: ' . $_SERVER['REMOTE_ADDR'] . "\n"; 245 | $email .= 'Server: ' . php_uname( 'n' ) . "\n"; 246 | $email .= 'Browser: ' . $_SERVER['HTTP_USER_AGENT'] . "\n"; 247 | $email .= 'Platform: VIP Go'; 248 | 249 | $url = add_query_arg( array( 'subject' => __( 'Descriptive subject please', 'vip-dashboard' ), 'body' => rawurlencode( $email ) ), 'mailto:vip-support@wordpress.com' ); 250 | 251 | // $url not escaped on output as email formatting is borked by esc_url: 252 | // https://core.trac.wordpress.org/ticket/31632 253 | $html = '' . esc_html( $linktext ) . ''; 254 | 255 | if ( $echo ) { 256 | echo $html; 257 | } 258 | 259 | return $html; 260 | } 261 | 262 | /** 263 | * Create admin menu, enqueue scripts etc 264 | * 265 | * @return void 266 | */ 267 | function wpcom_vip_admin_menu() { 268 | /** 269 | * Limit access to the VIP Menu to users with this capability. 270 | * 271 | * @param string $vip_page_cap The cap to use; default is `publish_posts`. 272 | */ 273 | $vip_page_cap = apply_filters( 'vip_dashboard_page_cap', 'publish_posts' ); 274 | 275 | if ( ! current_user_can( $vip_page_cap ) ) { 276 | return; 277 | } 278 | 279 | $vip_page_slug = 'vip-dashboard'; 280 | 281 | $page = add_menu_page( __( 'VIP Dashboard' ), __( 'VIP' ), $vip_page_cap, $vip_page_slug, 'vip_dashboard_page', 'dashicons-tickets' ); 282 | 283 | add_action( 'admin_print_styles-' . $page, 'vip_dashboard_admin_styles' ); 284 | add_action( 'admin_print_scripts-' . $page, 'vip_dashboard_admin_scripts' ); 285 | 286 | add_filter( 'custom_menu_order', '__return_true' ); 287 | add_filter( 'menu_order', 'wpcom_vip_menu_order' ); 288 | } 289 | 290 | /** 291 | * Rename the first (auto-added) entry in the Dashboard. Kinda hacky, but the menu doesn't have any filters 292 | * 293 | * @return void 294 | */ 295 | function wpcom_vip_rename_vip_menu_to_dashboard() { 296 | global $submenu; 297 | 298 | if ( isset( $submenu['vip-dashboard'][0][0] ) ) { 299 | $submenu['vip-dashboard'][0][0] = __( 'Dashboard' ); 300 | } 301 | } 302 | 303 | /** 304 | * Set the menu order for the VIP Dashboard 305 | * 306 | * @param array $menu_ord order of menu. 307 | * @return array 308 | */ 309 | function wpcom_vip_menu_order( $menu_ord ) { 310 | 311 | if ( empty( $menu_ord ) ) { 312 | return false; 313 | } 314 | 315 | $vip_order = array(); 316 | $previous_item = false; 317 | 318 | $vip_dash = 'vip-dashboard'; 319 | $dash_menu = 'index.php'; 320 | 321 | foreach ( $menu_ord as $item ) { 322 | if ( $dash_menu === $previous_item ) { 323 | $vip_order[] = $vip_dash; 324 | $vip_order[] = $item; 325 | unset( $menu_ord[ $vip_dash ] ); 326 | } elseif ( $item !== $vip_dash ) { 327 | $vip_order[] = $item; 328 | } 329 | 330 | $previous_item = $item; 331 | } 332 | 333 | return $vip_order; 334 | } 335 | --------------------------------------------------------------------------------
15 | { this.props.children } 16 |
Placeholder
WordPress.com VIP is a partnership between WordPress.com and the most high-profile, innovative and smart WordPress websites out there. We’re excited to have you here.