Skip to content

Commit 6a508f8

Browse files
authored
Add typescript (#34)
1 parent 68d17ec commit 6a508f8

35 files changed

+471
-183
lines changed

.eslintrc.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
module.exports = {
2+
parser: '@typescript-eslint/parser',
23
env: {
34
browser: true,
45
es2021: true,
56
node: true,
67
jest: true,
78
},
8-
extends: ['eslint:recommended', 'plugin:react/recommended', 'prettier'],
9+
extends: [
10+
'next',
11+
'eslint:recommended',
12+
'plugin:react/recommended',
13+
'prettier',
14+
'plugin:@typescript-eslint/recommended',
15+
],
916
parserOptions: {
1017
ecmaFeatures: {
1118
jsx: true,
1219
},
1320
ecmaVersion: 12,
1421
sourceType: 'module',
1522
},
16-
plugins: ['react', 'prettier'],
23+
plugins: ['react', 'prettier', '@typescript-eslint'],
1724
rules: {
1825
'prettier/prettier': 'error',
1926
'react/no-unescaped-entities': ['error', { forbid: ['>', '}'] }],
2027
'react/prop-types': 'off',
28+
'@typescript-eslint/no-empty-function': 'off',
2129
},
2230
settings: {
2331
react: {

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ yarn-error.log*
3232

3333
# vercel
3434
.vercel
35+
36+
# typescript (tsc)
37+
tsconfig.tsbuildinfo

components/button.js renamed to components/button.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
/* eslint-disable react/prop-types */
2-
import React from 'react';
2+
import React, { MouseEventHandler } from 'react';
33
import { useTheme } from '../hooks/theme/index';
4+
import { ReactChildrenProps } from '../types';
5+
6+
type ButtonProps = {
7+
onClick?: MouseEventHandler;
8+
attrs?: Record<string, string> & { type?: 'button' | 'submit' };
9+
size?: 'small' | 'large';
10+
};
411

512
export default function Button({
613
children,
714
onClick = () => {},
815
attrs = {},
916
size = 'large',
10-
}) {
17+
}: ReactChildrenProps & ButtonProps) {
1118
const [theme] = useTheme();
1219
const { buttonTextColor, buttonBgColor } = theme;
1320

components/content/section.js

-10
This file was deleted.

components/content/section.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import { ReactChildrenProps } from '../../types';
3+
4+
export function SectionHeading({ children }: ReactChildrenProps) {
5+
return <h3 className="text-3xl text-gray-800 font-bold mb-4">{children}</h3>;
6+
}
7+
8+
export function SectionParagraph({ children }: ReactChildrenProps) {
9+
return <p className="text-2xl text-gray-700">{children}</p>;
10+
}

components/link.js renamed to components/link.tsx

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable react/prop-types */
2-
import React from 'react';
2+
import React, { FunctionComponent } from 'react';
33
import { default as NextLink } from 'next/link';
44
import { useTheme } from '../hooks/theme/index';
55

@@ -8,18 +8,27 @@ const underlineClassNames = {
88
md: 'underline-offset-md',
99
};
1010

11-
export default function Link({
11+
type LinkProps = {
12+
attrs?: { [attrName: string]: string };
13+
href: string;
14+
underline?: boolean;
15+
underlineOffset?: keyof typeof underlineClassNames;
16+
};
17+
18+
const Link: FunctionComponent<LinkProps> = ({
1219
attrs,
1320
href,
1421
underline,
1522
underlineOffset,
1623
children,
17-
}) {
24+
}) => {
1825
const [theme] = useTheme();
1926

2027
attrs = attrs ?? {};
2128
underline = underline ?? true;
2229
href = href ?? attrs.href;
30+
const underlineOffsetClassName =
31+
underlineClassNames[underlineOffset] ?? underlineClassNames.sm;
2332

2433
const classNames = (attrs.className ?? '').split(' ');
2534

@@ -31,8 +40,6 @@ export default function Link({
3140

3241
if (underline) {
3342
classNames.push('underline');
34-
const underlineOffsetClassName =
35-
underlineClassNames[underlineOffset] ?? underlineClassNames.sm;
3643
classNames.push(underlineOffsetClassName);
3744
}
3845

@@ -45,4 +52,6 @@ export default function Link({
4552
</a>
4653
</NextLink>
4754
);
48-
}
55+
};
56+
57+
export default Link;

components/modal.js renamed to components/modal.tsx

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
import React from 'react';
1+
import React, { KeyboardEventHandler } from 'react';
22
import FocusLock from 'react-focus-lock';
3+
import { ReactChildrenProps } from '../types';
34

4-
// eslint-disable-next-line react/prop-types
5-
export default function Modal({ children, onClose }) {
5+
type ModalProps = {
6+
onClose: () => void;
7+
};
8+
9+
export default function Modal({
10+
children,
11+
onClose,
12+
}: ReactChildrenProps & ModalProps) {
613
const classNames = [
714
'fixed',
815
'top-0',
@@ -17,7 +24,7 @@ export default function Modal({ children, onClose }) {
1724
'bg-filter-blur-modal',
1825
];
1926

20-
const onKeyDown = (e) => {
27+
const onKeyDown: KeyboardEventHandler = (e) => {
2128
const ESCAPE = 27;
2229

2330
if (e.keyCode === ESCAPE) {

components/rhythm-edit/rhythm-edit.test.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ function setup({ rhythm } = {}) {
1616
cleanup();
1717

1818
rhythm = {
19-
id: 'some-rhythm-id',
2019
action: 'I want to run',
2120
frequency: [2, 3],
2221
reason: 'I want to train for a marathon',
@@ -36,13 +35,14 @@ function setup({ rhythm } = {}) {
3635
denominatorSelect = screen.getByLabelText('Rhythm action count time span');
3736
reasonInput = screen.getByDisplayValue('I want to train for a marathon');
3837

39-
submitButton = rhythm.id
40-
? screen.getByRole('button', { name: 'Update' })
41-
: screen.getByRole('button', { name: 'Create' });
38+
submitButton =
39+
'id' in rhythm
40+
? screen.getByRole('button', { name: 'Update' })
41+
: screen.getByRole('button', { name: 'Create' });
4242
}
4343

4444
beforeEach(() => {
45-
setup();
45+
setup({ rhythm: { id: 'some-rhythm-id' } });
4646
});
4747

4848
test("it renders the rhythm's editable properties", () => {
@@ -90,7 +90,7 @@ test('it evens out equal numerator and denominator values to once every day', ()
9090

9191
describe('submit button label', () => {
9292
it('shows a button with a label of "Create" when the rhythm model ** does not ** have an id', () => {
93-
setup({ rhythm: { id: undefined } });
93+
setup({ rhythm: {} });
9494
expect(submitButton).toHaveTextContent('Create');
9595
expect(submitButton).toBeInTheDocument();
9696
});

components/rhythm-edit/rhythm-edit.js renamed to components/rhythm-edit/rhythm-edit.tsx

+35-19
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
/* eslint-disable react/prop-types */
2-
3-
import React, { useState } from 'react';
1+
import React, { ChangeEventHandler, FormEventHandler, useState } from 'react';
42
import Button from '../button';
53
import { denominatorTerm } from '../../utils/denominator-term';
64
import { numeratorTerm } from '../../utils/numerator-term';
75
import { ValidationWrapper } from './validated-wrapper';
6+
import { Rhythm, UnsavedRhythm } from '../../types';
87

98
const sharedSelectAndInputClassNames = [
109
'text-gray-800',
@@ -28,7 +27,10 @@ const selectClassNames = [
2827
'w-48',
2928
].join(' ');
3029

31-
const generateNumeratorSelect = (selected, onChange) => {
30+
const generateNumeratorSelect = (
31+
selected: string,
32+
onChange: ChangeEventHandler<HTMLSelectElement>
33+
) => {
3234
const options = [1, 2, 3, 4, 5, 6, 7].map((number) => {
3335
return (
3436
<option key={number} value={number}>
@@ -49,7 +51,10 @@ const generateNumeratorSelect = (selected, onChange) => {
4951
);
5052
};
5153

52-
const generateDenominatorSelect = (selected, onChange) => {
54+
const generateDenominatorSelect = (
55+
selected: string,
56+
onChange: ChangeEventHandler<HTMLSelectElement>
57+
) => {
5358
const options = [1, 2, 3, 4, 5, 6, 7].map((number) => {
5459
return (
5560
<option key={number} value={number}>
@@ -70,7 +75,7 @@ const generateDenominatorSelect = (selected, onChange) => {
7075
);
7176
};
7277

73-
function validate(rhythm) {
78+
function validate(rhythm: Rhythm | UnsavedRhythm) {
7479
const [numerator, denominator] = rhythm.frequency;
7580

7681
const result = {
@@ -104,7 +109,15 @@ function validate(rhythm) {
104109
return result;
105110
}
106111

107-
export default function RhythmEdit({ rhythm, onClose, onSubmit }) {
112+
export default function RhythmEdit({
113+
rhythm,
114+
onClose,
115+
onSubmit,
116+
}: {
117+
rhythm: Rhythm | UnsavedRhythm;
118+
onClose: () => void;
119+
onSubmit: (rhythm: Rhythm | UnsavedRhythm) => void;
120+
}) {
108121
const [rhythmAction, setRhythmAction] = useState(rhythm.action);
109122
const [rhythmFrequency, setRhythmFrequency] = useState(rhythm.frequency);
110123
const [rhythmNumerator, rhythmDenominator] = rhythmFrequency;
@@ -120,7 +133,7 @@ export default function RhythmEdit({ rhythm, onClose, onSubmit }) {
120133

121134
const validationResult = validate(editedRhythm);
122135

123-
const submitHandler = (e) => {
136+
const submitHandler: FormEventHandler = (e) => {
124137
e.preventDefault();
125138
setHasSubmitted(true);
126139

@@ -129,20 +142,23 @@ export default function RhythmEdit({ rhythm, onClose, onSubmit }) {
129142
}
130143
};
131144

132-
const numeratorSelect = generateNumeratorSelect(rhythmNumerator, (e) => {
133-
let numeratorValue = Number(e.target.value);
134-
let denominatorValue = rhythmDenominator;
145+
const numeratorSelect = generateNumeratorSelect(
146+
rhythmNumerator.toString(),
147+
(e) => {
148+
let numeratorValue = Number(e.target.value);
149+
let denominatorValue = rhythmDenominator;
135150

136-
if (numeratorValue === denominatorValue) {
137-
numeratorValue = 1;
138-
denominatorValue = 1;
139-
}
151+
if (numeratorValue === denominatorValue) {
152+
numeratorValue = 1;
153+
denominatorValue = 1;
154+
}
140155

141-
setRhythmFrequency([numeratorValue, denominatorValue]);
142-
});
156+
setRhythmFrequency([numeratorValue, denominatorValue]);
157+
}
158+
);
143159

144160
const denominatorSelect = generateDenominatorSelect(
145-
rhythmDenominator,
161+
rhythmDenominator.toString(),
146162
(e) => {
147163
let numeratorValue = rhythmNumerator;
148164
let denominatorValue = Number(e.target.value);
@@ -210,7 +226,7 @@ export default function RhythmEdit({ rhythm, onClose, onSubmit }) {
210226
</ValidationWrapper>
211227
</div>
212228
<Button attrs={{ type: 'submit', className: '' }}>
213-
{rhythm.id ? 'Update' : 'Create'}
229+
{'id' in rhythm && rhythm ? 'Update' : 'Create'}
214230
</Button>
215231
</div>
216232
</form>

components/rhythm-edit/validated-wrapper.js

-45
This file was deleted.

0 commit comments

Comments
 (0)