Skip to content

Commit

Permalink
feat(luna): add error icon & animation
Browse files Browse the repository at this point in the history
  • Loading branch information
neuodev committed Aug 7, 2024
1 parent 27ee460 commit dd974ef
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 26 deletions.
21 changes: 21 additions & 0 deletions luna/src/components/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ export const Idle: StoryObj<typeof Wrapper> = {
},
};

export const IdelWithValue: StoryObj<typeof Wrapper> = {
args: {
id: "name",
label: ar["global.form.email.label"],
placeholder: ar["global.form.email.placeholder"],
value: "لايت اسبيس",
onChange: () => {},
},
};

export const Error: StoryObj<typeof Wrapper> = {
args: {
id: "name",
Expand All @@ -38,4 +48,15 @@ export const Error: StoryObj<typeof Wrapper> = {
},
};

export const ErrorWithValue: StoryObj<typeof Wrapper> = {
args: {
id: "name",
label: ar["global.form.email.label"],
placeholder: ar["global.form.email.placeholder"],
error: ar["errors.email.invlaid"],
value: "لايت اسبيس",
onChange: () => {},
},
};

export default meta;
78 changes: 52 additions & 26 deletions luna/src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react";
import React, { useMemo } from "react";
import { ChangeHandler } from "react-hook-form";
import { motion, AnimatePresence } from "framer-motion";
import cn from "classnames";
import ErrorOutlined from "@/icons/ErrorOutlined";

export const Input: React.FC<{
id: string;
Expand All @@ -13,6 +14,7 @@ export const Input: React.FC<{
onBlur?: ChangeHandler;
name?: string;
error?: string | null;
value?: string;
}> = ({
label,
type,
Expand All @@ -23,6 +25,7 @@ export const Input: React.FC<{
onBlur,
name,
error,
value,
}) => {
return (
<div className="ui-flex ui-flex-col ui-w-full">
Expand All @@ -34,42 +37,65 @@ export const Input: React.FC<{
>
{label}
</label>
<input
id={id}
type={type}
name={name}
autoComplete={autoComplete}
onChange={onChange}
onBlur={onBlur}
className={cn(
"ui-bg-inputbg ui-py-[10px] ui-px-lg ui-rounded-2xl ui-h-[72px] ui-font-cairo placeholder:ui-text-arxl placeholder:ui-font-medium ui-leading-normal",
"ui-text-arxl ui-font-bold ui-leading-normal ui-border focus:ui-outline-none focus:ui-border-blue-normal",
{
"ui-bg-red-light ui-border-red-border focus:ui-border-red-border":
!!error,
"ui-border-transparent": !error,
}
)}
placeholder={placeholder}
/>
<div className="ui-w-full ui-relative">
<input
id={id}
type={type}
name={name}
value={value}
autoComplete={autoComplete}
onChange={onChange}
onBlur={onBlur}
className={cn(
"ui-w-full ui-bg-inputbg ui-py-[10px] ui-px-lg ui-rounded-2xl ui-h-[72px] ui-font-cairo placeholder:ui-text-arxl placeholder:ui-font-medium ui-leading-normal",
"ui-text-arxl ui-font-bold ui-leading-normal ui-border focus:ui-outline-none focus:ui-border-blue-normal",
{
"ui-bg-red-light ui-border-red-border focus:ui-border-red-border":
!!error,
"ui-border-transparent": !error,
}
)}
placeholder={placeholder}
/>
<ErrorIcon error={!!error} />
</div>
<AnimatePresence mode="wait" initial={false}>
{error ? <InputError message={error} key={label} /> : null}
</AnimatePresence>
</div>
);
};

function framerError(y: string | number) {
return {
initial: { opacity: 0, y: 10 },
animate: { opacity: 1, y },
exit: { opacity: 0, y: 10 },
transition: { duration: 0.2 },
};
}

const InputError: React.FC<{ message: string }> = ({ message }) => {
const framer = useMemo(() => framerError(0), []);
return (
<motion.p className="ui-text-arsm ui-text-red-400" {...framerError}>
<motion.p className="ui-text-arsm ui-text-red-400" {...framer}>
{message}
</motion.p>
);
};

const framerError = {
initial: { opacity: 0, y: 10 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: 10 },
transition: { duration: 0.2 },
} as const;
const ErrorIcon: React.FC<{ error: boolean }> = ({ error }) => {
const framer = useMemo(() => framerError("-50%"), []);
return (
<AnimatePresence mode="wait" initial={false}>
{error ? (
<motion.div
className="ui-absolute ui-top-1/2 ui-left-lg ui-transform -ui-translate-y-1/2"
{...framer}
>
<ErrorOutlined />
</motion.div>
) : null}
</AnimatePresence>
);
};
17 changes: 17 additions & 0 deletions luna/src/icons/ErrorOutlined.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SVGProps } from "react";

const ErrorOutlined = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
fill="none"
{...props}
>
<path
fill="#EE3024"
d="M11 15h2v2h-2v-2Zm0-8h2v6h-2V7Zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2ZM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8Z"
/>
</svg>
);
export default ErrorOutlined;

0 comments on commit dd974ef

Please sign in to comment.