Skip to content

Commit cf3b9b8

Browse files
committed
client: add a customized color picker
1 parent d5396cf commit cf3b9b8

File tree

9 files changed

+129
-26
lines changed

9 files changed

+129
-26
lines changed

client/package-lock.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"class-variance-authority": "^0.5.1",
1414
"clsx": "^1.2.1",
1515
"react": "^18.2.0",
16+
"react-colorful": "^5.6.1",
1617
"react-dom": "^18.2.0",
1718
"react-icons": "^4.8.0",
1819
"tailwind-merge": "^1.12.0"

client/src/components/form/PageTwo.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ const PageTwo = () => {
5656
onChange={(e) =>
5757
setCard((prev) => ({ ...prev, backgroundColor: e.target.value }))
5858
}
59+
setColor={(c) =>
60+
setCard((prev) => ({ ...prev, backgroundColor: c }))
61+
}
5962
/>
6063
</InputWrapper>
6164

@@ -69,6 +72,7 @@ const PageTwo = () => {
6972
onChange={(e) =>
7073
setCard((prev) => ({ ...prev, borderColor: e.target.value }))
7174
}
75+
setColor={(c) => setCard((prev) => ({ ...prev, borderColor: c }))}
7276
/>
7377
</InputWrapper>
7478
</Flex>
@@ -84,6 +88,7 @@ const PageTwo = () => {
8488
onChange={(e) =>
8589
setCard((prev) => ({ ...prev, titleColor: e.target.value }))
8690
}
91+
setColor={(c) => setCard((prev) => ({ ...prev, titleColor: c }))}
8792
/>
8893
</InputWrapper>
8994

@@ -97,6 +102,7 @@ const PageTwo = () => {
97102
onChange={(e) =>
98103
setCard((prev) => ({ ...prev, badgeColor: e.target.value }))
99104
}
105+
setColor={(c) => setCard((prev) => ({ ...prev, badgeColor: c }))}
100106
/>
101107
</InputWrapper>
102108
</Flex>

client/src/components/lines/LineItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const LineItem = ({ line }: Props) => {
1313
const { addBadge } = useMultistepContext();
1414

1515
return (
16-
<div className="w-full overflow-hidden rounded-md border border-gh-border bg-gh-bg">
17-
<div className="border-b border-gh-border bg-gh-bg-secondary px-3 py-2 leading-none text-gh-text">
16+
<div className="w-full rounded-md border border-gh-border bg-gh-bg">
17+
<div className="rounded-tl-md rounded-tr-md border-b border-gh-border bg-gh-bg-secondary px-3 py-2 leading-none text-gh-text">
1818
{formatNumberWord(line.lineNumber)} line
1919
</div>
2020

client/src/components/lines/NewBadge.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const NewBadge = ({ addBadge }: Props) => {
6565
disabled={file !== null}
6666
value={color}
6767
onChange={(e) => setColor(e.target.value)}
68+
setColor={(c) => setColor(c)}
6869
placeholder="#58a6ff"
6970
/>
7071
</InputWrapper>

client/src/components/lines/SvgInput.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { cn } from "../ui/utils";
44
import { AiOutlineCloudUpload } from "react-icons/ai";
55
import Button from "../ui/Button";
66

7-
// eslint-disable-next-line
87
interface SvgInputProps
98
extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
109
onBtnClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
@@ -19,7 +18,6 @@ const SvgInput = forwardRef<HTMLInputElement, SvgInputProps>(
1918
value={value}
2019
type="text"
2120
required={required}
22-
pattern={`^${required ? "" : "[]{0}|"}#[a-fA-F0-9]{6}$`}
2321
className="z-10 rounded-br-none rounded-tr-none transition-none [border-right:none!important]"
2422
{...props}
2523
/>

client/src/components/ui/ColorInput.tsx

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,72 @@
1-
import { InputHTMLAttributes, forwardRef } from "react";
1+
import { InputHTMLAttributes, forwardRef, useState } from "react";
22
import Input from "./Input";
33
import { cn } from "./utils";
44
import { HEX_COLOR_REGEX } from "../../const";
5+
import { IoMdColorFilter } from "react-icons/io";
6+
import Button from "./Button";
7+
import { HexColorPicker } from "react-colorful";
8+
import { useOuterClick } from "../../hooks/useOuterClick";
59

6-
// eslint-disable-next-line
710
interface ColorInputProps
8-
extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {}
11+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
12+
setColor: (color: string) => void;
13+
}
914

1015
const ColorInput = forwardRef<HTMLInputElement, ColorInputProps>(
11-
({ className, required, value = "", ...props }, ref) => {
16+
({ className, required, value = "", setColor, ...props }, ref) => {
17+
value = value.toString();
18+
19+
const [isPickerActive, setIsPickerActive] = useState(false);
20+
const divRef = useOuterClick<HTMLDivElement>(() =>
21+
setIsPickerActive(false)
22+
);
23+
1224
return (
13-
<div className={cn("group flex items-stretch", className)}>
25+
<div
26+
ref={divRef}
27+
className={cn("group relative flex items-stretch", className)}
28+
>
1429
<Input
1530
ref={ref}
1631
value={value}
32+
onFocus={() => setIsPickerActive(true)}
1733
type="text"
1834
required={required}
1935
pattern={`^${required ? "" : "[]{0}|"}#[a-fA-F0-9]{6}$`}
20-
className="z-10 rounded-br-none rounded-tr-none transition-none [border-right:none!important]"
36+
className={cn(
37+
"z-10 rounded-br-none rounded-tr-none [border-right:none!important] [transition:none!important]",
38+
isPickerActive ? "bg-gh-bg outline-gh-blue" : ""
39+
)}
2140
{...props}
2241
/>
2342

24-
<input
25-
type="color"
26-
value={
27-
HEX_COLOR_REGEX.test(value.toString() ?? "") ? value : "#58a6ff"
28-
}
29-
className="h-[30.67px] w-10 cursor-pointer rounded-br-md rounded-tr-md border border-gh-border bg-gh-bg px-2 py-1 outline-0 outline-offset-0 outline-gh-blue transition-all duration-150 [outline-style:solid] hover:border-gh-border-active hover:bg-gh-gray-active disabled:cursor-not-allowed disabled:border-gh-border disabled:bg-gh-bg"
30-
{...props}
43+
<Button
44+
tabIndex={-1}
45+
variant="secondary"
46+
onFocus={() => setIsPickerActive(true)}
47+
style={{
48+
color: HEX_COLOR_REGEX.test(value) ? value : "#58a6ff",
49+
}}
50+
className={cn(
51+
"rounded-bl-none rounded-tl-none bg-gh-bg px-3 text-base [transition:none!important]",
52+
isPickerActive ? "outline-gh-blue hover:border-gh-border" : ""
53+
)}
54+
icon={<IoMdColorFilter />}
3155
/>
56+
57+
<div
58+
className={cn(
59+
"color-input absolute right-[-53%] top-[-590%] z-30 flex flex-col gap-1 overflow-hidden rounded-md border border-gh-border bg-gh-bg shadow-md",
60+
isPickerActive
61+
? "pointer-events-auto opacity-100"
62+
: "pointer-events-none opacity-0"
63+
)}
64+
>
65+
<div className="border-b border-gh-border p-1.5 text-center text-sm font-semibold leading-none text-gh-text">
66+
Color Picker
67+
</div>
68+
<HexColorPicker color={value} onChange={setColor} className="m-2" />
69+
</div>
3270
</div>
3371
);
3472
}

client/src/hooks/useOuterClick.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { RefObject, useEffect, useRef } from "react";
2+
3+
export const useOuterClick = <T extends HTMLElement>(
4+
callback: () => void
5+
): RefObject<T> => {
6+
// eslint-disable-next-line
7+
const callbackRef = useRef<() => void>(() => {});
8+
const ref = useRef<T>(null);
9+
10+
// update the callback on each render
11+
useEffect(() => {
12+
callbackRef.current = callback;
13+
});
14+
15+
useEffect(() => {
16+
const handleClick = (e: any) => {
17+
if (ref.current && !ref.current?.contains(e.target)) {
18+
callbackRef.current();
19+
}
20+
};
21+
22+
document.addEventListener("mousedown", handleClick);
23+
return () => document.removeEventListener("mousedown", handleClick);
24+
}, []);
25+
26+
return ref;
27+
};

client/src/index.css

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,46 @@
33
@tailwind utilities;
44

55
@layer base {
6+
body {
7+
@apply bg-gh-bg font-segoe;
8+
}
9+
10+
/* custom scrollbar */
611
*::-webkit-scrollbar {
712
@apply h-[.75rem] w-[.75rem];
813
}
9-
1014
*::-webkit-scrollbar-track {
1115
@apply bg-gh-gray;
1216
}
13-
1417
*::-webkit-scrollbar-thumb {
15-
@apply rounded-md border-[3px] border-solid border-transparent
16-
bg-gh-border bg-clip-content transition-all;
17-
}
18-
19-
body {
20-
@apply bg-gh-bg font-segoe;
18+
@apply rounded-md border-[3px] border-solid border-transparent bg-gh-border bg-clip-content transition-all;
2119
}
2220

21+
/* remove the arrows from the number input fields */
2322
input::-webkit-outer-spin-button,
2423
input::-webkit-inner-spin-button {
2524
-webkit-appearance: none;
2625
}
27-
2826
input[type="number"] {
2927
-moz-appearance: textfield;
3028
appearance: none;
3129
}
30+
31+
/* customize the color-picker */
32+
.color-input .react-colorful {
33+
@apply h-32 w-32;
34+
}
35+
.color-input .react-colorful__hue-pointer,
36+
.color-input .react-colorful__saturation-pointer {
37+
@apply h-4 w-4 border;
38+
}
39+
.color-input .react-colorful__hue {
40+
@apply h-3;
41+
}
42+
.color-input .react-colorful__saturation {
43+
@apply mb-2 rounded-sm;
44+
}
45+
.color-input .react-colorful__hue {
46+
@apply rounded-sm;
47+
}
3248
}

0 commit comments

Comments
 (0)