Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

fix: unable to type RGBA values #180

Merged
merged 3 commits into from
Feb 28, 2022
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { useState, useEffect, useCallback, useRef } from "react";
import tinycolor, { ColorInput } from "tinycolor2";

export type RGBA = {
r: number;
g: number;
b: number;
a: number;
};

export type Params = {
value?: string;
onChange?: (value: string | null) => void | undefined;
};

export default ({ value, onChange }: Params) => {
const [colorState, setColor] = useState<string | null>(null);
const [rgba, setRgba] = useState<RGBA>(tinycolor(value).toRgb());
const [open, setOpen] = useState(false);
const wrapperRef = useRef<HTMLDivElement>(null);
const pickerRef = useRef<HTMLDivElement>(null);

const getHexString = (value?: ColorInput) => {
if (!value) return undefined;
const color = tinycolor(value);
return color.getAlpha() === 1 ? color.toHexString() : color.toHex8String();
};

const handleChange = useCallback(newColor => {
const color = getHexString(newColor);
if (!color) return;
setColor(color);
setRgba(newColor);
}, []);
HideBa marked this conversation as resolved.
Show resolved Hide resolved

const handleRgbaInput = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();

if (e.target.name === "a") {
setRgba({
...rgba,
[e.target.name]: Number(e.target.value) / 100,
});
} else {
setRgba({
...rgba,
[e.target.name]: e.target.value ? Number(e.target.value) : undefined,
});
}
},
[rgba],
KaWaite marked this conversation as resolved.
Show resolved Hide resolved
);

const handleHexInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
setColor(e.target.value);
}, []);
HideBa marked this conversation as resolved.
Show resolved Hide resolved

const handleClose = useCallback(() => {
if (value) {
setColor(value);
setRgba(tinycolor(value).toRgb());
} else {
setColor(null);
setRgba(tinycolor(colorState == null ? undefined : colorState).toRgb());
}
setOpen(false);
}, [value, colorState]);
HideBa marked this conversation as resolved.
Show resolved Hide resolved

const handleSave = useCallback(() => {
if (!onChange) return;
if (colorState != value) {
onChange(colorState);
}
setOpen(false);
}, [colorState, onChange, value]);

const handleHexSave = useCallback(() => {
const hexPattern = /^#?([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/;
if (colorState && hexPattern.test(colorState)) {
handleSave();
} else {
value && setColor(value);
}
}, [colorState, handleSave, value]);

const handleClick = useCallback(() => setOpen(!open), [open]);

const handleKeyPress = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleHexSave();
}
},
[handleHexSave],
);

useEffect(() => {
if (value) {
setColor(value);
setRgba(tinycolor(value).toRgb());
} else {
setColor(null);
}
}, [value]);
HideBa marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
if (!value) return;
if (rgba && tinycolor(rgba).toHex8String() !== value) {
setColor(tinycolor(rgba).toHex8String());
}
}, [rgba]); // eslint-disable-line react-hooks/exhaustive-deps
HideBa marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
if (open && wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
if (colorState != value && !open) {
handleSave();
}
handleClose();
setOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("touchstart", handleClickOutside);

return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("touchstart", handleClickOutside);
};
}, [handleClose]); // eslint-disable-line react-hooks/exhaustive-deps

return {
wrapperRef,
pickerRef,
colorState,
open,
rgba,
handleClose,
handleSave,
handleHexSave,
handleChange,
handleRgbaInput,
handleHexInput,
handleClick,
handleKeyPress,
};
};
Original file line number Diff line number Diff line change
@@ -1,48 +1,37 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import React from "react";
import { RgbaColorPicker } from "react-colorful";
import { useIntl } from "react-intl";
import { usePopper } from "react-popper";
import tinycolor, { ColorInput } from "tinycolor2";

import Button from "@reearth/components/atoms/Button";
import Text from "@reearth/components/atoms/Text";
import { styled, css, useTheme, metricsSizes } from "@reearth/theme";

import { FieldProps } from "../types";

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

export type Props = FieldProps<string>;

export type RGBA = {
r: number;
g: number;
b: number;
a: number;
};

const getHexString = (value?: ColorInput) => {
if (!value) return undefined;
const color = tinycolor(value);
return color.getAlpha() === 1 ? color.toHexString() : color.toHex8String();
};

const initColor = tinycolor().toRgb();

const ColorField: React.FC<Props> = ({ value, onChange, overridden, linked }) => {
const intl = useIntl();
const [colorState, setColor] = useState<string | null>(null);
const [rgba, setRgba] = useState<RGBA>(initColor);
const [open, setOpen] = useState(false);
const wrapperRef = useRef<HTMLDivElement>(null);
const pickerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (value) {
setColor(value);
setRgba(tinycolor(value).toRgb());
} else {
setColor(null);
}
}, [value]);
const theme = useTheme();
const {
wrapperRef,
pickerRef,
colorState,
open,
rgba,
handleClose,
handleSave,
handleHexSave,
handleChange,
handleRgbaInput,
handleHexInput,
handleClick,
handleKeyPress,
} = useHooks({ value, onChange });

const { styles, attributes } = usePopper(wrapperRef.current, pickerRef.current, {
placement: "bottom-start",
Expand All @@ -64,97 +53,6 @@ const ColorField: React.FC<Props> = ({ value, onChange, overridden, linked }) =>
],
});

const handleChange = useCallback(newColor => {
const color = getHexString(newColor);
if (!color) return;
setColor(color);
setRgba(newColor);
}, []);

const handleRgbaInput = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();

if (e.target.name === "a") {
setRgba({
...rgba,
[e.target.name]: Number(e.target.value) / 100,
});
} else {
setRgba({
...rgba,
[e.target.name]: e.target.value ? Number(e.target.value) : undefined,
});
}
},
[rgba],
);

const handleHexInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
setColor(e.target.value);
}, []);

const handleClick = useCallback(() => setOpen(!open), [open]);

const handleClose = useCallback(() => {
if (value) {
setColor(value);
setRgba(tinycolor(value).toRgb());
} else {
setColor(null);
setRgba(tinycolor(colorState == null ? undefined : colorState).toRgb());
}
setOpen(false);
}, [value, colorState]);

const handleSave = useCallback(() => {
if (!onChange) return;
if (colorState != value) {
onChange(colorState);
}
setOpen(false);
}, [colorState, onChange, value]);

const handleHexSave = useCallback(() => {
const hexPattern = /^#?([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/;
if (colorState && hexPattern.test(colorState)) {
handleSave();
} else {
value && setColor(value);
}
}, [colorState, handleSave, value]);

const handleKeyPress = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleHexSave();
}
},
[handleHexSave],
);

useEffect(() => {
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
if (open && wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
if (colorState != value && !open) {
handleSave();
}
handleClose();
setOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("touchstart", handleClickOutside);

return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("touchstart", handleClickOutside);
};
}, [handleClose]); // eslint-disable-line react-hooks/exhaustive-deps
const theme = useTheme();

return (
<Wrapper ref={wrapperRef}>
<InputWrapper>
Expand Down Expand Up @@ -187,7 +85,7 @@ const ColorField: React.FC<Props> = ({ value, onChange, overridden, linked }) =>
linked={linked}
/>
<PickerText size="2xs" color={theme.properties.contentsFloatText}>
Red
{intl.formatMessage({ defaultMessage: "Red" })}
</PickerText>
</Field>
<Field>
Expand All @@ -202,7 +100,7 @@ const ColorField: React.FC<Props> = ({ value, onChange, overridden, linked }) =>
linked={linked}
/>
<PickerText size="2xs" color={theme.properties.contentsFloatText}>
Green
{intl.formatMessage({ defaultMessage: "Green" })}
</PickerText>
</Field>
<Field>
Expand All @@ -217,7 +115,7 @@ const ColorField: React.FC<Props> = ({ value, onChange, overridden, linked }) =>
linked={linked}
/>
<PickerText size="2xs" color={theme.properties.contentsFloatText}>
Blue
{intl.formatMessage({ defaultMessage: "Blue" })}
</PickerText>
</Field>
<Field>
Expand All @@ -230,7 +128,7 @@ const ColorField: React.FC<Props> = ({ value, onChange, overridden, linked }) =>
linked={linked}
/>
<PickerText size="2xs" color={theme.properties.contentsFloatText}>
Alpha
{intl.formatMessage({ defaultMessage: "Alpha" })}
</PickerText>
</Field>
</RgbaInputWrapper>
Expand Down
4 changes: 4 additions & 0 deletions translations/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ src:
ColorField:
'1115770113': Save
'1533357798': Not set
'1847234945': Red
'1936615876': Alpha
'2090560987': Green
'3528672691': Cancel
'3801160378': Blue
LocationField:
'2222144661': Latitude
'3067266584': Altitude
Expand Down
4 changes: 4 additions & 0 deletions translations/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,11 @@ src:
ColorField:
'1115770113': 保存
'1533357798': 未設定
'1847234945': 赤
'1936615876': アルファ
'2090560987': 緑
'3528672691': キャンセル
'3801160378': 青
LocationField:
'2222144661': 緯度
'3067266584': 高度
Expand Down