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

Refactor: Convert into TypeScript #23

Merged
merged 34 commits into from
Aug 29, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
87d1eb8
feat: First pass at a TS conversion
rschristian Aug 22, 2020
47ed0ea
fix: Had to break into separate commits to keep history
rschristian Aug 22, 2020
e488b03
fix: Correcting Jest with TS
rschristian Aug 23, 2020
7b0b93e
refactor: Move all type conversions into `conversions.ts`
rschristian Aug 23, 2020
0248b2b
fix(interactive): Hasty typing fix broke Interactive
rschristian Aug 23, 2020
ab81376
fix(types): Correcting all props to be optional
rschristian Aug 23, 2020
d127b38
refactor: Working on PR comments, cleaning up types
rschristian Aug 23, 2020
e8d867c
refactor: Further cleaning `utils` directory
rschristian Aug 23, 2020
115629c
fix: Correcting bad IDE suggestion to aviod null
rschristian Aug 23, 2020
e7afe16
test(util): Update util tests with correct import paths
rschristian Aug 23, 2020
971276b
fix: Resolving issue with incorrect Hex default color
rschristian Aug 23, 2020
42bd359
refactor(deps): Downgrading TS version to stay within tooling range
rschristian Aug 23, 2020
459d7dd
fix: Ensuring all components' props are optional
rschristian Aug 23, 2020
717d067
refactor(types): Solidified more of the types, some still left to do
rschristian Aug 23, 2020
432d871
fix(merge): Merging in hotfix/optionalProps
rschristian Aug 23, 2020
2bc692b
Merge branch 'master' into master
rschristian Aug 23, 2020
250edf4
refactor: Reworked build system
rschristian Aug 24, 2020
3572b53
fix: Resolving issues with size-limit paths
rschristian Aug 24, 2020
ad7ff69
fix(merge): Merging in refactored build process
rschristian Aug 24, 2020
91218d7
refactor: `Any` Crusade 2: Electric Boogaloo
rschristian Aug 24, 2020
0296be7
style: Silencing TS lint return type warning in JS files
rschristian Aug 24, 2020
14eec5e
fix(types): Correcting built types paths in modules
rschristian Aug 24, 2020
581ae52
refactor(pr): Working on PR comments
rschristian Aug 24, 2020
ba6864d
fix: Making `HexInput`'s props optional
rschristian Aug 24, 2020
1d70034
refactor(merge): Merging in #26
rschristian Aug 24, 2020
285dd5b
misc(feedback): Need feedback on this behavior
rschristian Aug 24, 2020
9a310e9
refactor(types): Mostly finalized types
rschristian Aug 25, 2020
0244e57
refactor: Removing spread operator from HexInput
rschristian Aug 25, 2020
50c5ba5
fix: Corrected HexInput
rschristian Aug 25, 2020
5268d9e
fix(props): Making props optional again after HOC removal
rschristian Aug 25, 2020
7c4a395
refactor: Moved packages, updated build process
rschristian Aug 26, 2020
7914786
Merge remote-tracking branch 'upstream/master' into master
rschristian Aug 26, 2020
427b210
revert: Going back to CommonJS for build script
rschristian Aug 26, 2020
f9d17ac
Fix demo
omgovich Aug 27, 2020
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
24 changes: 20 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
{
"env": {
"browser": true,
"es6": true
"es6": true,
"jest": true,
"node": true
},
"parserOptions": {
"sourceType": "module"
},
"plugins": ["prettier", "react"],
"extends": [
"eslint:recommended",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
"plugin:react-hooks/recommended",
"plugin:prettier/recommended",
"prettier/@typescript-eslint",
"prettier/react"
],
"rules": {
"prettier/prettier": "error",
Expand All @@ -21,5 +27,15 @@
"react": {
"version": "detect"
}
}
},
"overrides": [
{
"files": [
"*.js"
],
"rules": {
"@typescript-eslint/no-var-requires": [0]
}
}
]
}
5 changes: 3 additions & 2 deletions build-packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fs.readdir(entryDirPath, async (e, files) => {

// Run microbundle
const { stdout } = await exec(
`${bundlerPath} --entry ${filePath} --output ${outputDirPath}/index.js --name react-colorful-${name} --css-modules true --jsx React.createElement`
`${bundlerPath} --entry ${filePath} --output ${outputDirPath}/index.js --name react-colorful-${name} --css-modules true --jsx React.createElement --tsconfig tsconfig.build.json`
);
console.log(stdout);

Expand All @@ -35,10 +35,11 @@ fs.readdir(entryDirPath, async (e, files) => {
var manifestCode = JSON.stringify({
name: `react-colorful-${name}`,
private: true,
source: filePath,
main: "index.js",
module: "index.module.js",
esmodule: "index.esmodule.js",
types: "../types/index.d.ts"
types: "index.d.ts",
});

await writeFile(manifestPath, manifestCode, "utf8");
Expand Down
14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
"name": "react-colorful",
"version": "2.2.1",
"description": "A tiny color picker component for modern React apps",
"source": "src/index.js",
"source": "src/index.ts",
"main": "dist/index.js",
"module": "dist/index.module.js",
"esmodule": "dist/index.esmodule.js",
"umd:main": "dist/index.umd.js",
"types": "types/index.d.ts",
"types": "dist/index.d.ts",
"scripts": {
"lint": "eslint src/**/*.js demo/src/**/*.js",
"lint": "eslint src/**/*.{ts,tsx} demo/src/**/*.js",
"size": "npm run build && size-limit",
"test": "jest tests",
"build": "node ./build-packages.js",
Expand Down Expand Up @@ -51,7 +51,8 @@
"jest": {
"verbose": true,
"transform": {
"\\.js$": "jest-esm-jsx-transform"
"\\.js$": "jest-esm-jsx-transform",
"^.+\\.tsx?$": "ts-jest"
},
"moduleNameMapper": {
"\\.css$": "identity-obj-proxy"
Expand Down Expand Up @@ -95,7 +96,10 @@
"devDependencies": {
"@size-limit/preset-small-lib": "^4.5.6",
"@testing-library/react": "^10.4.8",
"@types/jest": "^26.0.10",
"@types/react": "^16.9.46",
"@typescript-eslint/eslint-plugin": "^3.9.1",
"@typescript-eslint/parser": "^3.9.1",
"del": "^5.1.0",
"del-cli": "^3.0.1",
"eslint": "^7.5.0",
Expand All @@ -114,6 +118,8 @@
"react": "^16.8.0",
"react-dom": "^16.8.0",
"size-limit": "^4.5.5",
"ts-jest": "^26.2.0",
"typescript": "^3.9.7",
"use-throttled-effect": "0.0.7"
},
"dependencies": {}
Expand Down
23 changes: 19 additions & 4 deletions src/components/ColorPicker.js → src/components/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import React, { useState, useEffect, useCallback, useRef } from "react";

import Hue from "./Hue";
import Saturation from "./Saturation";
import formatClassName from "../utils/formatClassName";
import equalColorObjects from "../utils/equalColorObjects";

import styles from "../styles.css";
import { ColorModel, AnyColor, HSV } from "../types";
import { equalColorObjects } from "../utils/compare";
import formatClassName from "../utils/formatClassName";

interface Props {
className: string;
colorModel: ColorModel<AnyColor>;
color: AnyColor;
onChange: (newColor: AnyColor) => void;
}

const ColorPicker = ({ className, colorModel, color = colorModel.defaultColor, onChange }) => {
const ColorPicker: React.FC<Props> = ({
className,
colorModel,
color = colorModel.defaultColor,
onChange,
}: Props) => {
// No matter which color model is used (HEX, RGB or HSL),
// all internal calculations are based on HSV model
const [hsv, updateHsv] = useState(() => colorModel.toHsv(color));
const [hsv, updateHsv] = useState<HSV>(() => colorModel.toHsv(color));

// By using this ref we're able to prevent extra updates
// and the effects recursion during the color conversion
Expand Down
12 changes: 9 additions & 3 deletions src/components/HexInput.js → src/components/HexInput.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import React, { useState, useEffect, useCallback } from "react";
import validHex from "../utils/validHex";

import { validHex } from "../utils/validate";

// Escapes all non-hexadecimal characters including "#"
const escape = (hex) => hex.replace(/([^0-9A-F]+)/gi, "");
const escape = (hex: string) => hex.replace(/([^0-9A-F]+)/gi, "");

interface Props {
color: string;
onChange: (newColor: string) => void;
}

const HexInput = (props) => {
const HexInput = (props: Props) => {
const { color, onChange } = props;
const [value, setValue] = useState(escape(color));

Expand Down
13 changes: 10 additions & 3 deletions src/components/Hue.js → src/components/Hue.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import React, { useCallback } from "react";

import Interactive from "./Interactive";
import formatClassName from "../utils/formatClassName";
import hsvToHslString from "../utils/hsvToHslString";

import styles from "../styles.css";
import { hsvToHslString } from "../utils/convert";
import formatClassName from "../utils/formatClassName";

interface Props {
hue: number;
onChange: (newColor: any) => void;
}

const Hue = ({ hue, onChange }) => {
const Hue = ({ hue, onChange }: Props) => {
const handleMove = useCallback(
(interaction) => {
// Hue measured in degrees of the color circle ranging from 0 to 360
Expand Down
25 changes: 20 additions & 5 deletions src/components/Interactive.js → src/components/Interactive.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import React, { useState, useLayoutEffect, useRef, useCallback } from "react";

import styles from "../styles.css";

// Limit number within [0, 1] bounds.
// Use ternary operator instead of `Math.min(Math.max(0, number), 1)` to save few bytes
const limit = (number) => (number > 1 ? 1 : number < 0 ? 0 : number);
const limit = (number: number) => (number > 1 ? 1 : number < 0 ? 0 : number);

interface Interaction {
left: number;
top: number;
}

const Interactive = ({ onMove, children }) => {
const container = useRef();
interface Props {
onMove: (interaction: Interaction) => void;
children: React.ReactNode;
}

const Interactive = ({ onMove, children }: Props) => {
Copy link
Owner

@omgovich omgovich Aug 24, 2020

Choose a reason for hiding this comment

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

I'm not good at it, but shouldn't we use React.FC everywhere? I mean:

const Interactive: React.FC<Props> = ({ onMove, children }) => (

See: https://github.com/typescript-cheatsheets/react#section-2-getting-started

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The type is inferred. It's not necessary, even in strict mode. Could type it, but it's essentially like doing:

const x: string = "hello world";

In fact, there's apparently reasons to avoid React.FC altogether.

Sorry, I've been playing around in Preact land for the past 6 months and it has entirely replaced React for me. Few differences in typing the two, so I might be out of touch with React specific typings, and now there seems to be a movement against React.FC as of recently.

Copy link
Owner

Choose a reason for hiding this comment

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

Alright.
I'm actually okay with any solution you choose here)

const container = useRef<HTMLDivElement>(null);
const [isDragging, setDragging] = useState(false);

const getRelativePosition = useCallback((event) => {
const rect = container.current.getBoundingClientRect();
// This should be okay. This is only called onMove, and for it to be moved it must actually exist.
// I won't suppress the ESLint warning though, as it should probably be something to be aware of.
const rect = container.current!.getBoundingClientRect();
const pointer = typeof event.pageX === "number" ? event : event.touches[0];

return {
Expand Down Expand Up @@ -51,7 +64,9 @@ const Interactive = ({ onMove, children }) => {

useLayoutEffect(() => {
toggleDocumentEvents(isDragging);
return () => isDragging && toggleDocumentEvents(false);
return () => {
isDragging && toggleDocumentEvents(false);
};
}, [isDragging, toggleDocumentEvents]);

return (
Expand Down
14 changes: 11 additions & 3 deletions src/components/Saturation.js → src/components/Saturation.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import React, { useCallback } from "react";

import Interactive from "./Interactive";
import formatClassName from "../utils/formatClassName";

import styles from "../styles.css";
import hsvToHslString from "../utils/hsvToHslString";
import { HSV } from "../types";
import { hsvToHslString } from "../utils/convert";
import formatClassName from "../utils/formatClassName";

interface Props {
hsv: HSV;
onChange: (newColor: any) => void;
}

const Saturation = ({ hsv, onChange }) => {
const Saturation = ({ hsv, onChange }: Props) => {
const handleMove = useCallback(
(interaction) => {
// Saturation and brightness always fit into [0, 100] range
Expand Down
16 changes: 16 additions & 0 deletions src/hocs/withColorModel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";

import { ColorModel, ColorPickerBaseProps } from "../types";

const withColorModel = (
Component: React.FC<any>,
colorModel: ColorModel<any>
): React.NamedExoticComponent<any> => {
const ColorPicker = (props: ColorPickerBaseProps) => {
return <Component {...props} colorModel={colorModel} />;
};

return React.memo(ColorPicker);
};

export default withColorModel;
File renamed without changes.
File renamed without changes.
17 changes: 0 additions & 17 deletions src/packages/hex.js

This file was deleted.

22 changes: 22 additions & 0 deletions src/packages/hex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

import ColorPicker from "../components/ColorPicker";
import withColorModel from "../hocs/withColorModel";
import { ColorModel, ColorPickerBaseProps } from "../types";
import { equalHex } from "../utils/compare";
import { hexToHsv, hsvToHex } from "../utils/convert";

interface Props extends ColorPickerBaseProps<string> {
color: string;
}

const colorModel: ColorModel<string> = {
defaultColor: "000",
toHsv: hexToHsv,
fromHsv: hsvToHex,
equal: equalHex,
};

const HexColorPicker: React.FC<Partial<Props>> = withColorModel(ColorPicker, colorModel);

export default HexColorPicker;
17 changes: 0 additions & 17 deletions src/packages/hsl.js

This file was deleted.

22 changes: 22 additions & 0 deletions src/packages/hsl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

import ColorPicker from "../components/ColorPicker";
import withColorModel from "../hocs/withColorModel";
import { ColorModel, ColorPickerBaseProps, HSL } from "../types";
import { equalColorObjects } from "../utils/compare";
import { hslToHsv, hsvToHsl } from "../utils/convert";

interface Props extends ColorPickerBaseProps<HSL> {
color: HSL;
}

const colorModel: ColorModel<HSL> = {
defaultColor: { h: 0, s: 0, l: 0 },
toHsv: hslToHsv,
fromHsv: hsvToHsl,
equal: equalColorObjects,
};

const HslColorPicker: React.FC<Partial<Props>> = withColorModel(ColorPicker, colorModel);

export default HslColorPicker;
17 changes: 0 additions & 17 deletions src/packages/hslString.js

This file was deleted.

22 changes: 22 additions & 0 deletions src/packages/hslString.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

import ColorPicker from "../components/ColorPicker";
import withColorModel from "../hocs/withColorModel";
import { ColorModel, ColorPickerBaseProps } from "../types";
import { equalColorObjects } from "../utils/compare";
import { hslStringToHsv, hsvToHslString } from "../utils/convert";

interface Props extends ColorPickerBaseProps<string> {
color: string;
}

const colorModel: ColorModel<string> = {
defaultColor: "hsl(0, 0%, 0%)",
toHsv: hslStringToHsv,
fromHsv: hsvToHslString,
equal: equalColorObjects,
};

const HslStringColorPicker: React.FC<Partial<Props>> = withColorModel(ColorPicker, colorModel);

export default HslStringColorPicker;
Loading