fix: custom theme form validations (#2565)

This commit is contained in:
Aaryan Khandelwal 2023-10-31 12:12:24 +05:30 committed by GitHub
parent 1c2ea6da5e
commit 08ca016f65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 175 additions and 105 deletions

View File

@ -11,12 +11,14 @@ export interface InputColorPickerProps {
value: string | undefined; value: string | undefined;
onChange: (value: string) => void; onChange: (value: string) => void;
name: string; name: string;
className: string; className?: string;
style?: React.CSSProperties;
placeholder: string; placeholder: string;
} }
export const InputColorPicker: React.FC<InputColorPickerProps> = (props) => { export const InputColorPicker: React.FC<InputColorPickerProps> = (props) => {
const { value, hasError, onChange, name, className, placeholder } = props; const { value, hasError, onChange, name, className, style, placeholder } =
props;
const [referenceElement, setReferenceElement] = const [referenceElement, setReferenceElement] =
React.useState<HTMLButtonElement | null>(null); React.useState<HTMLButtonElement | null>(null);
@ -32,12 +34,12 @@ export const InputColorPicker: React.FC<InputColorPickerProps> = (props) => {
onChange(hex); onChange(hex);
}; };
const handleInputChange = (value: any) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(value); onChange(e.target.value);
}; };
return ( return (
<div className="flex items-center justify-between rounded border border-custom-border-200 px-1"> <div className="relative">
<Input <Input
id={name} id={name}
name={name} name={name}
@ -46,10 +48,14 @@ export const InputColorPicker: React.FC<InputColorPickerProps> = (props) => {
onChange={handleInputChange} onChange={handleInputChange}
hasError={hasError} hasError={hasError}
placeholder={placeholder} placeholder={placeholder}
className={`border-none ${className}`} className={`border-[0.5px] border-custom-border-200 ${className}`}
style={style}
/> />
<Popover as="div"> <Popover
as="div"
className="absolute top-1/2 -translate-y-1/2 right-1 z-10"
>
{({ open }) => { {({ open }) => {
if (open) { if (open) {
} }
@ -60,26 +66,26 @@ export const InputColorPicker: React.FC<InputColorPickerProps> = (props) => {
ref={setReferenceElement} ref={setReferenceElement}
variant="neutral-primary" variant="neutral-primary"
size="sm" size="sm"
className="border-none !p-1.5" className="border-none !bg-transparent"
> >
{value && value !== "" ? ( <svg
<span xmlns="http://www.w3.org/2000/svg"
className="h-3.5 w-3.5 rounded" width="14"
style={{ height="14"
backgroundColor: `${value}`, viewBox="0 0 24 24"
}} fill="none"
/> stroke="currentColor"
) : ( stroke-width="2"
<svg stroke-linecap="round"
width={14} stroke-linejoin="round"
height={14} className="lucide lucide-palette"
viewBox="0 0 14 14" >
stroke="currentColor" <circle cx="13.5" cy="6.5" r=".5" />
xmlns="http://www.w3.org/2000/svg" <circle cx="17.5" cy="10.5" r=".5" />
> <circle cx="8.5" cy="7.5" r=".5" />
<path d="M0.8125 13.7508C0.65 13.7508 0.515625 13.6977 0.409375 13.5914C0.303125 13.4852 0.25 13.3508 0.25 13.1883V10.8258C0.25 10.7508 0.2625 10.682 0.2875 10.6195C0.3125 10.557 0.35625 10.4945 0.41875 10.432L7.31875 3.53203L6.34375 2.55703C6.24375 2.45703 6.19688 2.32891 6.20312 2.17266C6.20938 2.01641 6.2625 1.88828 6.3625 1.78828C6.4625 1.68828 6.59063 1.63828 6.74688 1.63828C6.90313 1.63828 7.03125 1.68828 7.13125 1.78828L8.4625 3.13828L11.125 0.475781C11.2625 0.338281 11.4094 0.269531 11.5656 0.269531C11.7219 0.269531 11.8688 0.338281 12.0063 0.475781L13.525 1.99453C13.6625 2.13203 13.7313 2.27891 13.7313 2.43516C13.7313 2.59141 13.6625 2.73828 13.525 2.87578L10.8625 5.53828L12.2125 6.88828C12.3125 6.98828 12.3625 7.11328 12.3625 7.26328C12.3625 7.41328 12.3125 7.53828 12.2125 7.63828C12.1125 7.73828 11.9844 7.78828 11.8281 7.78828C11.6719 7.78828 11.5438 7.73828 11.4438 7.63828L10.4688 6.68203L3.56875 13.582C3.50625 13.6445 3.44375 13.6883 3.38125 13.7133C3.31875 13.7383 3.25 13.7508 3.175 13.7508H0.8125ZM1.375 12.6258H3.00625L9.6625 5.96953L8.03125 4.33828L1.375 10.9945V12.6258ZM10.0563 4.75078L12.3813 2.42578L11.575 1.61953L9.25 3.94453L10.0563 4.75078Z" /> <circle cx="6.5" cy="12.5" r=".5" />
</svg> <path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z" />
)} </svg>
</Button> </Button>
</Popover.Button> </Popover.Button>
<Transition <Transition

View File

@ -1,27 +1,40 @@
import { FC } from "react"; import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useTheme } from "next-themes";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { Button, InputColorPicker } from "@plane/ui"; import { Button, InputColorPicker } from "@plane/ui";
// types // types
import { IUserTheme } from "types"; import { IUserTheme } from "types";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
type Props = {}; const inputRules = {
required: "Background color is required",
minLength: {
value: 7,
message: "Enter a valid hex code of 6 characters",
},
maxLength: {
value: 7,
message: "Enter a valid hex code of 6 characters",
},
pattern: {
value: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
message: "Enter a valid hex code of 6 characters",
},
};
export const CustomThemeSelector: FC<Props> = observer(() => { export const CustomThemeSelector: React.FC = observer(() => {
const { user: userStore } = useMobxStore(); const { user: userStore } = useMobxStore();
const userTheme = userStore?.currentUser?.theme; const userTheme = userStore?.currentUser?.theme;
// hooks // hooks
const { setTheme } = useTheme(); const { setTheme } = useTheme();
const { const {
control,
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
handleSubmit, handleSubmit,
control, watch,
} = useForm<IUserTheme>({ } = useForm<IUserTheme>({
defaultValues: { defaultValues: {
background: userTheme?.background !== "" ? userTheme?.background : "#0d101b", background: userTheme?.background !== "" ? userTheme?.background : "#0d101b",
@ -51,100 +64,151 @@ export const CustomThemeSelector: FC<Props> = observer(() => {
return userStore.updateCurrentUser({ theme: payload }); return userStore.updateCurrentUser({ theme: payload });
}; };
const handleValueChange = (val: string | undefined, onChange: any) => {
let hex = val;
// prepend a hashtag if it doesn't exist
if (val && val[0] !== "#") hex = `#${val}`;
onChange(hex);
};
return ( return (
<form onSubmit={handleSubmit(handleUpdateTheme)}> <form onSubmit={handleSubmit(handleUpdateTheme)}>
<div className="space-y-5"> <div className="space-y-5">
<h3 className="text-lg font-semibold text-custom-text-100">Customize your theme</h3> <h3 className="text-lg font-semibold text-custom-text-100">Customize your theme</h3>
<div className="space-y-4"> <div className="space-y-4">
<div className="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2 md:grid-cols-3"> <div className="grid grid-cols-1 gap-x-8 gap-y-4 sm:grid-cols-2 md:grid-cols-3">
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Background color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Background color</h3>
<Controller <div className="w-full">
control={control} <Controller
name="background" control={control}
render={({ field: { value, onChange } }) => ( name="background"
<InputColorPicker rules={inputRules}
name="background" render={({ field: { value, onChange } }) => (
value={value} <InputColorPicker
onChange={onChange} name="background"
className="" value={value}
placeholder="#ffffff" onChange={(val) => handleValueChange(val, onChange)}
hasError={Boolean(errors?.background)} placeholder="#0d101b"
/> className="w-full"
)} style={{
/> backgroundColor: value,
color: watch("text"),
}}
hasError={Boolean(errors?.background)}
/>
)}
/>
{errors.background && <p className="text-xs text-red-500 mt-1">{errors.background.message}</p>}
</div>
</div> </div>
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Text color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Text color</h3>
<Controller <div className="w-full">
control={control} <Controller
name="text" control={control}
render={({ field: { value, onChange } }) => ( name="text"
<InputColorPicker rules={inputRules}
name="text" render={({ field: { value, onChange } }) => (
value={value} <InputColorPicker
onChange={onChange} name="text"
className="" value={value}
placeholder="#ffffff" onChange={(val) => handleValueChange(val, onChange)}
hasError={Boolean(errors?.text)} placeholder="#c5c5c5"
/> className="w-full"
)} style={{
/> backgroundColor: watch("background"),
color: value,
}}
hasError={Boolean(errors?.text)}
/>
)}
/>
{errors.text && <p className="text-xs text-red-500 mt-1">{errors.text.message}</p>}
</div>
</div> </div>
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Primary(Theme) color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Primary(Theme) color</h3>
<Controller <div className="w-full">
control={control} <Controller
name="primary" control={control}
render={({ field: { value, onChange } }) => ( name="primary"
<InputColorPicker rules={inputRules}
name="primary" render={({ field: { value, onChange } }) => (
value={value} <InputColorPicker
onChange={onChange} name="primary"
className="" value={value}
placeholder="#ffffff" onChange={(val) => handleValueChange(val, onChange)}
hasError={Boolean(errors?.primary)} placeholder="#3f76ff"
/> className="w-full"
)} style={{
/> backgroundColor: value,
color: watch("text"),
}}
hasError={Boolean(errors?.primary)}
/>
)}
/>
{errors.primary && <p className="text-xs text-red-500 mt-1">{errors.primary.message}</p>}
</div>
</div> </div>
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar background color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar background color</h3>
<Controller <div className="w-full">
control={control} <Controller
name="sidebarBackground" control={control}
render={({ field: { value, onChange } }) => ( name="sidebarBackground"
<InputColorPicker rules={inputRules}
name="sidebarBackground" render={({ field: { value, onChange } }) => (
value={value} <InputColorPicker
onChange={onChange} name="sidebarBackground"
className="" value={value}
placeholder="#ffffff" onChange={(val) => handleValueChange(val, onChange)}
hasError={Boolean(errors?.sidebarBackground)} placeholder="#0d101b"
/> className="w-full"
style={{
backgroundColor: value,
color: watch("sidebarText"),
}}
hasError={Boolean(errors?.sidebarBackground)}
/>
)}
/>
{errors.sidebarBackground && (
<p className="text-xs text-red-500 mt-1">{errors.sidebarBackground.message}</p>
)} )}
/> </div>
</div> </div>
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar text color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar text color</h3>
<Controller <div className="w-full">
control={control} <Controller
name="sidebarText" control={control}
render={({ field: { value, onChange } }) => ( name="sidebarText"
<InputColorPicker rules={inputRules}
name="sidebarText" render={({ field: { value, onChange } }) => (
value={value} <InputColorPicker
onChange={onChange} name="sidebarText"
className="" value={value}
placeholder="#ffffff" onChange={(val) => handleValueChange(val, onChange)}
hasError={Boolean(errors?.sidebarText)} placeholder="#c5c5c5"
/> className="w-full"
)} style={{
/> backgroundColor: watch("sidebarBackground"),
color: value,
}}
hasError={Boolean(errors?.sidebarText)}
/>
)}
/>
{errors.sidebarText && <p className="text-xs text-red-500 mt-1">{errors.sidebarText.message}</p>}
</div>
</div> </div>
</div> </div>
</div> </div>