在Components資料夾,建立Button.tsx,用來構建一個按鈕物件。
'use client'
interface ButtonProps{
label: string;
secondary?: boolean;
fullWidth?: boolean;
large?: boolean;
onClick: () => void;
disabled?: boolean;
outline?: boolean;
}
const Button: React.FC<ButtonProps> = ({
label,
secondary,
fullWidth,
large,
onClick,
disabled,
outline
}) => {
return (
<button disabled={disabled} onClick={onClick} className={`
disabled:opacity-70 disabled:cursor-not-allowed rounded-full font-semibold hover:opacity-80 transition border-2
${fullWidth ? "w-full" : "w-fit"}
${secondary ? "bg-white" : "bg-sky-500"}
${secondary ? "text-black" : "text-white"}
${secondary ? "border-black" : "border-sky-500"}
${large ? "text-xl" : "text-md"}
${large ? "px-5" : "px-4"}
${large ? "py-3" : "py-2"}
${outline ? "bg-transparent" : ""}
${outline ? "border-white" : ""}
${outline ? "text-white" : ""}
`}>{label}</button>
)
}
export default Button
在Components資料夾,建立Modal.tsx,用來構建一個登入或註冊視窗。
'use client'
import { useCallback } from "react";
import { AiOutlineClose } from "react-icons/ai";
import Button from "./Button";
interface ModalProps {
isOpen?: boolean;
onClose: () => void;
onSubmit: () => void;
title?: string;
body?: React.ReactElement;
footer?: React.ReactElement;
actionLabel: string;
disabled?: boolean;
}
const Modal: React.FC<ModalProps> = ({
isOpen,
onClose,
onSubmit,
title,
body,
footer,
actionLabel,
disabled,
}) => {
const handleClose = useCallback(() => {
if (disabled) {
return;
}
onClose();
}, [disabled, onClose]);
const handleSubmit = useCallback(() => {
if (disabled) {
return;
}
onSubmit();
}, [disabled, onSubmit]);
if (!isOpen) {
return null;
}
return (
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none bg-neutral-800 bg-opacity-70">
<div className="relative w-full lg:w-3/6 my-6 mx-auto lg: max-w-3xl h-full lg:h-auto">
<div className="h-full lg:h-auto border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-black outline-none focus:outline-none">
<div className="flex items-center justify-between p-10 rounded-t">
<h3 className="text-3xl font-semibold text-white">{title}</h3>
<button
onClick={handleClose}
className="p-1 ml-auto bottom-0 text-white hover:opacity-70 transition"
>
<AiOutlineClose size={20} />
</button>
</div>
<div className="relative p-10 flex-auto">{body}</div>
<div className="flex flex-col gap-2 p-10">
<Button
disabled={disabled}
label={actionLabel}
secondary
fullWidth
large
onClick={handleSubmit}
/>
{footer}
</div>
</div>
</div>
</div>
);
};
export default Modal;
最後修改_app.tsx,顯示我們剛寫好的視窗和按鈕。
import Layout from "@/Components/Layout"
import Modal from "@/Components/Modal"
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<Layout>
<Modal actionLabel="Submit" isOpen title="Test Modal"/>
<Component {...pageProps} />
</Layout>
)
}
啟動專案,可以看到這個視窗,這些按鈕還沒有功能點下去沒有反應。
我們看完視窗呈現的狀態後,將layout.tsx恢復原狀。
import Layout from "@/Components/Layout"
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
在Components資料夾中,建立Input.tsx,用來構成登入表格中的輸入欄位。
interface InputProps{
placeholder?: string;
value?: string;
type?: string;
disabled?: boolean;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const Input: React.FC<InputProps> = ({
placeholder,
value,
type,
disabled,
onChange
}) => {
return (
<input
disabled={disabled}
onChange={onChange}
value={value}
placeholder={placeholder}
type={type}
className="w-full p-4 text-lg bg-black border-2 border-neutral-800 rounded-md outline-none text-white focus:border-2 transition disabled:bg-neutral-900 disabled:opacity-70 disabled:cursor-not-allowed"
/>
)
}
export default Input
接下來安裝zustand,用一種簡單直觀的方式管理應用程序的state,因為我們的登入表單的資料由一個個component所組成,我們需要有一個東西能夠方便的管理裏面的內容以及設定。
在根目錄下,建立Hooks資料夾。
在Hooks資料夾下,新增useLoginModal.tsx,將LoginModal登入表單預設為不顯示,觸發onOpen事件時會顯示表單,觸發onClose事件會關閉表單。
import { create } from "zustand";
interface LoginModalStore{
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
}
const useLoginModal = create<LoginModalStore>((set) => ({
isOpen: false,
onOpen: () => set({isOpen: true}),
onClose: () => set({isOpen: false}),
}));
export default useLoginModal;
在Components資料夾下,新增Modals資料夾。
在Modals資料夾下,新增LoginModal.tsx,我們使用了Input和Modal來組成我們的登入表單。
'use client'
import useLoginModal from "@/Hooks/useLoginModal";
import { useCallback, useState } from "react";
import Input from "../Input";
import Modal from "../Modal";
const LoginModal = () => {
const loginModal = useLoginModal();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const onSubmit = useCallback(async () => {
try {
setIsLoading(true);
loginModal.onClose();
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
}, [loginModal]);
const bodyContent = (
<div className="flex flex-col gap-4">
<Input
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
value={email}
disabled={isLoading}
/>
<Input
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
value={password}
disabled={isLoading}
/>
</div>
);
return (
<Modal
disabled={isLoading}
isOpen={loginModal.isOpen}
title="Login"
actionLabel="Sign in"
onClose={loginModal.onClose}
onSubmit={onSubmit}
body={bodyContent}
/>
);
};
export default LoginModal;
為了看登入表單的外觀如何,我們對_app.tsx和useLoginModal.tsx進行修改。
_app.tsx
import Layout from "@/Components/Layout"
import LoginModal from "@/Components/Modals/LoginModal"
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<Layout>
<LoginModal />
<Component {...pageProps} />
</Layout>
)
}
useLoginModal.tsx
import { create } from "zustand";
interface LoginModalStore{
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
}
const useLoginModal = create<LoginModalStore>((set) => ({
isOpen: true,
onOpen: () => set({isOpen: true}),
onClose: () => set({isOpen: false}),
}));
export default useLoginModal;
我們的登入表單可以輸入Email和密碼,現在按下表單右上角的X可以關閉表單了。
觀察變化結束後,把useLoginModal.tsx改回來。
useLoginModal.tsx
import { create } from "zustand";
interface LoginModalStore{
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
}
const useLoginModal = create<LoginModalStore>((set) => ({
isOpen: false,
onOpen: () => set({isOpen: true}),
onClose: () => set({isOpen: false}),
}));
export default useLoginModal;