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

typescript(vx-responsive): re-write package in TypeScript #517

Merged
merged 12 commits into from
Oct 9, 2019
2 changes: 2 additions & 0 deletions packages/vx-responsive/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"esm"
Expand All @@ -30,6 +31,7 @@
},
"homepage": "https://github.com/hshoff/vx#readme",
"dependencies": {
"@types/react": "*",
"lodash": "^4.17.10",
"prop-types": "^15.6.1",
"resize-observer-polyfill": "1.5.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
import debounce from 'lodash/debounce';
import PropTypes from 'prop-types';
import React from 'react';
import ResizeObserver from 'resize-observer-polyfill';

export default class ParentSize extends React.Component {
constructor(props) {
type Props = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

could we export this and also name it ParentSizeProps?

className?: string;
debounceTime?: number;
innerRef?: React.Ref<HTMLDivElement>;
Rudeg marked this conversation as resolved.
Show resolved Hide resolved
children: (args: {
Rudeg marked this conversation as resolved.
Show resolved Hide resolved
ref: HTMLDivElement | null;
resize: (state: State) => void;
}) => React.ReactNode;
};

type State = {
Rudeg marked this conversation as resolved.
Show resolved Hide resolved
width: number;
height: number;
top: number;
left: number;
};

export default class ParentSize extends React.Component<Props, State> {
Rudeg marked this conversation as resolved.
Show resolved Hide resolved
static defaultProps = {
debounceTime: 300,
};

animationFrameID: number | null;
ro: ResizeObserver | undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we want to use more verbose name?

target: HTMLDivElement | null = null;

constructor(props: Props) {
super(props);
this.state = {
width: 0,
Expand All @@ -26,19 +50,19 @@ export default class ParentSize extends React.Component {
});
});
});
this.ro.observe(this.target);
if (this.target) this.ro.observe(this.target);
}

componentWillUnmount() {
window.cancelAnimationFrame(this.animationFrameID);
this.ro.disconnect();
if (this.animationFrameID) window.cancelAnimationFrame(this.animationFrameID);
if (this.ro) this.ro.disconnect();
}

resize({ width, height, top, left }) {
resize({ width, height, top, left }: State) {
this.setState(() => ({ width, height, top, left }));
}

setTarget(ref) {
setTarget(ref: HTMLDivElement | null) {
this.target = ref;
}

Expand All @@ -60,13 +84,3 @@ export default class ParentSize extends React.Component {
);
}
}

ParentSize.defaultProps = {
debounceTime: 300,
};

ParentSize.propTypes = {
className: PropTypes.string,
children: PropTypes.func.isRequired,
debounceTime: PropTypes.number,
};
Original file line number Diff line number Diff line change
@@ -1,42 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';

ResponsiveSVG.propTypes = {
children: PropTypes.any,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
xOrigin: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
yOrigin: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
preserveAspectRatio: PropTypes.string,
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
};

export default function ResponsiveSVG({
children,
width,
height,
xOrigin = 0,
yOrigin = 0,
preserveAspectRatio = 'xMinYMin meet',
innerRef,
}) {
return (
<div
style={{
display: 'inline-block',
position: 'relative',
width: '100%',
verticalAlign: 'top',
overflow: 'hidden',
}}
>
<svg
preserveAspectRatio={preserveAspectRatio}
viewBox={`${xOrigin} ${yOrigin} ${width} ${height}`}
ref={innerRef}
>
{children}
</svg>
</div>
);
}
import React from 'react';

type Props = {
Rudeg marked this conversation as resolved.
Show resolved Hide resolved
children?: any;
Rudeg marked this conversation as resolved.
Show resolved Hide resolved
width?: number | string;
height?: number | string;
xOrigin?: number | string;
yOrigin?: number | string;
preserveAspectRatio?: string;
innerRef?: React.Ref<SVGSVGElement>;
};

export default function ResponsiveSVG({
children,
width,
height,
xOrigin = 0,
yOrigin = 0,
preserveAspectRatio = 'xMinYMin meet',
innerRef,
}: Props) {
return (
<div
style={{
display: 'inline-block',
position: 'relative',
width: '100%',
verticalAlign: 'top',
overflow: 'hidden',
}}
>
<svg
preserveAspectRatio={preserveAspectRatio}
viewBox={`${xOrigin} ${yOrigin} ${width} ${height}`}
ref={innerRef}
>
{children}
</svg>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,73 +1,80 @@
import React from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash/debounce';
import ResizeObserver from 'resize-observer-polyfill';

export default function withParentSize(BaseComponent) {
class WrappedComponent extends React.Component {
constructor(props) {
super(props);

this.state = {
parentWidth: null,
parentHeight: null,
};

this.animationFrameID = null;
this.debouncedResize = debounce(this.resize.bind(this), props.debounceTime).bind(this);
}

componentDidMount() {
this.ro = new ResizeObserver((entries /** , observer */) => {
entries.forEach(entry => {
const { width, height } = entry.contentRect;
this.animationFrameID = window.requestAnimationFrame(() => {
this.debouncedResize({
width,
height,
});
});
});
});
this.ro.observe(this.container);
}

componentWillUnmount() {
window.cancelAnimationFrame(this.animationFrameID);
this.ro.disconnect();
}

resize({ width, height }) {
this.setState({
parentWidth: width,
parentHeight: height,
});
}

render() {
const { parentWidth, parentHeight } = this.state;
return (
<div
style={{ width: '100%', height: '100%' }}
ref={ref => {
this.container = ref;
}}
>
{parentWidth !== null && parentHeight !== null && (
<BaseComponent parentWidth={parentWidth} parentHeight={parentHeight} {...this.props} />
)}
</div>
);
}
}

WrappedComponent.propTypes = {
debounceTime: PropTypes.number,
};

WrappedComponent.defaultProps = {
debounceTime: 300,
};

return WrappedComponent;
}
import React from 'react';
import debounce from 'lodash/debounce';
import ResizeObserver from 'resize-observer-polyfill';

type WithParentSizeProps = {
Rudeg marked this conversation as resolved.
Show resolved Hide resolved
debounceTime?: number;
};

type WithParentSizeState = {
Rudeg marked this conversation as resolved.
Show resolved Hide resolved
parentWidth: number | null;
parentHeight: number | null;
};

export default function withParentSize<Props extends WithParentSizeProps = {}>(
BaseComponent: React.ComponentType<Props>,
) {
return class WrappedComponent extends React.Component<Props, WithParentSizeState> {
static defaultProps = {
debounceTime: 300,
};
animationFrameID: number | null;
ro: ResizeObserver | undefined;
container: HTMLDivElement | null = null;
debouncedResize: ({ width, height }: { width: number; height: number }) => void;

constructor(props: Props) {
super(props);
this.state = {
parentWidth: null,
parentHeight: null,
};

this.animationFrameID = null;
this.debouncedResize = debounce(this.resize.bind(this), props.debounceTime).bind(this);
}

componentDidMount() {
this.ro = new ResizeObserver((entries /** , observer */) => {
entries.forEach(entry => {
const { width, height } = entry.contentRect;
this.animationFrameID = window.requestAnimationFrame(() => {
this.debouncedResize({
width,
height,
});
});
});
});
if (this.container) this.ro.observe(this.container);
}

componentWillUnmount() {
if (this.animationFrameID) window.cancelAnimationFrame(this.animationFrameID);
if (this.ro) this.ro.disconnect();
}

resize({ width, height }: { width: number; height: number }) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

use arrow function for binding?

this.setState({
parentWidth: width,
parentHeight: height,
});
}

render() {
const { parentWidth, parentHeight } = this.state;
return (
<div
style={{ width: '100%', height: '100%' }}
Copy link
Collaborator

@kristw kristw Oct 2, 2019

Choose a reason for hiding this comment

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

Convert to constant.
Note: This 100% style is known to create a layout bug that leave some area of the screen empty when the children use less than 100% width or height, e.g. 50% width & 50% height. This is because this div always secure 100% width & height bounding box in the layout.

ref={ref => {
this.container = ref;
}}
>
{parentWidth !== null && parentHeight !== null && (
<BaseComponent parentWidth={parentWidth} parentHeight={parentHeight} {...this.props} />
)}
</div>
);
}
};
}
Loading