feat(client): ts-migrate client/src/components/helpers/** (#42593)

* refactor(client): convert toggle-button to TypeScript

* chore: rename Space to tsx

* refactor(client): convert space to TypeScript

* chore: rename SlimWidthRow to tsx

* refactor(client): slim-width-row to TypeScript

* chore: rename SkeletonSprite to ts

* fix: fixed typos and resolved paths

* chore: resolve path inconsistencies

* refactor(client): skelton-sprite to TypeScript

* chore: rename loader.test to tsx

* chore: add types for react-spinkit

* refactor(client): loader to TypeScript

* refactor(client): link to TypeScript

* refactor(client): image-loader to TypeScript

* refactor(client): full-width-row to TypeScript

* refactor(client): current-challenge-link to TypeScript

* refactor(client): button to TypeScript

* refactor(client): border-color-picker to TypeScript

* refactor(client): avatar-renderer to TypeScript

* chore: changed loadertest(snap) to ts

* chore: optional types added and cleaned files

* fix: args are now optional

* push small updates for Spacer component merge

* update snapshot

* remove type defs from deps

* Revert "remove type defs from deps"

This reverts commit 9f58bf3554.

* correctly remove client type deps

* final push to remove from deps

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
Marlon Johnson
2021-06-25 07:23:52 -07:00
committed by Mrugesh Mohapatra
parent 660c3b3440
commit 4b44bb37d9
36 changed files with 173 additions and 140 deletions

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { isEmpty } from 'lodash-es';
import Loader from '../components/helpers/Loader';
import Loader from '../components/helpers/loader';
import {
userByNameSelector,
userProfileFetchStateSelector,

View File

@ -5,7 +5,7 @@ import Helmet from 'react-helmet';
import { useTranslation } from 'react-i18next';
import envData from '../../../config/env.json';
import FullWidthRow from '../components/helpers/FullWidthRow';
import FullWidthRow from '../components/helpers/full-width-row';
import { Spacer } from '../components/helpers';
const { apiLocation } = envData;

View File

@ -20,7 +20,7 @@ import {
defaultDonation,
modalDefaultDonation
} from '../../../../config/donation-settings';
import Spacer from '../helpers/Spacer';
import Spacer from '../helpers/spacer';
import PaypalButton from './PaypalButton';
import DonateCompletion from './DonateCompletion';
import {

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import Link from '../helpers/Link';
import Link from '../helpers/link';
import './footer.css';
const propTypes = {

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Link, Spacer, Loader, FullWidthRow } from '../helpers';
import { randomQuote } from '../../utils/get-words';
import CurrentChallengeLink from '../helpers/CurrentChallengeLink';
import CurrentChallengeLink from '../helpers/current-challenge-link';
import IntroDescription from './components/IntroDescription';
import { Trans, useTranslation } from 'react-i18next';

View File

@ -1,22 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const styles = { padding: '15px 0', height: '1px' };
const Comp = props => <div className='spacer' style={styles} {...props} />;
const Spacer = ({ size = 1 }) =>
size === 1 ? (
<Comp />
) : (
'#'
.repeat(size)
.split('')
.map((_, i) => <Comp key={`spacer_${i}`} />)
);
Spacer.propTypes = {
size: PropTypes.number
};
export default Spacer;

View File

@ -1,22 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Image } from '@freecodecamp/react-bootstrap';
import DefaultAvatar from '../../assets/icons/DefaultAvatar';
import { defaultUserImage } from '../../../../config/misc';
import { borderColorPicker } from '../helpers';
import { borderColorPicker } from '.';
import { useTranslation } from 'react-i18next';
const propTypes = {
isDonating: PropTypes.bool,
isTopContributor: PropTypes.bool,
picture: PropTypes.any.isRequired,
userName: PropTypes.string.isRequired
};
interface AvatarRendererProps {
isDonating?: boolean;
isTopContributor?: boolean;
picture: string;
userName: string;
}
function AvatarRenderer({ picture, userName, isDonating, isTopContributor }) {
function AvatarRenderer({
picture,
userName,
isDonating,
isTopContributor
}: AvatarRendererProps): JSX.Element {
const { t } = useTranslation();
let borderColor = borderColorPicker(isDonating, isTopContributor);
let isPlaceHolderImage =
const borderColor: string = borderColorPicker(isDonating, isTopContributor);
const isPlaceHolderImage =
/example.com|identicon.org/.test(picture) || picture === defaultUserImage;
return (
@ -35,6 +39,5 @@ function AvatarRenderer({ picture, userName, isDonating, isTopContributor }) {
);
}
AvatarRenderer.propTypes = propTypes;
AvatarRenderer.displayName = 'AvatarRenderer';
export default AvatarRenderer;

View File

@ -1,4 +1,7 @@
export default function borderColorPicker(isDonating, isTopContributor) {
export default function borderColorPicker(
isDonating?: boolean,
isTopContributor?: boolean
): string {
if (isDonating && isTopContributor) return 'purple-border';
else if (isTopContributor) return 'blue-border';
else if (isDonating) return 'gold-border';

View File

@ -1,6 +1,6 @@
import React from 'react';
function ButtonSpacer() {
function ButtonSpacer(): JSX.Element {
return <div className='button-spacer' style={{ padding: '5px 0' }} />;
}

View File

@ -1,18 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import envData from '../../../../config/env.json';
const { apiLocation } = envData;
interface EnvData {
apiLocation: string;
}
const { apiLocation } = envData as EnvData;
const currentChallengeApi = '/challenges/current-challenge';
const propTypes = {
children: PropTypes.any,
isLargeBtn: PropTypes.bool
};
function CurrentChallengeLink({ children, isLargeBtn }) {
function CurrentChallengeLink({
children,
isLargeBtn
}: {
children?: JSX.ElementChildrenAttribute;
isLargeBtn?: boolean;
}): JSX.Element {
let classNames;
if (isLargeBtn) {
classNames = 'btn btn-lg btn-primary btn-block';
@ -27,6 +31,5 @@ function CurrentChallengeLink({ children, isLargeBtn }) {
}
CurrentChallengeLink.displayName = 'CurrentChallengeLink';
CurrentChallengeLink.propTypes = propTypes;
export default CurrentChallengeLink;

View File

@ -1,8 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Row, Col } from '@freecodecamp/react-bootstrap';
function FullWidthRow({ children, className }) {
function FullWidthRow({
children,
className
}: {
children?: JSX.ElementChildrenAttribute;
className?: string;
}): JSX.Element {
return (
<Row className={className}>
<Col sm={8} smOffset={2} xs={12}>
@ -13,9 +18,5 @@ function FullWidthRow({ children, className }) {
}
FullWidthRow.displayName = 'FullWidthRow';
FullWidthRow.propTypes = {
children: PropTypes.any,
className: PropTypes.string
};
export default FullWidthRow;

View File

@ -1,20 +1,21 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React, { useState } from 'react';
import './image-loader.css';
// @ts-ignore
import LazyLoad from 'react-lazy-load';
import PropTypes from 'prop-types';
const propTypes = {
alt: PropTypes.string,
className: PropTypes.string,
height: PropTypes.number,
loadedClassName: PropTypes.string,
loadingClassName: PropTypes.string,
offsetVertical: PropTypes.number,
src: PropTypes.string,
style: PropTypes.object,
width: PropTypes.number
};
interface ImageLoaderProps {
alt?: string;
className?: string;
height?: number;
loadedClassName?: string;
loadingClassName?: string;
offsetVertical?: number;
src?: string;
style?: React.CSSProperties;
width?: number;
}
const ImageLoader = ({
className = '',
@ -26,7 +27,7 @@ const ImageLoader = ({
style,
width,
height
}) => {
}: ImageLoaderProps): JSX.Element => {
const [loaded, setLoaded] = useState(false);
const fullClassName = `${className} ${
loaded ? loadedClassName : loadingClassName
@ -49,5 +50,5 @@ const ImageLoader = ({
</LazyLoad>
);
};
ImageLoader.propTypes = propTypes;
export default ImageLoader;

View File

@ -1,11 +1,11 @@
export { default as ButtonSpacer } from './ButtonSpacer';
export { default as FullWidthRow } from './FullWidthRow';
export { default as SlimWidthRow } from './SlimWidthRow';
export { default as Loader } from './Loader';
export { default as SkeletonSprite } from './SkeletonSprite';
export { default as Spacer } from './Spacer';
export { default as Link } from './Link';
export { default as CurrentChallengeLink } from './CurrentChallengeLink';
export { default as ImageLoader } from './ImageLoader';
export { default as AvatarRenderer } from './AvatarRenderer';
export { default as borderColorPicker } from './borderColorPicker';
export { default as ButtonSpacer } from './button-spacer';
export { default as FullWidthRow } from './full-width-row';
export { default as SlimWidthRow } from './slim-width-row';
export { default as Loader } from './loader';
export { default as SkeletonSprite } from './skeleton-sprite';
export { default as Spacer } from './spacer';
export { default as Link } from './link';
export { default as CurrentChallengeLink } from './current-challenge-link';
export { default as ImageLoader } from './image-loader';
export { default as AvatarRenderer } from './avatar-renderer';
export { default as borderColorPicker } from './border-color-picker';

View File

@ -1,13 +1,15 @@
import React from 'react';
import renderer from 'react-test-renderer';
import renderer, { ReactTestRendererJSON } from 'react-test-renderer';
import Link from './Link';
import Link from './link';
describe('<Link />', () => {
const externalLink = renderer
.create(<Link external={true} to='/home' />)
.toJSON();
const gatsbyLink = renderer.create(<Link to='/home' />).toJSON();
.toJSON() as ReactTestRendererJSON;
const gatsbyLink = renderer
.create(<Link to='/home' />)
.toJSON() as ReactTestRendererJSON;
it('renders to the DOM', () => {
expect(gatsbyLink).toBeTruthy();

View File

@ -1,15 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link as GatsbyLink } from 'gatsby';
const propTypes = {
children: PropTypes.any,
external: PropTypes.bool,
sameTab: PropTypes.bool,
to: PropTypes.string.isRequired
};
interface LinkProps {
children?: JSX.ElementChildrenAttribute;
external?: boolean;
sameTab?: boolean;
to: string;
}
const Link = ({ children, to, external, sameTab, ...other }) => {
const Link = ({
children,
to,
external,
sameTab,
...other
}: LinkProps): JSX.Element => {
if (!external && /^\/(?!\/)/.test(to)) {
return (
<GatsbyLink to={to} {...other}>
@ -30,6 +35,5 @@ const Link = ({ children, to, external, sameTab, ...other }) => {
</a>
);
};
Link.propTypes = propTypes;
export default Link;

View File

@ -1,7 +1,7 @@
import React from 'react';
import { render, cleanup } from '@testing-library/react';
import Loader from './Loader';
import Loader from './loader';
describe('<Loader />', () => {
afterEach(cleanup);

View File

@ -1,13 +1,16 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Spinner from 'react-spinkit';
import './loader.css';
function Loader({ fullScreen, timeout }) {
interface LoaderProps {
fullScreen?: boolean;
timeout?: number;
}
function Loader({ fullScreen, timeout }: LoaderProps): JSX.Element {
const [showSpinner, setShowSpinner] = useState(!timeout);
useEffect(() => {
let timerId;
let timerId: ReturnType<typeof setTimeout>;
if (!showSpinner) {
timerId = setTimeout(() => setShowSpinner(true), timeout);
}
@ -21,9 +24,5 @@ function Loader({ fullScreen, timeout }) {
}
Loader.displayName = 'Loader';
Loader.propTypes = {
fullScreen: PropTypes.bool,
timeout: PropTypes.number
};
export default Loader;

View File

@ -1,8 +1,11 @@
import React from 'react';
import styles from './skeletonStyles';
import styles from './skeleton-styles';
function SkeletonSprite() {
// TODO: unsure about parameter typing
function SkeletonSprite({}: React.FC<
React.ComponentPropsWithoutRef<'svg'>
>): JSX.Element {
return (
<div className='sprite-container'>
<style dangerouslySetInnerHTML={{ __html: styles }} />

View File

@ -1,8 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Row, Col } from '@freecodecamp/react-bootstrap';
function SlimWidthRow({ children, ...restProps }) {
interface SlimWidthRowProps {
children: JSX.ElementChildrenAttribute;
}
function SlimWidthRow({
children,
...restProps
}: SlimWidthRowProps): JSX.Element {
return (
<Row {...restProps}>
<Col md={6} mdOffset={3} sm={12}>
@ -13,8 +18,5 @@ function SlimWidthRow({ children, ...restProps }) {
}
SlimWidthRow.displayName = 'SlimWidthRow';
SlimWidthRow.propTypes = {
children: PropTypes.any
};
export default SlimWidthRow;

View File

@ -0,0 +1,24 @@
import React from 'react';
interface SpacerProps {
size: number;
}
const styles = { padding: '15px 0', height: '1px' };
const Comp = ({ ...props }): JSX.Element => (
<div className='spacer' style={styles} {...props} />
);
const Spacer = ({ size = 1 }: SpacerProps): JSX.Element =>
size === 1 ? (
<Comp />
) : (
<>
{Array.from(Array(size), (_, i) => (
<Comp key={`spacer_${i}`} />
))}
</>
);
export default Spacer;

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import {
ToggleButtonGroup as BSBG,
ToggleButton as TB
@ -9,15 +8,18 @@ import './toggle-button.css';
import ToggleCheck from '../../assets/icons/ToggleCheck';
import Spacer from '../../assets/icons/Spacer';
const propTypes = {
name: PropTypes.string.isRequired,
offLabel: PropTypes.string,
onChange: PropTypes.func.isRequired,
onLabel: PropTypes.string,
value: PropTypes.bool.isRequired
};
interface ButtonProps {
name: string;
offLabel: string;
onChange: (value: string) => void;
onLabel: string;
value: boolean;
condition: boolean;
}
function getActiveClass(condition) {
type ActiveClass = Pick<ButtonProps, 'condition'>;
function getActiveClass(condition: ActiveClass | unknown) {
return condition ? 'active' : 'not-active';
}
@ -27,7 +29,7 @@ export default function ToggleButton({
value,
onLabel = 'On',
offLabel = 'Off'
}) {
}: ButtonProps): JSX.Element {
const checkIconStyle = {
height: '15px',
width: '20px'
@ -71,4 +73,3 @@ export default function ToggleButton({
}
ToggleButton.displayName = 'ToggleButton';
ToggleButton.propTypes = propTypes;

View File

@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Grid, Row } from '@freecodecamp/react-bootstrap';
import Helmet from 'react-helmet';
import Link from '../helpers/Link';
import Link from '../helpers/link';
import { useTranslation } from 'react-i18next';
import { CurrentChallengeLink, FullWidthRow, Spacer } from '../helpers';

View File

@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
import { AvatarRenderer } from '../../helpers';
import SocialIcons from './SocialIcons';
import Link from '../../helpers/Link';
import Link from '../../helpers/link';
import './camper.css';

View File

@ -9,8 +9,8 @@ import startOfDay from 'date-fns/startOfDay';
import isEqual from 'date-fns/isEqual';
import { useTranslation } from 'react-i18next';
import FullWidthRow from '../../helpers/FullWidthRow';
import Spacer from '../../helpers/Spacer';
import FullWidthRow from '../../helpers/full-width-row';
import Spacer from '../../helpers/spacer';
import '@freecodecamp/react-calendar-heatmap/dist/styles.css';
import './heatmap.css';

View File

@ -17,8 +17,8 @@ import { Trans, withTranslation } from 'react-i18next';
import { updateMyEmail } from '../../redux/settings';
import { maybeEmailRE } from '../../utils';
import FullWidthRow from '../helpers/FullWidthRow';
import Spacer from '../helpers/Spacer';
import FullWidthRow from '../helpers/full-width-row';
import Spacer from '../helpers/spacer';
import SectionHeader from './SectionHeader';
import BlockSaveButton from '../helpers/form/BlockSaveButton';
import ToggleSetting from './ToggleSetting';

View File

@ -9,8 +9,8 @@ import { withTranslation } from 'react-i18next';
import { userSelector } from '../../redux';
import { submitProfileUI } from '../../redux/settings';
import FullWidthRow from '../helpers/FullWidthRow';
import Spacer from '../helpers/Spacer';
import FullWidthRow from '../helpers/full-width-row';
import Spacer from '../helpers/spacer';
import ToggleSetting from './ToggleSetting';
import SectionHeader from './SectionHeader';

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import FullWidthRow from '../helpers/FullWidthRow';
import FullWidthRow from '../helpers/full-width-row';
const propTypes = {
children: PropTypes.oneOfType([

View File

@ -6,7 +6,7 @@ import {
HelpBlock
} from '@freecodecamp/react-bootstrap';
import TB from '../helpers/ToggleButton';
import TB from '../helpers/toggle-button';
import { ButtonSpacer } from '../helpers';
import './toggle-setting.css';

View File

@ -17,7 +17,7 @@ import {
submitNewUsername
} from '../../redux/settings';
import BlockSaveButton from '../helpers/form/BlockSaveButton';
import FullWidthRow from '../helpers/FullWidthRow';
import FullWidthRow from '../helpers/full-width-row';
import { isValidUsername } from '../../../../utils/validate';
const propTypes = {

View File

@ -29,7 +29,7 @@ import CompletionModal from '../../components/CompletionModal';
import HelpModal from '../../components/HelpModal';
import ProjectToolPanel from '../Tool-Panel';
import SolutionForm from '../SolutionForm';
import Spacer from '../../../../components/helpers/Spacer';
import Spacer from '../../../../components/helpers/spacer';
import { ChallengeNode } from '../../../../redux/prop-types';
import { isSignedInSelector } from '../../../../redux';
import Hotkeys from '../../components/Hotkeys';

View File

@ -22,7 +22,7 @@ import { getGuideUrl } from '../../utils';
import LearnLayout from '../../../../components/layouts/Learn';
import ChallengeTitle from '../../components/Challenge-Title';
import ChallengeDescription from '../../components/Challenge-Description';
import Spacer from '../../../../components/helpers/Spacer';
import Spacer from '../../../../components/helpers/spacer';
import SolutionForm from '../SolutionForm';
import ProjectToolPanel from '../Tool-Panel';
import CompletionModal from '../../components/CompletionModal';

View File

@ -17,10 +17,10 @@ import { ChallengeNode } from '../../../redux/prop-types';
import LearnLayout from '../../../components/layouts/Learn';
import ChallengeTitle from '../components/Challenge-Title';
import ChallengeDescription from '../components/Challenge-Description';
import Spacer from '../../../components/helpers/Spacer';
import Spacer from '../../../components/helpers/spacer';
import CompletionModal from '../components/CompletionModal';
import Hotkeys from '../components/Hotkeys';
import Loader from '../../../components/helpers/Loader';
import Loader from '../../../components/helpers/loader';
import {
isChallengeCompletedSelector,
challengeMounted,

View File

@ -6,8 +6,8 @@ import { Grid, ListGroup, ListGroupItem } from '@freecodecamp/react-bootstrap';
import { useTranslation } from 'react-i18next';
import LearnLayout from '../../components/layouts/Learn';
import FullWidthRow from '../../components/helpers/FullWidthRow';
import ButtonSpacer from '../../components/helpers/ButtonSpacer';
import FullWidthRow from '../../components/helpers/full-width-row';
import ButtonSpacer from '../../components/helpers/button-spacer';
import { MarkdownRemark, AllChallengeNode } from '../../redux/prop-types';
import './intro.css';

View File

@ -1,5 +1,10 @@
{
"include": ["./i18n/**/*", "./plugins/**/*","./src/**/*","./utils/**/*"],
"include": [
"./i18n/**/*",
"./plugins/**/*",
"./src/**/*",
"./utils/**/*"
],
"compilerOptions": {
"target": "es2020",
"module": "es2020",
@ -13,6 +18,10 @@
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"types": ["node", "jest", "@testing-library/jest-dom"]
"types": [
"node",
"jest",
"@testing-library/jest-dom"
]
}
}
}