diff --git a/.gitignore b/.gitignore index b3037c5b8d..ab0e6eaa4f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ coverage/ dist/ docs/src/componentInfo docs/src/componentMenu.json +docs/src/behaviorMenu.json docs/src/exampleMenus docs/dist/ dll/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f06670685d..ca9463111f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Features - Add `state` to `props` in component styling functions @Bugaa92 ([#173](https://github.com/stardust-ui/react/pull/173)) +- Adding 'behaviors' section to the menu, under the components @kolaps33 ([#119] (https://github.com/stardust-ui/react/pull/119) ## [v0.5.0](https://github.com/stardust-ui/react/tree/v0.5.0) (2018-08-30) diff --git a/build/gulp/plugins/gulp-component-menu-behaviors.ts b/build/gulp/plugins/gulp-component-menu-behaviors.ts new file mode 100644 index 0000000000..1f0ac7e40c --- /dev/null +++ b/build/gulp/plugins/gulp-component-menu-behaviors.ts @@ -0,0 +1,98 @@ +import * as gutil from 'gulp-util' +import * as path from 'path' +import * as through2 from 'through2' +import * as Vinyl from 'vinyl' +import * as _ from 'lodash' +import * as fs from 'fs' + +const pluginName = 'gulp-component-menu-behaviors' +const extract = require('extract-comments') +const doctrine = require('doctrine') + +type BehaviorMenuItem = { + displayName: string + type: string + variations: { + name: string + text: string + } +} + +export default () => { + const result: BehaviorMenuItem[] = [] + function bufferContents(file, enc, cb) { + if (file.isNull()) { + cb(null, file) + return + } + + if (file.isStream()) { + cb(new gutil.PluginError(pluginName, 'Streaming is not supported')) + return + } + + try { + const absPath = path.resolve(process.cwd(), file.path) + const dir = path.dirname(absPath) + const componentType = _.lowerFirst(path.basename(path.dirname(dir)).replace(/s$/, '')) + const behaviorVariantName = file.basename + const behaviorName = path.basename(dir) + + let description + const fileContent = fs.readFileSync(file.path).toString() + const blockComments = extract(fileContent).filter(comment => comment.type === 'BlockComment') // filtering only block comments + const emptyDescriptionText = 'Behavior file has no description.' + + // getting object that describes '@description' part of the comment's text + if (!_.isEmpty(blockComments)) { + const commentTokens = doctrine.parse(blockComments[0].raw, { unwrap: true }).tags + const descriptionToken = commentTokens.find(token => token.title === 'description') + description = descriptionToken ? descriptionToken.description : emptyDescriptionText + } else { + description = emptyDescriptionText + } + + result.push({ + displayName: behaviorName, + type: componentType, + variations: { + name: behaviorVariantName, + text: description, + }, + }) + cb() + } catch (err) { + const pluginError = new gutil.PluginError(pluginName, err) + const relativePath = path.relative(process.cwd(), file.path) + pluginError.message = [ + gutil.colors.magenta(`Error in file: ${relativePath}`), + gutil.colors.red(err.message), + gutil.colors.gray(err.stack), + ].join('\n\n') + this.emit('error', pluginError) + } + } + + function getParsedResults() { + return _(result) + .groupBy('displayName') + .map((behaviors, displayName) => ({ + displayName, + type: behaviors[0].type, + variations: _.map(behaviors, 'variations'), + })) + .value() + } + + function endStream(cb) { + const file = new Vinyl({ + path: './behaviorMenu.json', + contents: Buffer.from(JSON.stringify(getParsedResults(), null, 2)), + }) + + this.push(file) + cb() + } + + return through2.obj(bufferContents, endStream) +} diff --git a/build/gulp/tasks/docs.ts b/build/gulp/tasks/docs.ts index e495136167..e4284d6b4a 100644 --- a/build/gulp/tasks/docs.ts +++ b/build/gulp/tasks/docs.ts @@ -1,6 +1,7 @@ import * as historyApiFallback from 'connect-history-api-fallback' import * as express from 'express' import { task, src, dest, lastRun, parallel, series, watch } from 'gulp' +import * as remember from 'gulp-remember' import * as path from 'path' import * as rimraf from 'rimraf' import * as through2 from 'through2' @@ -11,6 +12,7 @@ import * as WebpackHotMiddleware from 'webpack-hot-middleware' import sh from '../sh' import config from '../../../config' import gulpComponentMenu from '../plugins/gulp-component-menu' +import gulpComponentMenuBehaviors from '../plugins/gulp-component-menu-behaviors' import gulpExampleMenu from '../plugins/gulp-example-menu' import gulpReactDocgen from '../plugins/gulp-react-docgen' @@ -18,7 +20,7 @@ const { paths } = config const g = require('gulp-load-plugins')() const { colors, log, PluginError } = g.util -const handleWatchChange = e => log(`File ${e.path} was ${e.type}, running tasks...`) +const handleWatchChange = (path, stats) => log(`File ${path} was changed, running tasks...`) // ---------------------------------------- // Clean @@ -32,6 +34,10 @@ task('clean:docs:component-menu', cb => { rimraf(paths.docsSrc('componentMenu.json'), cb) }) +task('clean:docs:component-menu-behaviors', cb => { + rimraf(paths.docsSrc('behaviorMenu.json'), cb) +}) + task('clean:docs:dist', cb => { rimraf(paths.docsDist(), cb) }) @@ -45,6 +51,7 @@ task( parallel( 'clean:docs:component-info', 'clean:docs:component-menu', + 'clean:docs:component-menu-behaviors', 'clean:docs:dist', 'clean:docs:example-menus', ), @@ -55,6 +62,7 @@ task( // ---------------------------------------- const componentsSrc = [`${config.paths.src()}/components/*/[A-Z]*.tsx`] +const behaviorSrc = [`${config.paths.src()}/lib/accessibility/Behaviors/*/[A-Z]*.ts`] const examplesSrc = `${paths.docsSrc()}/examples/*/*/*/index.tsx` const markdownSrc = ['.github/CONTRIBUTING.md', 'specifications/*.md'] @@ -70,6 +78,13 @@ task('build:docs:component-menu', () => .pipe(dest(paths.docsSrc())), ) +task('build:docs:component-menu-behaviors', () => + src(behaviorSrc, { since: lastRun('build:docs:component-menu-behaviors') }) + .pipe(remember('component-menu-behaviors')) + .pipe(gulpComponentMenuBehaviors()) + .pipe(dest(paths.docsSrc())), +) + task('build:docs:example-menu', () => src(examplesSrc, { since: lastRun('build:docs:example-menu') }) .pipe(gulpExampleMenu()) @@ -78,7 +93,12 @@ task('build:docs:example-menu', () => task( 'build:docs:json', - parallel('build:docs:docgen', 'build:docs:component-menu', 'build:docs:example-menu'), + parallel( + 'build:docs:docgen', + 'build:docs:component-menu', + 'build:docs:component-menu-behaviors', + 'build:docs:example-menu', + ), ) task('build:docs:html', () => src(paths.docsSrc('404.html')).pipe(dest(paths.docsDist()))) @@ -193,6 +213,13 @@ task('watch:docs', cb => { // rebuild example menus watch(examplesSrc, series('build:docs:example-menu')).on('change', handleWatchChange) + watch(behaviorSrc, series('build:docs:component-menu-behaviors')) + .on('change', handleWatchChange) + .on('unlink', path => { + log(`File ${path} was deleted, running tasks...`) + remember.forget('component-menu-behaviors', path) + }) + // rebuild images watch(`${config.paths.src()}/**/*.{png,jpg,gif}`, series('build:docs:images')).on( 'change', diff --git a/docs/src/components/DocsBehaviorRoot.tsx b/docs/src/components/DocsBehaviorRoot.tsx new file mode 100644 index 0000000000..badfaa049f --- /dev/null +++ b/docs/src/components/DocsBehaviorRoot.tsx @@ -0,0 +1,72 @@ +import * as _ from 'lodash' +import PropTypes from 'prop-types' +import * as React from 'react' +const behaviorMenuItems = require('docs/src/behaviorMenu') +import { Grid } from 'semantic-ui-react' +import ComponentExampleTitle from './ComponentDoc/ComponentExample/ComponentExampleTitle' +class DocsBehaviorRoot extends React.Component { + static propTypes = { + children: PropTypes.node, + match: PropTypes.shape({ + params: PropTypes.shape({ + name: PropTypes.string.isRequired, + }), + }), + } + + baseName(fileName: string) { + const divided = _.startCase(fileName.replace('ts', '')) + return _.upperFirst(_.lowerCase(divided)) + } + + render() { + const exampleStyle: React.CSSProperties = { + position: 'relative', + transition: 'box-shadow 200ms, background 200ms', + background: '#fff', + boxShadow: '0 1px 2px #ccc', + margin: '10px', + } + + const commentBox: React.CSSProperties = { + padding: 5, + } + const { match } = this.props + return ( +
+ {behaviorMenuItems + .find(behavior => behavior.displayName === _.capitalize(match.params.name)) + .variations.map((variation, keyValue) => ( + + +
+
+ +
+
+
+ +
+ Description: +
+ {variation.text.split('\n').map((splittedText, keyValue) => { + return ( + + {splittedText} +
+
+ ) + })} +
+ + ))} +
+ ) + } +} + +export default DocsBehaviorRoot diff --git a/docs/src/components/DocsRoot.tsx b/docs/src/components/DocsRoot.tsx index 2d56de9add..d62c53d1cd 100644 --- a/docs/src/components/DocsRoot.tsx +++ b/docs/src/components/DocsRoot.tsx @@ -5,6 +5,7 @@ import * as React from 'react' import ComponentDoc from '../components/ComponentDoc' import PageNotFound from '../views/PageNotFound' import componentInfoContext from '../utils/componentInfoContext' +import DocsBehaviorRoot from './DocsBehaviorRoot' class DocsRoot extends React.Component { static propTypes = { @@ -22,6 +23,9 @@ class DocsRoot extends React.Component { render() { const { match } = this.props const displayName = _.startCase(match.params.name).replace(/ /g, '') + if (match.params.type === 'behaviors') { + return + } const info = componentInfoContext.byDisplayName[displayName] if (info) return diff --git a/docs/src/components/Sidebar/Sidebar.tsx b/docs/src/components/Sidebar/Sidebar.tsx index 4efec3515b..29db06e4a7 100644 --- a/docs/src/components/Sidebar/Sidebar.tsx +++ b/docs/src/components/Sidebar/Sidebar.tsx @@ -12,10 +12,10 @@ import { getComponentPathname, typeOrder, repoURL } from 'docs/src/utils' const pkg = require('../../../../package.json') const componentMenu = require('docs/src/componentMenu') +const behaviorMenu = require('docs/src/behaviorMenu') const selectedItemLabelStyle: any = { color: '#35bdb2', float: 'right' } const selectedItemLabel = Press Enter - type ComponentMenuItem = { displayName: string; type: string } class Sidebar extends React.Component { @@ -111,7 +111,7 @@ class Sidebar extends React.Component { activeClassName="active" /> )), - )(componentMenu) + )([...componentMenu, ...behaviorMenu]) return ( diff --git a/docs/src/utils/constants.ts b/docs/src/utils/constants.ts index e248b7d2a3..4b69448b90 100644 --- a/docs/src/utils/constants.ts +++ b/docs/src/utils/constants.ts @@ -1,2 +1,2 @@ export const repoURL = 'https://github.com/stardust-ui/react' -export const typeOrder = ['component'] +export const typeOrder = ['component', 'behavior'] diff --git a/package.json b/package.json index ec81dd4d01..42e31ef2bb 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "enzyme": "^3.1.0", "enzyme-adapter-react-16": "^1.0.1", "express": "^4.15.4", + "extract-comments": "^1.0.0", "fbjs": "^0.8.17", "gh-pages": "^1.0.0", "glob": "^7.1.2", @@ -114,6 +115,7 @@ "gulp-debug": "^4.0.0", "gulp-load-plugins": "^1.5.0", "gulp-plumber": "^1.2.0", + "gulp-remember": "^1.0.1", "gulp-rename": "^1.3.0", "gulp-replace": "^1.0.0", "gulp-transform": "^3.0.5", diff --git a/yarn.lock b/yarn.lock index ef32f816b8..128928d4a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2190,6 +2190,12 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" +esprima-extract-comments@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/esprima-extract-comments/-/esprima-extract-comments-1.1.0.tgz#0dacab567a5900240de6d344cf18c33617becbc9" + dependencies: + esprima "^4.0.0" + esprima@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -2431,6 +2437,13 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-comments@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/extract-comments/-/extract-comments-1.0.0.tgz#ad4e640704d8a9a124faf8776b47735ff092a593" + dependencies: + esprima-extract-comments "^1.0.1" + parse-code-context "^0.2.2" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -3123,6 +3136,14 @@ gulp-plumber@^1.2.0: plugin-error "^0.1.2" through2 "^2.0.3" +gulp-remember@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gulp-remember/-/gulp-remember-1.0.1.tgz#cc3aab2a04a623614375571ca464d13e87502bfe" + dependencies: + fancy-log "^1.3.2" + plugin-error "^0.1.2" + through2 "^0.5.0" + gulp-rename@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.3.0.tgz#2e789d8f563ab0c924eeb62967576f37ff4cb826" @@ -5791,6 +5812,10 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" +parse-code-context@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/parse-code-context/-/parse-code-context-0.2.2.tgz#144b8afb7219482d7e88c1eb6a765596f3a6ac0d" + parse-entities@^1.0.2: version "1.1.2" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.1.2.tgz#9eaf719b29dc3bd62246b4332009072e01527777" @@ -6401,7 +6426,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@1.0: +readable-stream@1.0, readable-stream@~1.0.17: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: @@ -7475,6 +7500,13 @@ through2-filter@^2.0.0: through2 "~2.0.0" xtend "~4.0.0" +through2@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.5.1.tgz#dfdd012eb9c700e2323fd334f38ac622ab372da7" + dependencies: + readable-stream "~1.0.17" + xtend "~3.0.0" + through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" @@ -8305,6 +8337,10 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" +xtend@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" + y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"