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 conversion #1

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ module.exports = {
}
}
],
'@babel/preset-react'
'@babel/preset-react',
'@babel/typescript',
],
comments: false,
env: {
Expand Down
12 changes: 6 additions & 6 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = {

setupFiles: [require.resolve('core-js')],

moduleFileExtensions: ['jsx', 'js'],
moduleFileExtensions: ['ts', 'tsx', 'jsx', 'js'],

collectCoverageFrom: ['<rootDir>/src/**/*.{js,jsx}'],

Expand All @@ -30,19 +30,19 @@ module.exports = {
branches: 100,
function: 100,
lines: 100,
statements: 100
}
statements: 100,
},
},

// A map from regular expressions to paths to transformers
transform: {
'^.+\\.(js|jsx)$': require.resolve('./test-harness/preprocessor'),
'^(?!.*\\.(js|jsx|css|json)$)': require.resolve('./test-harness/fileTransform')
'^.+\\.(ts|tsx|js|jsx)$': require.resolve('./test-harness/preprocessor'),
'^(?!.*\\.(ts|tsx|js|jsx|css|json)$)': require.resolve('./test-harness/fileTransform'),
},

verbose: true,
testURL: 'http://localhost',

// The test environment that will be used for testing
testEnvironment: 'jest-environment-jsdom-global'
testEnvironment: 'jest-environment-jsdom-global',
};
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
],
"main": "dist/index.js",
"scripts": {
"build": "rimraf dist && NODE_ENV=production babel --config-file=./babel.config.js src -d dist",
"build": "rimraf dist && NODE_ENV=production babel --config-file=./babel.config.js src -d dist --extensions '.js,.jsx,.ts,.tsx' && tsc",
"lint": "eslint --config .eslintrc --ext .jsx --ext .js .",
"lint:fix": "eslint --config .eslintrc --ext .jsx --ext .js . --fix",
"test": "jest --config=./jest.config.js",
Expand Down Expand Up @@ -40,10 +40,13 @@
"@babel/plugin-transform-runtime": "~7.9.6",
"@babel/preset-env": "~7.9.6",
"@babel/preset-react": "~7.9.4",
"@babel/preset-typescript": "^7.10.1",
"@babel/register": "~7.9.0",
"@babel/runtime": "~7.9.6",
"@testing-library/jest-dom": "~5.7.0",
"@testing-library/react": "~10.0.4",
"@types/jest": "^26.0.0",
"@types/react": "^16.9.36",
"babel-eslint": "~10.1.0",
"babel-jest": "~26.0.1",
"babel-loader": "~8.1.0",
Expand Down Expand Up @@ -72,6 +75,8 @@
"react-dom": "~16.13.1",
"rimraf": "~3.0.2",
"snyk": "~1.320.1",
"ts-jest": "^26.1.0",
"typescript": "^3.9.5",
"uuid": "~8.0.0"
},
"peerDependencies": {
Expand Down
10 changes: 8 additions & 2 deletions src/Hide.jsx → src/Hide.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import useThreshold from './useThreshold';
import { Threshold } from './ThresholdMap';

export const Hide = props => {
interface HideThresholdProps {
thresholds?: Array<Threshold>;
children: React.ReactNode;
}

export const Hide = (props: HideThresholdProps) => {
const { children, thresholds } = props;
const breakpoints = Array.isArray(thresholds) ? thresholds : [thresholds];
const threshold = useThreshold();
Expand All @@ -15,7 +21,7 @@ Hide.propTypes = {
/** @ignore */
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
/** A single value or an array of values to hide this containers content */
thresholds: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired
thresholds: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
};

export default Hide;
File renamed without changes.
12 changes: 9 additions & 3 deletions src/ResponsiveProvider.jsx → src/ResponsiveProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import ResponsiveContext from './ResponsiveContext';
import { ThresholdMap } from './ThresholdMap';
import defaultThresholdMap from './defaultThresholdMap';

export const ResponsiveProvider = props => {
interface WithThresholdProps {
thresholdMap?: ThresholdMap;
children: React.ReactNode;
}

export const ResponsiveProvider = (props: WithThresholdProps) => {
const { thresholdMap, children } = props;

const getThresholdMap = () => {
Expand All @@ -17,11 +23,11 @@ ResponsiveProvider.propTypes = {
/** The names and values of the responsive breakpoints */
thresholdMap: PropTypes.object,
/** @ignore */
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,

Choose a reason for hiding this comment

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

Shouldn't propTypes be replaced with interfaces?

Copy link
Author

Choose a reason for hiding this comment

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

Technically they are not identical and still have value in non-TS apps. I was trying to change as little functionality as possible in this PR.

};

ResponsiveProvider.defaultProps = {
thresholdMap: defaultThresholdMap
thresholdMap: defaultThresholdMap,
};

export default ResponsiveProvider;
10 changes: 8 additions & 2 deletions src/Show.jsx → src/Show.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import useThreshold from './useThreshold';
import { Threshold } from './ThresholdMap';

export const Show = props => {
interface ShowThresholdProps {
thresholds?: Array<Threshold>;
children: React.ReactNode;
}

export const Show = (props: ShowThresholdProps) => {
const { children, thresholds } = props;
const breakpoints = Array.isArray(thresholds) ? thresholds : [thresholds];
const threshold = useThreshold();
Expand All @@ -15,7 +21,7 @@ Show.propTypes = {
/** @ignore */
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
/** A single value or an array of values to show this containers content */
thresholds: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired
thresholds: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
};

export default Show;
4 changes: 4 additions & 0 deletions src/ThresholdMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Threshold = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export type ThresholdMap = {
[key in Threshold]?: number;
};
8 changes: 6 additions & 2 deletions src/WithResponsiveProps.jsx → src/WithResponsiveProps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import ResponsiveContext from './ResponsiveContext';
import useThreshold from './useThreshold';
import defaultThresholdMap from './defaultThresholdMap';

export interface ResponsivePropsConfig {
propKeys: Array<string>;
}

// This component takes a given property, like size, and based on the current threshold replaces that value
// for example on a mobile phone the value for size would be replaced with the value from xs
// e.g. size={{xs: 'small', 'md': 'large'}}
// const examplePropsConfiguration = {
// propKeys: [ 'size' ],
// }

const WithResponsiveProps = configuration => WrappedComponent => {
const Component = props => {
const WithResponsiveProps = (configuration: ResponsivePropsConfig) => (WrappedComponent: React.ComponentType) => {
const Component = (props: any) => {
const threshold = useThreshold();

const responsiveContext = useContext(ResponsiveContext);
Expand Down
29 changes: 16 additions & 13 deletions src/WithThreshold.jsx → src/WithThreshold.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/destructuring-assignment */
import React from 'react';
import getThreshold from './getThreshold';
import { Threshold } from './ThresholdMap';
import ResponsiveContext from './ResponsiveContext';
import defaultThresholdMap from './defaultThresholdMap';

const withThreshold = () => Component => {
class WithThreshold extends React.Component {
map;
interface WithThresholdProps {
threshold?: Threshold;
}

timeout = 0;
function withThreshold<P extends WithThresholdProps>(Component: React.ComponentType<P>) {
return class extends React.Component<P, { threshold: Threshold }> {
static contextType = ResponsiveContext;
omniverse marked this conversation as resolved.
Show resolved Hide resolved
static displayName = Component.displayName || `WithThreshold${Component.name}`;

constructor(props) {
private map: ThresholdMap = defaultThresholdMap;
private timeout = 0;

constructor(props: P) {
super(props);

this.state = {
Expand Down Expand Up @@ -63,10 +68,8 @@ const withThreshold = () => Component => {

return <Component {...more} />;
}
}
WithThreshold.contextType = ResponsiveContext;

return WithThreshold;
};
};
}

export default withThreshold;
const WithThreshold = () => withThreshold;
export default WithThreshold;
4 changes: 3 additions & 1 deletion src/defaultThresholdMap.js → src/defaultThresholdMap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const defaultThresholdMap = () => {
import { ThresholdMap } from './ThresholdMap';

const defaultThresholdMap = (): ThresholdMap => {
return {
xs: 0,
sm: 480,
Expand Down
6 changes: 4 additions & 2 deletions src/getThreshold.js → src/getThreshold.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const getThreshold = (width = 0, breakpoints = { xs: 0 }) => {
const breakpointKeys = Object.keys(breakpoints);
import { Threshold, ThresholdMap } from './ThresholdMap';

const getThreshold = (width: number = 0, breakpoints: ThresholdMap = { xs: 0 }) => {
const breakpointKeys = Object.keys(breakpoints) as Array<Threshold>;

let result = breakpointKeys[0];

Expand Down
17 changes: 13 additions & 4 deletions src/responsivePropBuilder.js → src/responsivePropBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Threshold } from './ThresholdMap';
import defaultThresholdMap from './defaultThresholdMap';

const getCurrentValue = value => (value !== undefined ? value : null);
const getCurrentValue = (value: any) => (value !== undefined ? value : null);

const responsivePropBuilder = (currentThreshold, props, configuration, thresholdMap = defaultThresholdMap) => {
export interface GenericProps {
[key: string]: any;
}

export interface ResponsivePropsConfig {
propKeys: Array<string>;

Choose a reason for hiding this comment

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

Array<T> is used a couple times in this PR, but it's generally better to use T[].

}

const responsivePropBuilder = (currentThreshold: Threshold, props: GenericProps, configuration: ResponsivePropsConfig, thresholdMap = defaultThresholdMap) => {
// get the keys from the map, e.g. ['xs', 'sm', 'md', 'lg', 'xl']
const thresholdKeys = Object.keys(thresholdMap);

Expand All @@ -16,10 +25,10 @@ const responsivePropBuilder = (currentThreshold, props, configuration, threshold

// only an object can contain responsive values, null is an object also but that's not valid
// e.g. size={{xs: 'h4', md: 'h3'}}
const propKeys = configuration.propKeys.filter(propKey => typeof props[propKey] === 'object' && props[propKey] !== null);
const propKeys = configuration.propKeys.filter((propKey: string) => typeof props[propKey] === 'object' && props[propKey] !== null);

// loop through the props that have been found as being responsive and extract an object of name/value pairs
const translatedValues = propKeys.reduce((acc, propKey) => {
const translatedValues = propKeys.reduce((acc: any, propKey: string) => {
let result = null;
// find the first threshold with a value. That is our value because we reversed them above starting at the current threshold and moving to smaller thresholds
for (let i = 0; i < thresholds.length; i++) {
Expand Down
3 changes: 2 additions & 1 deletion src/useThreshold.js → src/useThreshold.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* eslint-disable import/no-named-as-default */
import { useState, useEffect, useContext } from 'react';
import getThreshold from './getThreshold';
import { ThresholdMap } from './ThresholdMap';
import ResponsiveContext from './ResponsiveContext';
import defaultThresholdMap from './defaultThresholdMap';

const getCurrentThreshold = map => getThreshold(window.innerWidth, map);
const getCurrentThreshold = (map: ThresholdMap) => getThreshold(window.innerWidth, map);

function useThreshold() {
const responsiveContext = useContext(ResponsiveContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const breakpoints = {
sm: 480,
md: 768,
lg: 960,
xl: 1280
xl: 1280,
};

beforeEach(() => {
Expand Down
28 changes: 28 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"outDir": "lib",
"target": "es5",
"module": "commonjs",
"jsx": "react",
"allowJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"sourceMap": true,
"importHelpers": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "Node",
"noImplicitThis": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"skipLibCheck": true,
"lib": ["es5", "dom"],
"types": ["node", "jest"],
"noUnusedLocals": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"preserveSymlinks": true,
"baseUrl": ".",
"outDir": "./dist"

Choose a reason for hiding this comment

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

Does this build to both common-js and es-modules?

Copy link
Author

Choose a reason for hiding this comment

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

only cjs

},
"include": ["src"],
}