Skip to content

Commit

Permalink
[Motion] feat: 모션 수정 치트 레이아웃 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
hyeongjun3 committed Apr 25, 2024
1 parent f8d6648 commit a701132
Show file tree
Hide file tree
Showing 6 changed files with 435 additions and 214 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"framer-motion": "^11.1.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.3",
"react-router-dom": "^6.22.3"
},
"devDependencies": {
Expand Down
7 changes: 6 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import CheatLayout from "@Components/layout/CheatLayout";
import Router from "./Router";

function App() {
return <Router />;
return (
<CheatLayout>
<Router />
</CheatLayout>
);
}

export default App;
2 changes: 2 additions & 0 deletions src/components/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface ButtonProps extends ComponentProps<"button"> {
export default function Button({
variant,
className,
type = "button",
...restprops
}: ButtonProps) {
const getBoxStyle = () => {
Expand Down Expand Up @@ -41,6 +42,7 @@ export default function Button({
getTypo(),
className
)}
type={type}
{...restprops}
/>
);
Expand Down
186 changes: 186 additions & 0 deletions src/components/layout/CheatLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import Button from "@Components/button/Button";
import useModal from "@Hooks/useModal";
import { ComponentProps, ReactNode, forwardRef } from "react";
import { createPortal } from "react-dom";
import {
FormProvider,
useFieldArray,
useForm,
useFormContext,
} from "react-hook-form";
import {
MotionJsonKeyType,
MotionJsonType,
MotionJsonValue,
getMotionJson,
} from "@Utils/motionManager";
import { ObjectClean } from "@Utils/objectExtension";
import { setLocalStorage } from "@Utils/storageExtension";
import { STORAGE_KEYS } from "@Constant/storageKeys";

interface CheatLayoutProps {
children: ReactNode;
}

export default function CheatLayout({ children }: CheatLayoutProps) {
return (
<>
{createPortal(
<div className="absolute top-1 left-0 right-0 py-3 flex justify-center">
<MotionSettingButton />
</div>,
document.body
)}
{children}
</>
);
}

function MotionSettingButton() {
const { isOpen, open, close } = useModal();

return (
<>
<Button variant={"primary"} onClick={open}>
모션 설정
</Button>
<MotionSettingModal isOpen={isOpen} onClose={close} />
</>
);
}

interface MotionSettingModalProps {
isOpen: boolean;
onClose: () => void;
}

function MotionSettingModal({ isOpen, onClose }: MotionSettingModalProps) {
const motionJson = getMotionJson();
const form = useForm({ defaultValues: motionJson });

const onSubmit = (data: typeof motionJson) => {
console.log(data);
console.log(ObjectClean(data));
setLocalStorage(STORAGE_KEYS.motion, ObjectClean(data));
};

const reset = () => form.reset(motionJson);

if (!isOpen) return <></>;
return (
<>
{createPortal(
<FormProvider {...form}>
<form
className="absolute bg-[rgb(209,213,219)] top-1 flex w-[calc(100%-16px)] left-0 right-0 m-auto py-4 px-2 rounded-lg flex-col gap-2 z-10"
onSubmit={form.handleSubmit(onSubmit)}
>
<div className="flex justify-between w-full items-center">
<p className="font-yClover text-base font-bold">모션 설정</p>
<div className="flex gap-2">
<Button variant={"primary"} type="submit">
저장
</Button>
<Button variant={"primary"} onClick={reset}>
초기화
</Button>
<Button variant={"primary"}>내보내기</Button>
<Button variant={"primary"} onClick={onClose}>
닫기
</Button>
</div>
</div>
<div className="flex flex-col h-[500px] overflow-auto gap-1">
{Object.entries(motionJson).map(([key, value]) => {
return (
<SettingArea
key={key}
keyName={key as MotionJsonKeyType}
value={value}
/>
);
})}
</div>
</form>
</FormProvider>,
document.body
)}
</>
);
}

function SettingArea<T extends MotionJsonKeyType>({
keyName,
}: {
keyName: T;
value: MotionJsonValue;
}) {
const { register, control } = useFormContext<MotionJsonType>();
const { fields, remove, append } = useFieldArray({
control,
name: `${keyName}.to`,
});

return (
<div className="flex flex-col gap-2 rounded-sm border border-sky-100 border-solid py-2 px-1">
<div className="flex flex-row items-center w-full gap-4">
<p className="font-yClover font-normal text-[14px] w-[180px]">
{keyName}
</p>
<Button variant={"primary"} onClick={() => append({})}>
+
</Button>
</div>
<SettingInput
label="duration"
type="number"
step={0.01}
{...register(`${keyName}.duration`)}
/>
<SettingInput
label="delay"
type="number"
step={0.01}
{...register(`${keyName}.delay`)}
/>
<div className="border-sky-100 border-solid flex flex-col gap-2">
{fields.map((item, idx) => {
return (
<div key={item.id} className="flex flex-row gap-2">
<div className="flex flex-col">
{["x", "y", "rotate", "opacity", "ease"].map((key) => {
const formName = `${keyName}.to[${idx}].${key}` as const;
return (
<SettingInput
key={key}
label={key}
{...register(formName)}
/>
);
})}
</div>
<Button variant={"primary"} onClick={() => remove(idx)}>
-
</Button>
</div>
);
})}
</div>
</div>
);
}

interface SettingInputProps extends ComponentProps<"input"> {
label: string;
}

const SettingInput = forwardRef<HTMLInputElement, SettingInputProps>(
({ label, ...props }, ref) => {
return (
<div className="flex gap-2 items-center">
<label className="w-[80px] font-yClover">{label}</label>
<input ref={ref} className="p-1" placeholder="입력" {...props} />
</div>
);
}
);
11 changes: 11 additions & 0 deletions src/hooks/useModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useCallback, useState } from "react";

export default function useModal() {
const [isOpen, setIsOpen] = useState(false);

const open = useCallback(() => setIsOpen(true), []);

const close = useCallback(() => setIsOpen(false), []);

return { isOpen, open, close };
}
Loading

0 comments on commit a701132

Please sign in to comment.