Add motion animations to Button and CabinetMaintenance components; update form validation and styling
This commit is contained in:
parent
442b8e52dd
commit
bfe22a61a5
72
package-lock.json
generated
72
package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"lucide-react": "^0.446.0",
|
||||
"motion": "^11.15.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -2857,6 +2858,33 @@
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "11.15.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.15.0.tgz",
|
||||
"integrity": "sha512-MLk8IvZntxOMg7lDBLw2qgTHHv664bYoYmnFTmE0Gm/FW67aOJk0WM3ctMcG+Xhcv+vh5uyyXwxvxhSeJzSe+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^11.14.3",
|
||||
"motion-utils": "^11.14.3",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@ -3928,6 +3956,44 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/motion": {
|
||||
"version": "11.15.0",
|
||||
"resolved": "https://registry.npmjs.org/motion/-/motion-11.15.0.tgz",
|
||||
"integrity": "sha512-iZ7dwADQJWGsqsSkBhNHdI2LyYWU+hA1Nhy357wCLZq1yHxGImgt3l7Yv0HT/WOskcYDq9nxdedyl4zUv7UFFw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"framer-motion": "^11.15.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "11.14.3",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.3.tgz",
|
||||
"integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "11.14.3",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.14.3.tgz",
|
||||
"integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@ -5327,6 +5393,12 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"lucide-react": "^0.446.0",
|
||||
"motion": "^11.15.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
31
src/App.jsx
31
src/App.jsx
@ -1,19 +1,38 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { useOutlet } from "react-router";
|
||||
import { useState } from "react";
|
||||
import Header from "./components/Header";
|
||||
import Footer from "./components/Footer";
|
||||
import { ToastProvider } from "./components/Toast";
|
||||
import { AnimatePresence } from "motion/react";
|
||||
import { motion } from "motion/react";
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
const AnimatedOutlet = () => {
|
||||
const o = useOutlet();
|
||||
const [outlet] = useState(o);
|
||||
|
||||
return <div>{outlet}</div>;
|
||||
};
|
||||
|
||||
function App() {
|
||||
const location = useLocation();
|
||||
return (
|
||||
<ToastProvider>
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Header />
|
||||
<main className="flex flex-grow transition-color-mode md:p-7 2xl:p-12 bg-surface dark:bg-surface-dark">
|
||||
<Outlet />
|
||||
<main className="overflow-hidden flex flex-grow transition-color-mode md:p-7 2xl:p-12 bg-surface dark:bg-surface-dark">
|
||||
<AnimatePresence mode="popLayout">
|
||||
<motion.div
|
||||
className="w-full ovwerflow-hidden"
|
||||
key={location.pathname}
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 50 }}
|
||||
>
|
||||
<AnimatedOutlet />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
function Button({text, onClick}) {
|
||||
return (
|
||||
<button
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="px-12 py-2 text-lg text-white dark:text-primary-dark rounded-full bg-primary dark:bg-secondary-dark"
|
||||
onClick={onClick}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
</motion.button>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,23 @@
|
||||
import { PropTypes } from 'prop-types';
|
||||
import clsx from 'clsx';
|
||||
import { PropTypes } from "prop-types";
|
||||
import clsx from "clsx";
|
||||
|
||||
function FormBox({ title, children, alt = false }) {
|
||||
return (
|
||||
<form className={clsx(alt ? 'bg-secondary-variant dark:bg-secondary-variant-dark border-secondary-variant dark:border-secondary-variant-dark' : 'bg-surface-variant dark:bg-surface-variant-dark', 'transition-color-mode font-body border-secondary dark:border-secondary-dark border-2 p-4 rounded-3xl relative h-full')}>
|
||||
<label className={clsx(alt && 'bg-surface dark:bg-surface-dark border-3 border-secondary-variant dark:border-secondary-variant-dark', 'font-body absolute left-11 -top-4 bg-secondary dark:bg-secondary-dark text-primary dark:text-primary-dark font-medium py-1 px-4 rounded-full')}>
|
||||
<form
|
||||
className={clsx(
|
||||
alt
|
||||
? "bg-secondary-variant dark:bg-secondary-variant-dark border-secondary-variant dark:border-secondary-variant-dark"
|
||||
: "bg-surface-variant dark:bg-surface-variant-dark",
|
||||
"transition-color-mode font-body border-secondary dark:border-secondary-dark border-2 p-4 rounded-3xl relative h-full"
|
||||
)}
|
||||
>
|
||||
<label
|
||||
className={clsx(
|
||||
alt &&
|
||||
"bg-surface dark:bg-surface-dark border-3 border-secondary-variant dark:border-secondary-variant-dark",
|
||||
"font-body absolute left-11 -top-4 bg-secondary dark:bg-secondary-dark text-primary dark:text-primary-dark font-medium py-1 px-4 rounded-full"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</label>
|
||||
{children}
|
||||
|
@ -1,33 +1,56 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
|
||||
function SubMenu({ items }) {
|
||||
function SubMenu({ items, isVisible, onLinkClick }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="absolute left-0 z-50 invisible transition-all duration-200 -translate-y-6 shadow-sm opacity-0 top-full border-t-3 border-primary dark:border-primary-dark bg-secondary dark:bg-secondary-dark rounded-2xl shadow-surface-variant-dark dark:shadow-primary group-hover:visible group-hover:translate-y-0 group-hover:opacity-100">
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute left-0 z-50 shadow-sm top-full border-t-3 border-primary dark:border-primary-dark bg-secondary dark:bg-secondary-dark rounded-2xl shadow-surface-variant-dark dark:shadow-primary",
|
||||
isVisible ? "block" : "hidden"
|
||||
)}
|
||||
>
|
||||
{items.map((subItem, index) => (
|
||||
<div key={index} className="px-6 py-2 cursor-pointer text-nowrap hover:bg-white dark:hover:bg-secondary-variant-dark first:rounded-t-2xl second-last:rounded-b-2xl first:pt-4 last:pb-4">
|
||||
<div
|
||||
key={index}
|
||||
className="px-6 py-2 cursor-pointer text-nowrap hover:bg-white dark:hover:bg-secondary-variant-dark first:rounded-t-2xl second-last:rounded-b-2xl first:pt-4 last:pb-4"
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
<Link to={subItem.path}>{t(subItem.name)}</Link>
|
||||
</div>
|
||||
))}
|
||||
<svg className="absolute top-0 left-6 mt-[-13px] fill-primary dark:fill-primary-dark" width="16" height="13" viewBox="0 0 16 13" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="absolute top-0 left-6 mt-[-13px] fill-primary dark:fill-primary-dark"
|
||||
width="16"
|
||||
height="13"
|
||||
viewBox="0 0 16 13"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M6.26795 1C7.03775 -0.333332 8.96225 -0.333334 9.73205 0.999999L14.9282 10C15.698 11.3333 14.7358 13 13.1962 13H2.80385C1.26425 13 0.301996 11.3333 1.0718 10L6.26795 1Z" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function MenuBar({ menuItems }) {
|
||||
const { t } = useTranslation();
|
||||
const [activeMenu, setActiveMenu] = useState(null);
|
||||
return (
|
||||
<div className="flex justify-between pt-5 pb-2 text-base font-body">
|
||||
{menuItems.map((item, index) =>
|
||||
<div key={index} className="relative pb-3 cursor-pointer group">
|
||||
{t(item.name)}
|
||||
{item.submenu.length > 0 && <SubMenu items={item.submenu} />}
|
||||
{menuItems.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative pb-3 cursor-pointer"
|
||||
onMouseEnter={() => setActiveMenu(index)}
|
||||
onMouseLeave={() => setActiveMenu(null)}
|
||||
>
|
||||
<Link to={item.path}>{t(item.name)}</Link>
|
||||
{item.submenu.length > 0 && <SubMenu items={item.submenu} isVisible={ activeMenu === index } onLinkClick={() => setActiveMenu(null)}/>}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -42,18 +65,20 @@ MenuBar.propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
})
|
||||
),
|
||||
path: PropTypes.string
|
||||
path: PropTypes.string,
|
||||
})
|
||||
).isRequired
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
SubMenu.propTypes = {
|
||||
items: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired
|
||||
path: PropTypes.string.isRequired,
|
||||
})
|
||||
).isRequired
|
||||
).isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
onLinkClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default MenuBar;
|
||||
|
@ -3,33 +3,40 @@ import { useTranslation } from "react-i18next";
|
||||
import FormBox from "../components/FormBox";
|
||||
import Button from "../components/Button";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import clsx from "clsx";
|
||||
|
||||
function CabinetCreation() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [cabinetId, setCabinetId] = useState({id: "", valid: true});
|
||||
const [cabinetKeyId, setCabinetKeyId] = useState({id: "", valid: true});
|
||||
const [noOfLockers, setNoOfLockers] = useState({number: 0, valid: true});
|
||||
const [cabinetId, setCabinetId] = useState({ id: "", valid: true });
|
||||
const [cabinetKeyId, setCabinetKeyId] = useState({ id: "", valid: true });
|
||||
const [noOfLockers, setNoOfLockers] = useState({ number: 0, valid: true });
|
||||
|
||||
const handleNext = (e) => {
|
||||
e.preventDefault();
|
||||
const idRegex = /^[A-Z]{2}[0-9]{4}$/;
|
||||
if (!idRegex.test(cabinetId.id)) {
|
||||
setCabinetId({id: cabinetId.id, valid: false});
|
||||
setCabinetId({ id: cabinetId.id, valid: false });
|
||||
return;
|
||||
} else if (!idRegex.test(cabinetKeyId.id)) {
|
||||
setCabinetKeyId({id: cabinetKeyId.id, valid: false});
|
||||
setCabinetKeyId({ id: cabinetKeyId.id, valid: false });
|
||||
return;
|
||||
} else if (noOfLockers.number === 0) {
|
||||
setNoOfLockers({number: noOfLockers.number, valid: false});
|
||||
setNoOfLockers({ number: noOfLockers.number, valid: false });
|
||||
return;
|
||||
}
|
||||
navigate("register-lockers", {state: {cabinetId: cabinetId.id, cabinetKeyId: cabinetKeyId.id, noOfLockers: noOfLockers.number}});
|
||||
navigate("register-lockers", {
|
||||
state: {
|
||||
cabinetId: cabinetId.id,
|
||||
cabinetKeyId: cabinetKeyId.id,
|
||||
noOfLockers: noOfLockers.number,
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="w-full h-fit">
|
||||
<motion.div className="w-full h-fit" initial={{ scale: 0.9 }} animate={{ scale: 1 }} exit={{ scale: 0 }}>
|
||||
<FormBox title={t("cabinetCreation")}>
|
||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||
<div className="flex">
|
||||
@ -39,12 +46,32 @@ function CabinetCreation() {
|
||||
<div className="w-full">
|
||||
<input
|
||||
value={cabinetId.id}
|
||||
className={clsx("w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey", !cabinetId.valid && "border-error")}
|
||||
onChange={(e) => setCabinetId({id: e.target.value.toUpperCase(), valid: true })}
|
||||
className={clsx(
|
||||
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||
!cabinetId.valid && "border-error"
|
||||
)}
|
||||
onChange={(e) =>
|
||||
setCabinetId({
|
||||
id: e.target.value.toUpperCase(),
|
||||
valid: true,
|
||||
})
|
||||
}
|
||||
type="text"
|
||||
maxLength={6}
|
||||
/>
|
||||
{!cabinetId.valid && <div className="text-sm text-error ml-3 pt-1">Invalid Cabinet Id</div>}
|
||||
<AnimatePresence initial={false}>
|
||||
{!cabinetId.valid && (
|
||||
<motion.div
|
||||
className="w-1/5 text-sm text-error ml-3 pt-1"
|
||||
initial={{ opacity: 0,scale: 0 }}
|
||||
animate={{ opacity: 1,scale: 1 }}
|
||||
exit={{ opacity: 0,scale: 0 }}
|
||||
key="cabinetIdError"
|
||||
>
|
||||
Invalid Cabinet Id
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
@ -54,12 +81,24 @@ function CabinetCreation() {
|
||||
<div className="w-full">
|
||||
<input
|
||||
value={cabinetKeyId.id}
|
||||
className={clsx("w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey", !cabinetKeyId.valid && "border-error")}
|
||||
onChange={(e) => setCabinetKeyId({id: e.target.value.toUpperCase(), valid: true })}
|
||||
className={clsx(
|
||||
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||
!cabinetKeyId.valid && "border-error"
|
||||
)}
|
||||
onChange={(e) =>
|
||||
setCabinetKeyId({
|
||||
id: e.target.value.toUpperCase(),
|
||||
valid: true,
|
||||
})
|
||||
}
|
||||
type="text"
|
||||
maxLength={6}
|
||||
/>
|
||||
{!cabinetKeyId.valid && <div className="text-sm text-error ml-3 pt-1">Invalid Key Id</div>}
|
||||
{!cabinetKeyId.valid && (
|
||||
<div className="text-sm text-error ml-3 pt-1">
|
||||
Invalid Key Id
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
@ -69,17 +108,31 @@ function CabinetCreation() {
|
||||
<div className="w-full">
|
||||
<input
|
||||
value={noOfLockers.number}
|
||||
className={clsx("w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey", !noOfLockers.valid && "border-error")}
|
||||
onChange={(e) => setNoOfLockers({number: e.target.value, valid: true })}
|
||||
className={clsx(
|
||||
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||
!noOfLockers.valid && "border-error"
|
||||
)}
|
||||
onChange={(e) =>
|
||||
setNoOfLockers({ number: e.target.value, valid: true })
|
||||
}
|
||||
type="number"
|
||||
/>
|
||||
{!noOfLockers.valid && <div className="text-sm text-error ml-3 pt-1">Invalid Number of Lockers</div>}
|
||||
{!noOfLockers.valid && (
|
||||
<div className="text-sm text-error ml-3 pt-1">
|
||||
Invalid Number of Lockers
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button text={t("next")} onClick={(e) => {handleNext(e)}}/>
|
||||
<Button
|
||||
text={t("next")}
|
||||
onClick={(e) => {
|
||||
handleNext(e);
|
||||
}}
|
||||
/>
|
||||
</FormBox>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,43 +1,68 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FormBox from "../components/FormBox";
|
||||
import Button from "../components/Button";
|
||||
import { motion } from "motion/react";
|
||||
import clsx from "clsx";
|
||||
import { AnimatePresence } from "motion/react";
|
||||
|
||||
function CabinetMaintenace() {
|
||||
const navigate = useNavigate();
|
||||
const showToast = useToast();
|
||||
const { t } = useTranslation();
|
||||
const [operation, setOperation] = useState("");
|
||||
const [operation, setOperation] = useState({ value: "", valid: true });
|
||||
|
||||
const handleNext = () => {
|
||||
if (operation === "") {
|
||||
showToast("Please select an operation", "error");
|
||||
return;
|
||||
const handleNext = (e) => {
|
||||
e.preventDefault();
|
||||
if (operation.value === "") {
|
||||
setOperation({ value: operation.value, valid: false });
|
||||
}
|
||||
navigate(operation);
|
||||
navigate(operation.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-fit">
|
||||
<FormBox title={t('cabinetMaintenance')}>
|
||||
<div className="p-2">
|
||||
<div className="my-5">
|
||||
<label className="mr-4 text-lg text-black dark:text-white">{t('operation')}</label>
|
||||
<div>
|
||||
<FormBox title={t("cabinetMaintenance")}>
|
||||
<div className="p-2 pt-7">
|
||||
<div className="flex">
|
||||
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">
|
||||
{t("operation")}
|
||||
</label>
|
||||
<div className="w-full">
|
||||
<select
|
||||
className="w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 border-grey text-grey focus:border-none focus:outline-none"
|
||||
onChange={(e) => setOperation(e.target.value)}
|
||||
defaultValue={operation}
|
||||
className={clsx(
|
||||
"w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 focus:outline-grey border-grey",
|
||||
!operation.valid && "border-error"
|
||||
)}
|
||||
onChange={(e) =>
|
||||
setOperation({ value: e.target.value, valid: true })
|
||||
}
|
||||
defaultValue={operation.value}
|
||||
value={operation.value}
|
||||
>
|
||||
<option value="" disabled>
|
||||
{t('select')}
|
||||
{t("select")}
|
||||
</option>
|
||||
<option value="create">{t('create')}</option>
|
||||
<option value="create">{t("create")}</option>
|
||||
</select>
|
||||
<AnimatePresence initial={false}>
|
||||
{!operation.valid && (
|
||||
<motion.div
|
||||
className="w-1/5 text-sm text-error ml-3 pt-1"
|
||||
initial={{ opacity: 0,scale: 0 }}
|
||||
animate={{ opacity: 1,scale: 1 }}
|
||||
exit={{ opacity: 0,scale: 0 }}
|
||||
key="cabinetIdError"
|
||||
>
|
||||
Invalid Operation
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<Button text={t('next')} onClick={handleNext} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<Button text={t("next")} onClick={(e) => handleNext(e)} />
|
||||
</FormBox>
|
||||
</div>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user