Skip to content

Commit

Permalink
feat(drawer): add innerRef prop (#758)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasecdb authored and Matt Goo committed Mar 19, 2019
1 parent fcd480d commit 364b0b2
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/drawer/README.md
Expand Up @@ -419,6 +419,7 @@ modal | Boolean | Indicates that the drawer is of type modal.
dismissible | Boolean | Indicates that the drawer is of type dismissible.
tag | String | Customizes the drawer tag type (default to `<aside>`).
open | boolean | If true, opens drawer. If false, closes drawer.
innerRef | RefObject | Root drawer element ref.

## Sass Mixins

Expand Down
29 changes: 28 additions & 1 deletion packages/drawer/index.tsx
Expand Up @@ -39,6 +39,8 @@ import {FocusTrap} from 'focus-trap';

const {cssClasses: listCssClasses} = MDCListFoundation;

type RefCallback<T> = (node: T) => void;

export interface DrawerProps extends React.HTMLProps<HTMLElement>{
className?: string;
open?: boolean;
Expand All @@ -47,12 +49,17 @@ export interface DrawerProps extends React.HTMLProps<HTMLElement>{
tag?: string;
dismissible?: boolean;
modal?: boolean;
innerRef?: RefCallback<HTMLElement> | React.RefObject<HTMLElement>;
};

interface DrawerState {
classList: Set<string>;
};

const isRefObject = function(ref: DrawerProps['innerRef']): ref is React.RefObject<HTMLElement> {
return typeof ref !== 'function';
};

class Drawer extends React.Component<DrawerProps, DrawerState> {
previousFocus: HTMLElement | null = null;
foundation: MDCDismissibleDrawerFoundation | MDCModalDrawerFoundation;
Expand Down Expand Up @@ -193,6 +200,25 @@ class Drawer extends React.Component<DrawerProps, DrawerState> {
this.foundation.handleTransitionEnd(evt);
};

attachRef = (node: HTMLElement) => {
const {innerRef} = this.props;

// https://github.com/facebook/react/issues/13029#issuecomment-410002316
// @ts-ignore this is acceptable according to the comment above
this.drawerElement.current = node;

if (!innerRef) {
return;
}

if (isRefObject(innerRef)) {
// @ts-ignore same as above
innerRef.current = node;
} else {
innerRef(node);
}
}

render() {
const {
/* eslint-disable no-unused-vars */
Expand All @@ -203,6 +229,7 @@ class Drawer extends React.Component<DrawerProps, DrawerState> {
dismissible,
children,
className,
innerRef,
/* eslint-enable no-unused-vars */
tag: Tag,
modal,
Expand All @@ -215,7 +242,7 @@ class Drawer extends React.Component<DrawerProps, DrawerState> {
// @ts-ignore */}
<Tag
className={this.classes}
ref={this.drawerElement}
ref={this.attachRef}
onKeyDown={this.handleKeyDown}
onTransitionEnd={this.handleTransitionEnd}
{...otherProps}
Expand Down
14 changes: 14 additions & 0 deletions test/unit/drawer/index.test.tsx
Expand Up @@ -283,3 +283,17 @@ test('click on scrim calls #foundation.handleScrimClick', () => {
scrim.simulate('click');
td.verify(wrapper.instance().foundation.handleScrimClick(), {times: 1});
});

test('handles object refs correctly', () => {
const myRef = React.createRef<HTMLElement>();
const wrapper = mount<Drawer>(<Drawer innerRef={myRef} />);
assert.isNotNull(myRef.current);
assert.isNotNull(wrapper.instance().drawerElement.current);
});

test('it handles function refs correctly', () => {
const refFn = coerceForTesting<(node: HTMLElement) => void>(td.func());
const wrapper = mount<Drawer>(<Drawer innerRef={refFn} />);
assert.isNotNull(wrapper.instance().drawerElement.current);
td.verify(refFn(wrapper.instance().drawerElement.current!), {times: 1});
});

0 comments on commit 364b0b2

Please sign in to comment.