diff --git a/.gitignore b/.gitignore index 4e90dc53..e460f981 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ styles.css es/ lib/ coverage/ +website/_build/_doc/sampleDoc.json diff --git a/package.json b/package.json index 085c6f86..803195ef 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "build:css": "node-sass src/_BaseTable.scss ./styles.css --output-style expanded", "build": "npm run build:js && npm run build:es && npm run build:css", "build:typescript": "tsc", + "postinstall": "npm run build:typescript", "format": "prettier --write 'src/**/*.{js,scss}'", "prebuild": "npm run clean", "precommit": "lint-staged", diff --git a/src/TableHeaderRow.tsx b/src/TableHeaderRow.tsx index be174084..c561f352 100644 --- a/src/TableHeaderRow.tsx +++ b/src/TableHeaderRow.tsx @@ -12,7 +12,7 @@ export interface ITableHeaderRowProps { style?: React.CSSProperties; columns: IColumnProps[]; headerIndex?: number; - cellRenderer?: React.ElementType>; + cellRenderer?: (props: ICellRendererCBParam) => React.ReactNode; headerRenderer?: React.ElementType; expandColumnKey?: string; expandIcon?: React.ElementType; @@ -28,7 +28,7 @@ const TableHeaderRow: TTableHeaderRow = ({ style, columns, headerIndex, - cellRenderer: CellRenderer, + cellRenderer, headerRenderer, expandColumnKey, expandIcon: ExpandIcon, @@ -43,7 +43,7 @@ const TableHeaderRow: TTableHeaderRow = ({ headerIndex, expandIcon: column.key === expandColumnKey && , }; - return ; + return cellRenderer({...cellProps}); }); if (headerRenderer) { diff --git a/src/TableRow.tsx b/src/TableRow.tsx index aec7f5c2..a5ddf856 100644 --- a/src/TableRow.tsx +++ b/src/TableRow.tsx @@ -25,7 +25,7 @@ export interface ITableRowProps { depth?: number; rowEventHandlers?: THandlerCollection; rowRenderer?: React.ElementType | React.ReactElement; - cellRenderer?: React.ElementType>; + cellRenderer?: (props: ICellRendererCBParam) => React.ReactNode; expandIconRenderer?: React.ElementType>; onRowHover?: (args: IOnRowHover) => void; onRowExpand?: (args: IOnRowExpandCBParam) => any; @@ -52,7 +52,7 @@ class TableRow extends React.PureComponent> { depth, rowEventHandlers, rowRenderer, - cellRenderer: CellRenderer, + cellRenderer, expandIconRenderer: ExpandIconRenderer, tagName: Tag, // omit the following from rest @@ -76,7 +76,7 @@ class TableRow extends React.PureComponent> { rowIndex, expandIcon: column.key === expandColumnKey && expandIcon, }; - return ; + return cellRenderer({...cellProps}); }); if (rowRenderer) { diff --git a/website/gatsby-config.js b/website/gatsby-config.js index f5dc695b..fc17cf60 100644 --- a/website/gatsby-config.js +++ b/website/gatsby-config.js @@ -66,7 +66,7 @@ module.exports = { options: { name: 'api', ignore: ['**/*.snap', '**/*.scss'], - path: `${__dirname}/../build`, + path: `${__dirname}/../src`, }, }, { @@ -75,6 +75,6 @@ module.exports = { plugins: ['gatsby-remark-copy-linked-files'], }, }, - 'gatsby-transformer-react-docgen', + 'gatsby-transformer-react-docgen-typescript', ], } diff --git a/website/gatsby-node.js b/website/gatsby-node.js index 38da9148..29eac3e5 100644 --- a/website/gatsby-node.js +++ b/website/gatsby-node.js @@ -1,6 +1,7 @@ const path = require('path') +const fs = require('fs') const _ = require('lodash') - +const { findFileWithExtension } = require('./utilsNode') const siteConfig = require('./siteConfig') exports.onCreateWebpackConfig = ({ stage, getConfig, actions }) => { @@ -17,6 +18,8 @@ exports.onCreateWebpackConfig = ({ stage, getConfig, actions }) => { 'react-base-table': path.resolve(__dirname, '../build'), } + config.devtool = `source-map` + actions.replaceWebpackConfig(config) } @@ -69,7 +72,6 @@ exports.onCreateNode = ({ node, actions, getNode, createNodeId }) => { exports.createPages = async ({ graphql, actions, getNode }) => { const { createPage } = actions - const docPage = path.resolve('src/templates/doc.js') const apiPage = path.resolve('src/templates/api.js') const examplePage = path.resolve('src/templates/example.js') @@ -123,12 +125,24 @@ exports.createPages = async ({ graphql, actions, getNode }) => { }) }) + const docDisplayNames = findFileWithExtension('tsx').map(item => { + return path.basename(item).replace('.tsx', '') + }) + result.data.allComponentMetadata.edges.forEach(edge => { const node = edge.node const fileNode = getNode(node.parent.id) if (fileNode.sourceInstanceName !== 'api') return const { displayName: name, docblock } = node - if (!docblock) return + if (!docblock) { + const currentDisplayNameMatch = docDisplayNames.find(displayNameItem => { + return displayNameItem === name + }) + + if (!currentDisplayNameMatch) { + return + } + } createPage({ path: `/api/${name.toLowerCase()}`, component: apiPage, diff --git a/website/package.json b/website/package.json index 5f4c7c21..b5e0be0e 100644 --- a/website/package.json +++ b/website/package.json @@ -26,6 +26,7 @@ "prop-types": "^15.7.2", "react": "^16.8.5", "react-dom": "^16.8.5", + "react-docgen-typescript": "^1.15.0", "react-helmet": "^5.2.0", "react-inspector": "^2.3.1", "react-live-runner": "^0.7.3", diff --git a/website/plugins/gatsby-transformer-react-docgen-typescript/README.md b/website/plugins/gatsby-transformer-react-docgen-typescript/README.md new file mode 100644 index 00000000..a27ddbf8 --- /dev/null +++ b/website/plugins/gatsby-transformer-react-docgen-typescript/README.md @@ -0,0 +1,38 @@ + +# gatsby-transformer-react-docgen-typescript +Parses inline component-documentation using +[react-docgen](https://github.com/reactjs/react-docgen), +[react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript) and +[gatsby-transform-react-docgen](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-transformer-react-docgen) + +## Usage + +Add a plugin-entry to your `gatsby-config.js` + +```js +module.exports = { + plugins: [`gatsby-transformer-react-docgen-typescript`], +} +``` + +## How to query + +An example _graphql_ query to get nodes: + +```graphql +{ + allComponentMetadata { + edges { + node { + displayName + description + props { + name + type + required + } + } + } + } +} +``` diff --git a/website/plugins/gatsby-transformer-react-docgen-typescript/gatsby-node.js b/website/plugins/gatsby-transformer-react-docgen-typescript/gatsby-node.js new file mode 100644 index 00000000..f5d061d5 --- /dev/null +++ b/website/plugins/gatsby-transformer-react-docgen-typescript/gatsby-node.js @@ -0,0 +1,4 @@ +"use strict"; + +exports.onCreateNode = require(`./on-node-create`).default; +exports.setFieldsOnGraphQLNodeType = require(`gatsby-transformer-react-docgen/extend-node-type`).default; \ No newline at end of file diff --git a/website/plugins/gatsby-transformer-react-docgen-typescript/on-node-create.js b/website/plugins/gatsby-transformer-react-docgen-typescript/on-node-create.js new file mode 100644 index 00000000..79f94739 --- /dev/null +++ b/website/plugins/gatsby-transformer-react-docgen-typescript/on-node-create.js @@ -0,0 +1,206 @@ +const path = require('path') +var tsxParser = require('react-docgen-typescript') + +try { + tsxParser = tsxParser.withCustomConfig( + path.resolve(__dirname, '../../../tsconfig.json'), + { + propFilter(prop) { + if (prop.parent) { + return !prop.parent.fileName.includes('node_modules') + } + return true + }, + } + ) +} catch (err) { + console.log('Error in initiating react-docgen-typescript: ', err) +} + +var _interopRequireDefault = require(`@babel/runtime/helpers/interopRequireDefault`) + +exports.__esModule = true +exports.default = onCreateNode + +const _parse = _interopRequireDefault( + require('gatsby-transformer-react-docgen/parse') +) + +const propsId = (parentId, name) => `${parentId}--ComponentProp-${name}` + +const descId = parentId => `${parentId}--ComponentDescription` + +function canParse(node) { + return ( + node && + (node.internal.mediaType === `application/javascript` || + // TypeScript doesn't really have a mime type and .ts files are a media file :/ + node.internal.mediaType === `application/typescript` || + node.internal.mediaType === `text/jsx` || + node.internal.mediaType === `text/tsx` || + node.extension === `tsx` || + node.extension === `ts`) + ) +} + +function createDescriptionNode( + node, + entry, + actions, + createNodeId, + createContentDigest +) { + const { createNode } = actions + delete node.description + const descriptionNode = { + id: createNodeId(descId(node.id)), + parent: node.id, + children: [], + text: entry.description, + internal: { + type: `ComponentDescription`, + mediaType: `text/markdown`, + content: entry.description, + contentDigest: createContentDigest(entry.description), + }, + } + node.description___NODE = descriptionNode.id + node.children = node.children.concat([descriptionNode.id]) + createNode(descriptionNode) + return node +} + +function createPropNodes( + node, + component, + actions, + createNodeId, + createContentDigest +) { + const { createNode } = actions + + let children = new Array(component.props.length) + + let propNames + let currentProps + if (Array.isArray(component.props)) { + currentProps = component.props + } else { + propNames = Object.keys(component.props) + currentProps = propNames.map(propName => { + return component.props[propName] + }) + } + + currentProps.length > 0 && + currentProps.forEach((prop, i) => { + let propNodeId = propsId(node.id, prop.name) + let content = JSON.stringify(prop) + let propNode = Object.assign({}, prop, { + id: createNodeId(propNodeId), + children: [], + parent: node.id, + parentType: prop.type, + internal: { + type: `ComponentProp`, + contentDigest: createContentDigest(content), + }, + }) + children[i] = propNode.id + propNode = createDescriptionNode( + propNode, + prop, + actions, + createNodeId, + createContentDigest + ) + createNode(propNode) + }) + node.props___NODE = children + node.children = node.children.concat(children) + return node +} + +async function onCreateNode( + { + node, + loadNodeContent, + actions, + createNodeId, + reporter, + createContentDigest, + }, + pluginOptions +) { + const { createNode, createParentChildLink } = actions + + if (!canParse(node)) return + + const content = await loadNodeContent(node) + let components + + const filepath = path.resolve(node.absolutePath) + try { + if ( + !filepath.includes('examples') && + (filepath.includes('.ts') || filepath.includes('.tsx')) + ) { + console.log('parsing: ', filepath) + components = tsxParser.parse(filepath) + } else { + components = (0, _parse.default)(content, node, pluginOptions) + } + } catch (err) { + reporter.error( + `There was a problem parsing component metadata for file: "${ + node.relativePath + }"`, + err + ) + return + } + + components.forEach(component => { + const strContent = JSON.stringify(component) + const contentDigest = createContentDigest(strContent) + const nodeId = `${node.id}--${component.displayName}--ComponentMetadata` + let metadataNode = Object.assign({}, component, { + props: null, + // handled by the prop node creation + id: createNodeId(nodeId), + children: [], + parent: node.id, + internal: { + contentDigest, + type: `ComponentMetadata`, + }, + }) + createParentChildLink({ + parent: node, + child: metadataNode, + }) + metadataNode = createPropNodes( + metadataNode, + component, + actions, + createNodeId, + createContentDigest + ) + metadataNode = createDescriptionNode( + metadataNode, + component, + actions, + createNodeId, + createContentDigest + ) + + if (metadataNode.children) { + for (let i = 0; i < metadataNode.children.length; i++) { + if (metadataNode.children[i] === undefined) { + metadataNode.children.splice(i, 1) + } + } + } + createNode(metadataNode) + }) +} diff --git a/website/plugins/gatsby-transformer-react-docgen-typescript/package.json b/website/plugins/gatsby-transformer-react-docgen-typescript/package.json new file mode 100644 index 00000000..c087d81e --- /dev/null +++ b/website/plugins/gatsby-transformer-react-docgen-typescript/package.json @@ -0,0 +1,3 @@ +{ + "name": "gatsby-transformer-react-docgen-typescript" +} diff --git a/website/utilsNode.js b/website/utilsNode.js new file mode 100644 index 00000000..a4dbdea0 --- /dev/null +++ b/website/utilsNode.js @@ -0,0 +1,33 @@ +const path = require('path') +const cp = require('child_process') + +/** + * Assuming being executed from somewhere inside the website folder, the function returns all files in + * react-base-table/src directory with the `ext` file-extension. + * @param {string} ext The file extension, e.g, 'tsx', 'jsx' etc. + * @returns {Array} Array of files found. + */ +const findFileWithExtension = ext => { + const pathWebsite_pwd = cp.execSync('pwd', { encoding: 'utf-8' }) + const pathToReactBaseTableSrc_pwd = + pathWebsite_pwd.split('website')[0] + 'src' + var command = `find ${pathToReactBaseTableSrc_pwd} -name *.${ext}` + const filesWithExt = cp.execSync(command, { encoding: 'utf-8' }) + const filesWithExtArr = filesWithExt.split('\n') + + const outArr = [] + for (let i = 0; i < filesWithExtArr.length - 1; i++) { + const filesWithExtArr_dirString = path.resolve( + __dirname, + '../', + 'src', + filesWithExtArr[i].split('src/')[1] + ) + outArr.push(filesWithExtArr_dirString) + } + return outArr +} + +module.exports = { + findFileWithExtension, +} diff --git a/website/yarn.lock b/website/yarn.lock index 18ea1c2e..bf2705fd 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -9277,6 +9277,11 @@ react-dev-utils@^4.2.3: dependencies: ast-types "0.13.2" +react-docgen-typescript@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-1.15.0.tgz#963f14210841f9b51ed18c65152a6cc37f1c3184" + integrity sha512-8xObdkRQbrc0505tEdVRO+pdId8pKFyD6jhLYM9FDdceKma+iB+a17Dk7e3lPRBRh8ArQLCedOCOfN/bO338kw== + react-docgen@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-4.1.1.tgz#8fef0212dbf14733e09edecef1de6b224d87219e"