Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
26 changes: 14 additions & 12 deletions src/WithThreshold.jsx → src/WithThreshold.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
/* 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;
}

function withThreshold<P extends WithThresholdProps>(Component: React.ComponentType<P>) {
return class extends React.Component<P, { threshold: Threshold }> {
static contextType = ResponsiveContext;

map = {};
timeout = 0;

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

this.state = {
Expand Down Expand Up @@ -63,10 +67,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"],
}