Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Add ref forwarding to API docs #15135

Merged
merged 6 commits into from Apr 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/scripts/buildApi.js
Expand Up @@ -7,6 +7,7 @@ import { parse as docgenParse } from 'react-docgen';
import generateMarkdown from '../src/modules/utils/generateMarkdown';
import { findPagesMarkdown, findComponents } from '../src/modules/utils/find';
import { getHeaders } from '../src/modules/utils/parseMarkdown';
import parseTest from '../src/modules/utils/parseTest';
import createMuiTheme from '../../packages/material-ui/src/styles/createMuiTheme';
import getStylesCreator from '../../packages/material-ui-styles/src/getStylesCreator';

Expand Down Expand Up @@ -73,7 +74,7 @@ function getInheritance(src) {
};
}

function buildDocs(options) {
async function buildDocs(options) {
const { component: componentObject, pagesMarkdown } = options;
const src = readFileSync(componentObject.filename, 'utf8');

Expand Down Expand Up @@ -143,6 +144,10 @@ function buildDocs(options) {
reactAPI.src = src;
reactAPI.spread = spread;

const testInfo = await parseTest(componentObject.filename);
// no Object.assign to visually check for collisions
reactAPI.forwardsRefTo = testInfo.forwardsRefTo;

// if (reactAPI.name !== 'TableCell') {
// return;
// }
Expand Down Expand Up @@ -199,7 +204,11 @@ function run() {
const components = findComponents(path.resolve(rootDirectory, args[2]));

components.forEach(component => {
buildDocs({ component, pagesMarkdown });
buildDocs({ component, pagesMarkdown }).catch(error => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised it hasn't exploded yet 🚀🌕. This is like 200 concurrent promises 💪.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will celebrate the day uncaught promises will just throw. The current state of async errors in node is really annoying.

console.warn(`error building docs for ${component.filename}`);
console.error(error);
process.exit(1);
});
});
}

Expand Down
8 changes: 8 additions & 0 deletions docs/src/modules/utils/generateMarkdown.js
Expand Up @@ -273,6 +273,14 @@ function generateProps(reactAPI) {
return textProps;
}, text);

let refHint = 'The `ref` is forwarded to the root element.';
if (reactAPI.forwardsRefTo == null) {
refHint = 'The component cannot hold a ref.';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the case for almost all components that do not forward refs. Even if some of them can hold a ref we want to prevent users from doing that because converting those components to function components would be breaking.

} else if (reactAPI.forwardsRefTo === 'React.Component') {
refHint = 'The `ref` is attached to a component class.';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TouchRipple is the only candidate for that (allows imperative handle). The other components will be covered by #15170.

}
text = `${text}\n${refHint}\n`;

if (reactAPI.spread) {
text = `${text}
Any other properties supplied will be spread to the root element (${
Expand Down
102 changes: 102 additions & 0 deletions docs/src/modules/utils/parseTest.js
@@ -0,0 +1,102 @@
import * as babel from '@babel/core';
import { readFile } from 'fs-extra';
import * as path from 'path';

const workspaceRoot = path.join(__dirname, '../../../../');
const babelConfigPath = path.join(workspaceRoot, 'babel.config.js');

function withExtension(filepath, extension) {
return path.join(
path.dirname(filepath),
path.basename(filepath, path.extname(filepath)) + extension,
);
}

/**
* @param {string} filename
* @param {string} configFilePath
*/
async function parseWithConfig(filename, configFilePath) {
const source = await readFile(filename, { encoding: 'utf8' });
const partialConfig = babel.loadPartialConfig({
configFile: configFilePath,
filename,
});
return babel.parseAsync(source, partialConfig.options);
}

function findConformanceDescriptor(program) {
const { types: t } = babel;

let descriptor = {};
babel.traverse(program, {
CallExpression(babelPath) {
const { node: callExpression } = babelPath;
const { callee } = callExpression;
if (t.isIdentifier(callee) && callee.name === 'describeConformance') {
// describeConformance(element, () => options);
descriptor = callExpression.arguments[1].body;
}
},
});

if (descriptor.type != null && !t.isObjectExpression(descriptor)) {
throw new Error(`Expected an object expression as a descriptor but found ${descriptor.type}`);
}

return descriptor;
}

/**
*
* @param {import('@babel/core').Node} valueNode
*/
function getRefInstance(valueNode) {
if (!babel.types.isMemberExpression(valueNode)) {
throw new Error('Expected a member expression in refInstanceof');
}

switch (valueNode.object.name) {
case 'window':
return valueNode.property.name;
case 'React':
return `React.${valueNode.property.name}`;
default:
throw new Error(`Unrecognized member expression starting with '${valueNode.object.name}'`);
}
}

/**
* @typedef {Object} ParseResult
* @property {string?} forwardsRefTo
*/

/**
*
* @param {string} componentFilename
* @returns {ParseResult}
*/
export default async function parseTest(componentFilename) {
const testFilename = withExtension(componentFilename, '.test.js');
const babelParseResult = await parseWithConfig(testFilename, babelConfigPath);
const descriptor = findConformanceDescriptor(babelParseResult.program);

const result = {
forwardsRefTo: undefined,
};

const { properties = [] } = descriptor;
properties.forEach(property => {
const key = property.key.name;

switch (key) {
case 'refInstanceof':
result.forwardsRefTo = getRefInstance(property.value);
eps1lon marked this conversation as resolved.
Show resolved Hide resolved
break;
default:
break;
}
});

return result;
}
20 changes: 8 additions & 12 deletions packages/material-ui/src/ButtonBase/TouchRipple.test.js
Expand Up @@ -4,7 +4,7 @@ import { assert } from 'chai';
import {
createShallow,
createMount,
findOutermostIntrinsic,
describeConformance,
getClasses,
unwrap,
} from '@material-ui/core/test-utils';
Expand All @@ -29,17 +29,13 @@ describe('<TouchRipple />', () => {
mount.cleanUp();
});

it('should render a span', () => {
const wrapper = mount(<TouchRipple />);
const root = findOutermostIntrinsic(wrapper);
assert.strictEqual(root.type(), 'span');
assert.strictEqual(root.hasClass(classes.root), true);
});

it('should render the custom className', () => {
const wrapper = mount(<TouchRipple className="test-class-name" />);
assert.strictEqual(findOutermostIntrinsic(wrapper).hasClass('test-class-name'), true);
});
describeConformance(<TouchRipple />, () => ({
classes,
inheritComponent: 'span',
mount,
refInstanceof: React.Component,
testComponentPropWith: false,
}));

describe('prop: center', () => {
it('should should compute the right ripple dimensions', () => {
Expand Down
2 changes: 2 additions & 0 deletions pages/api/app-bar.md
Expand Up @@ -23,6 +23,8 @@ import AppBar from '@material-ui/core/AppBar';
| <span class="prop-name">color</span> | <span class="prop-type">enum:&nbsp;'inherit'&nbsp;&#124;<br>&nbsp;'primary'&nbsp;&#124;<br>&nbsp;'secondary'&nbsp;&#124;<br>&nbsp;'default'<br></span> | <span class="prop-default">'primary'</span> | The color of the component. It supports those theme colors that make sense for this component. |
| <span class="prop-name">position</span> | <span class="prop-type">enum:&nbsp;'fixed', 'absolute', 'sticky', 'static', 'relative'<br></span> | <span class="prop-default">'fixed'</span> | The positioning type. The behavior of the different options is described [in the MDN web docs](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning). Note: `sticky` is not universally supported and will fall back to `static` when unavailable. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element ([Paper](/api/paper/)).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/avatar.md
Expand Up @@ -27,6 +27,8 @@ import Avatar from '@material-ui/core/Avatar';
| <span class="prop-name">src</span> | <span class="prop-type">string</span> | | The `src` attribute for the `img` element. |
| <span class="prop-name">srcSet</span> | <span class="prop-type">string</span> | | The `srcSet` attribute for the `img` element. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/backdrop.md
Expand Up @@ -23,6 +23,8 @@ import Backdrop from '@material-ui/core/Backdrop';
| <span class="prop-name required">open&nbsp;*</span> | <span class="prop-type">bool</span> | | If `true`, the backdrop is open. |
| <span class="prop-name">transitionDuration</span> | <span class="prop-type">union:&nbsp;number&nbsp;&#124;<br>&nbsp;{ enter?: number, exit?: number }<br></span> | | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/badge.md
Expand Up @@ -28,6 +28,8 @@ import Badge from '@material-ui/core/Badge';
| <span class="prop-name">showZero</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Controls whether the badge is hidden when `badgeContent` is zero. |
| <span class="prop-name">variant</span> | <span class="prop-type">enum:&nbsp;'standard'&nbsp;&#124;<br>&nbsp;'dot'<br></span> | <span class="prop-default">'standard'</span> | The variant to use. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/bottom-navigation-action.md
Expand Up @@ -25,6 +25,8 @@ import BottomNavigationAction from '@material-ui/core/BottomNavigationAction';
| <span class="prop-name">showLabel</span> | <span class="prop-type">bool</span> | | If `true`, the `BottomNavigationAction` will show its label. By default, only the selected `BottomNavigationAction` inside `BottomNavigation` will show its label. |
| <span class="prop-name">value</span> | <span class="prop-type">any</span> | | You can provide your own value. Otherwise, we fallback to the child position index. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element ([ButtonBase](/api/button-base/)).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/bottom-navigation.md
Expand Up @@ -25,6 +25,8 @@ import BottomNavigation from '@material-ui/core/BottomNavigation';
| <span class="prop-name">showLabels</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, all `BottomNavigationAction`s will show their labels. By default, only the selected `BottomNavigationAction` will show its label. |
| <span class="prop-name">value</span> | <span class="prop-type">any</span> | | The value of the currently selected `BottomNavigationAction`. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/breadcrumbs.md
Expand Up @@ -26,6 +26,8 @@ import Breadcrumbs from '@material-ui/core/Breadcrumbs';
| <span class="prop-name">maxItems</span> | <span class="prop-type">number</span> | <span class="prop-default">8</span> | Specifies the maximum number of breadcrumbs to display. When there are more than the maximum number, only the first and last will be shown, with an ellipsis in between. |
| <span class="prop-name">separator</span> | <span class="prop-type">node</span> | <span class="prop-default">'/'</span> | Custom separator node. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## Demos
Expand Down
2 changes: 2 additions & 0 deletions pages/api/button-base.md
Expand Up @@ -35,6 +35,8 @@ It contains a load of style reset and some focus/ripple logic.
| <span class="prop-name">TouchRippleProps</span> | <span class="prop-type">object</span> | | Properties applied to the `TouchRipple` element. |
| <span class="prop-name">type</span> | <span class="prop-type">enum:&nbsp;'submit'&nbsp;&#124;<br>&nbsp;'reset'&nbsp;&#124;<br>&nbsp;'button'<br></span> | <span class="prop-default">'button'</span> | Used to control the button's purpose. This property passes the value to the `type` attribute of the native button component. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/button.md
Expand Up @@ -30,6 +30,8 @@ import Button from '@material-ui/core/Button';
| <span class="prop-name">size</span> | <span class="prop-type">enum:&nbsp;'small'&nbsp;&#124;<br>&nbsp;'medium'&nbsp;&#124;<br>&nbsp;'large'<br></span> | <span class="prop-default">'medium'</span> | The size of the button. `small` is equivalent to the dense button styling. |
| <span class="prop-name">variant</span> | <span class="prop-type">enum:&nbsp;'text'&nbsp;&#124;<br>&nbsp;'outlined'&nbsp;&#124;<br>&nbsp;'contained'<br></span> | <span class="prop-default">'text'</span> | The variant to use. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element ([ButtonBase](/api/button-base/)).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/card-action-area.md
Expand Up @@ -21,6 +21,8 @@ import CardActionArea from '@material-ui/core/CardActionArea';
| <span class="prop-name">children</span> | <span class="prop-type">node</span> | | The content of the component. |
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element ([ButtonBase](/api/button-base/)).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/card-actions.md
Expand Up @@ -22,6 +22,8 @@ import CardActions from '@material-ui/core/CardActions';
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
| <span class="prop-name">disableActionSpacing</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the card actions do not have additional margin. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/card-content.md
Expand Up @@ -21,6 +21,8 @@ import CardContent from '@material-ui/core/CardContent';
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
| <span class="prop-name">component</span> | <span class="prop-type">elementType</span> | <span class="prop-default">'div'</span> | The component used for the root node. Either a string to use a DOM element or a component. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/card-header.md
Expand Up @@ -28,6 +28,8 @@ import CardHeader from '@material-ui/core/CardHeader';
| <span class="prop-name">title</span> | <span class="prop-type">node</span> | | The content of the Card Title. |
| <span class="prop-name">titleTypographyProps</span> | <span class="prop-type">object</span> | | These props will be forwarded to the title (as long as disableTypography is not `true`). |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/card-media.md
Expand Up @@ -23,6 +23,8 @@ import CardMedia from '@material-ui/core/CardMedia';
| <span class="prop-name">image</span> | <span class="prop-type">string</span> | | Image to be displayed as a background image. Either `image` or `src` prop must be specified. Note that caller must specify height otherwise the image will not be visible. |
| <span class="prop-name">src</span> | <span class="prop-type">string</span> | | An alias for `image` property. Available only with media components. Media components: `video`, `audio`, `picture`, `iframe`, `img`. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/card.md
Expand Up @@ -21,6 +21,8 @@ import Card from '@material-ui/core/Card';
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
| <span class="prop-name">raised</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the card will use raised styling. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element ([Paper](/api/paper/)).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/checkbox.md
Expand Up @@ -34,6 +34,8 @@ import Checkbox from '@material-ui/core/Checkbox';
| <span class="prop-name">type</span> | <span class="prop-type">string</span> | | The input component property `type`. |
| <span class="prop-name">value</span> | <span class="prop-type">string</span> | | The value of the component. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element ([IconButton](/api/icon-button/)).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/chip.md
Expand Up @@ -30,6 +30,8 @@ Chips represent complex entities in small blocks, such as a contact.
| <span class="prop-name">onDelete</span> | <span class="prop-type">func</span> | | Callback function fired when the delete icon is clicked. If set, the delete icon will be shown. |
| <span class="prop-name">variant</span> | <span class="prop-type">enum:&nbsp;'default'&nbsp;&#124;<br>&nbsp;'outlined'<br></span> | <span class="prop-default">'default'</span> | The variant to use. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/circular-progress.md
Expand Up @@ -30,6 +30,8 @@ attribute to `true` on that region until it has finished loading.
| <span class="prop-name">value</span> | <span class="prop-type">number</span> | <span class="prop-default">0</span> | The value of the progress indicator for the determinate and static variants. Value between 0 and 100. |
| <span class="prop-name">variant</span> | <span class="prop-type">enum:&nbsp;'determinate'&nbsp;&#124;<br>&nbsp;'indeterminate'&nbsp;&#124;<br>&nbsp;'static'<br></span> | <span class="prop-default">'indeterminate'</span> | The variant to use. Use indeterminate when there is no progress value. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/click-away-listener.md
Expand Up @@ -24,6 +24,8 @@ For instance, if you need to hide a menu when people click anywhere else on your
| <span class="prop-name required">onClickAway&nbsp;*</span> | <span class="prop-type">func</span> | | Callback fired when a "click away" event is detected. |
| <span class="prop-name">touchEvent</span> | <span class="prop-type">enum:&nbsp;'onTouchStart'&nbsp;&#124;<br>&nbsp;'onTouchEnd'&nbsp;&#124;<br>&nbsp;false<br></span> | <span class="prop-default">'onTouchEnd'</span> | The touch event to listen to. You can disable the listener by providing `false`. |

The component cannot hold a ref.

Any other properties supplied will be spread to the root element ([EventListener](https://github.com/oliviertassinari/react-event-listener/)).

## Inheritance
Expand Down
2 changes: 2 additions & 0 deletions pages/api/collapse.md
Expand Up @@ -27,6 +27,8 @@ It uses [react-transition-group](https://github.com/reactjs/react-transition-gro
| <span class="prop-name">in</span> | <span class="prop-type">bool</span> | | If `true`, the component will transition in. |
| <span class="prop-name">timeout</span> | <span class="prop-type">union:&nbsp;number&nbsp;&#124;<br>&nbsp;{ enter?: number, exit?: number }&nbsp;&#124;<br>&nbsp;enum:&nbsp;'auto'<br><br></span> | <span class="prop-default">duration.standard</span> | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object.<br>Set to 'auto' to automatically calculate transition time based on height. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element ([Transition](https://reactcommunity.org/react-transition-group/#Transition)).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/container.md
Expand Up @@ -24,6 +24,8 @@ import Container from '@material-ui/core/Container';
| <span class="prop-name">fixed</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Set the max-width to match the min-width of the current breakpoint. This is useful if you'd prefer to design for a fixed set of sizes instead of trying to accommodate a fully fluid viewport. It's fluid by default. |
| <span class="prop-name">maxWidth</span> | <span class="prop-type">enum:&nbsp;'xs', 'sm', 'md', 'lg', 'xl', false<br></span> | <span class="prop-default">'lg'</span> | Determine the max-width of the container. The container width grows with the size of the screen. Set to `false` to disable `maxWidth`. |

The `ref` is forwarded to the root element.

Any other properties supplied will be spread to the root element (native element).

## CSS
Expand Down
2 changes: 2 additions & 0 deletions pages/api/css-baseline.md
Expand Up @@ -20,6 +20,8 @@ Kickstart an elegant, consistent, and simple baseline to build upon.
|:-----|:-----|:--------|:------------|
| <span class="prop-name">children</span> | <span class="prop-type">node</span> | <span class="prop-default">null</span> | You can wrap a node. |

The component cannot hold a ref.


## Demos

Expand Down