Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: shared component and layout #429

Merged
merged 4 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions components/shared/Cover/index.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions components/shared/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
import { UserInfoForm } from "@/features/user";
import useAuth from "@/hooks/context/useAuth";
import Modal from "./Modal";
import Cover from "./Cover";
import Image from "./Image";

enum HeaderActions {
CHAT = "CHAT",
Expand Down Expand Up @@ -65,7 +65,7 @@ export default function Header({
)}
>
<div className="flex items-center gap-3">
<Cover src="/logo.png" alt="logo" width={40} height={40} />
<Image src="/logo.png" alt="logo" width={40} height={40} priority />
<h2 className="text-primary-100 text-2xl">遊戲微服務大平台</h2>
</div>
<div className="header___actions flex gap-5">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Meta, StoryObj } from "@storybook/react";
import Cover from "./index";
import Image from ".";

const meta: Meta<typeof Cover> = {
title: "Data Display/ Cover",
component: Cover,
const meta: Meta<typeof Image> = {
title: "Data Display/ Image",
component: Image,
tags: ["autodocs"],
args: {
src: "https://images.unsplash.com/photo-1684006231760-3f0290bf42a6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1550&q=80",
alt: "Cover",
alt: "image",
width: 100,
height: 130,
},
Expand All @@ -21,17 +21,17 @@ const meta: Meta<typeof Cover> = {
},
};

type Story = StoryObj<typeof Cover>;
type Story = StoryObj<typeof Image>;

export const Playground: Story = {
render: (args) => <Cover {...args} />,
render: (args) => <Image alt={args.alt} {...args} />,
};

export const DefaultErrorCover: Story = {
render: (args) => <Cover {...args} />,
export const DefaultErrorImage: Story = {
render: (args) => <Image alt={args.alt} {...args} />,
};

DefaultErrorCover.args = {
DefaultErrorImage.args = {
src: "error src",
};

Expand Down
41 changes: 41 additions & 0 deletions components/shared/Image/Image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ReactEventHandler } from "react";
import gameDefaultCoverImg from "@/public/images/game-default-cover.png";
import { cn } from "@/lib/utils";

interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
priority?: boolean;
fill?: boolean;
}

function Image({
alt,
onError,
priority,
fill,
className,
...restProps
}: ImageProps) {
const handleError: ReactEventHandler<HTMLImageElement> = (e) => {
if (e.target instanceof HTMLImageElement) {
e.target.src = gameDefaultCoverImg.src;
}
onError?.(e);
};
return (
<picture>
<img
alt={alt}
className={cn(className, fill && "absolute inset-0 w-full h-full")}
draggable={false}
loading={priority ? "eager" : "lazy"}
decoding="async"
onError={handleError}
// @ts-ignore https://github.com/vercel/next.js/issues/65161
fetchpriority={priority ? "high" : "auto"}
{...restProps}
/>
</picture>
);
}

export default Image;
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import Cover from ".";
import Image from ".";

describe("Cover", () => {
test("should render Cover component", () => {
describe("Image", () => {
test("should render Image component", () => {
render(
<Cover
<Image
src="https://images.unsplash.com/photo-1683125695370-1c7fc9ff1315?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1160"
alt="Cover"
alt="Image"
width={300}
height={300}
/>
);
const cover = screen.getByRole("img");
expect(cover).toBeInTheDocument();
expect(cover).toHaveAccessibleName("Cover");
const image = screen.getByRole("img");
expect(image).toBeInTheDocument();
expect(image).toHaveAccessibleName("Image");
});
});
1 change: 1 addition & 0 deletions components/shared/Image/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./Image";
49 changes: 27 additions & 22 deletions components/shared/SelectBoxGroup/SelectBoxGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,28 +61,33 @@ function SelectBoxGroup<T = string>({
</label>
)}
<ul className={cn("inline-flex gap-2.5", itemWrapperClassName)}>
{items.map((item) => (
<li key={item.key} className={itemClassName}>
<label
className={cn(
"block box-border leading-tight text-primary-200 bg-primary-200/20",
"border-2 border-transparent rounded-lg cursor-pointer",
value === item.value && "border-primary-200 text-primary-50"
)}
htmlFor={`${selectBoxId}_${item.key}`}
aria-checked={value === item.value}
>
{item.label}
</label>
<input
type="checkbox"
id={`${selectBoxId}_${item.key}`}
checked={value === item.value}
className="absolute w-0 h-0 pointer-events-none"
onChange={() => onChange?.(item.value)}
/>
</li>
))}
{items.map((item) => {
const checkboxId = `${selectBoxId}_${item.key}`;
const isChecked = value === item.value;

return (
<li key={item.key} className={itemClassName}>
<label
className={cn(
"block box-border leading-tight text-primary-200 bg-primary-200/20",
"border-2 border-transparent rounded-lg cursor-pointer select-none",
isChecked && "border-primary-200 text-primary-50"
)}
htmlFor={checkboxId}
aria-checked={isChecked}
>
{item.label}
</label>
<input
type="checkbox"
id={checkboxId}
checked={isChecked}
className="[clip:rect(0,0,0,0)] absolute p-0 border-0 w-0 h-0 overflow-hidden"
onChange={() => onChange?.(item.value)}
/>
</li>
);
})}
</ul>
</div>
);
Expand Down
8 changes: 6 additions & 2 deletions containers/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PropsWithChildren, useEffect } from "react";
import { useEffect } from "react";
import { useRouter } from "next/router";
import Header from "@/components/shared/Header";
import Sidebar from "@/components/shared/Sidebar";
Expand All @@ -9,7 +9,7 @@ import SearchBar from "@/components/shared/SearchBar";
import { useToast } from "@/components/shared/Toast";
import { GameListProvider } from "@/features/game";

export default function AppLayout({ children }: PropsWithChildren) {
function AppLayout({ children }: React.PropsWithChildren) {
const toast = useToast();
const router = useRouter();
const {
Expand Down Expand Up @@ -85,3 +85,7 @@ export default function AppLayout({ children }: PropsWithChildren) {
</GameListProvider>
);
}

export default function getAppLayout(page: React.ReactElement) {
return <AppLayout>{page}</AppLayout>;
}
17 changes: 4 additions & 13 deletions features/game/components/GameCardDetailed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PropsWithChildren } from "react";
import gameDefaultCoverImg from "@/public/images/game-default-cover.png";
import { GameType } from "@/requests/games";
import Icon from "@/components/shared/Icon";
import Cover from "@/components/shared/Cover";
import Image from "@/components/shared/Image";

function GameCardDetailed({
children,
Expand All @@ -16,13 +16,11 @@ function GameCardDetailed({
return (
<div className="flex text-white mx-10 px-11 gap-4">
<div className="relative flex items-end justify-end flex-[60%]">
<Cover
<Image
src={img || gameDefaultCoverImg.src}
alt={name}
draggable={false}
priority
fill
className="object-cover"
fill
/>
{children}
</div>
Expand All @@ -48,14 +46,7 @@ function GameCardDetailed({
<ul className="flex gap-3">
<li className="relative w-16 h-10">
{img && (
<Cover
src={img}
alt={name}
draggable={false}
priority
fill
className="object-cover"
/>
<Image src={img} alt={name} className="object-cover" fill />
)}
</li>
</ul>
Expand Down
8 changes: 3 additions & 5 deletions features/game/components/GameCardSimple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ import { PropsWithChildren } from "react";

import gameDefaultCoverImg from "@/public/images/game-default-cover.png";
import { GameType } from "@/requests/games";
import Cover from "@/components/shared/Cover";
import Image from "@/components/shared/Image";

function GameCardSimple({ children, img, name }: PropsWithChildren<GameType>) {
return (
<div className="group">
<div className="relative aspect-game-cover flex items-end justify-end">
<picture className="overflow-hidden">
<Cover
<Image
src={img || gameDefaultCoverImg.src}
alt={name}
draggable={false}
priority
fill
className="object-cover"
fill
/>
</picture>
<div className="opacity-0 group-hover:opacity-100 transition-opacity">
Expand Down
4 changes: 2 additions & 2 deletions features/room/components/RoomCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Room } from "@/requests/rooms";

import Cover from "@/components/shared/Cover";
import Image from "@/components/shared/Image";
import Button, { ButtonSize } from "@/components/shared/Button/v2";
import { useJoinRoom } from "../hooks";

Expand All @@ -22,7 +22,7 @@ function RoomCard({ room, onClick }: Readonly<RoomsCardProps>) {
return (
<div className="bg-primary-700/40 rounded-2xl">
<div className="flex p-4 gap-4 text-primary-50">
<Cover
<Image
className="w-16 h-16 rounded-lg object-cover"
src={room.game.imgUrl}
alt={room.game.name}
Expand Down
1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ if (process.env.ANALYZE === "true") {
});
}

/** @type {import('next').NextConfig} */
const nextConfig = {
i18n,
output: "standalone",
Expand Down
12 changes: 4 additions & 8 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import "@/styles/global.css";
import "@/scripts/whyDidYouRender";

import AxiosProvider from "@/containers/provider/AxiosProvider";
import AppLayout from "@/containers/layout/AppLayout";
import getAppLayout from "@/containers/layout/AppLayout";
import AuthProvider from "@/containers/provider/AuthProvider";
import Startup from "@/containers/util/Startup";
import HistoryProvider from "@/containers/provider/HistoryProvider";
Expand All @@ -20,7 +20,7 @@ import { SocketProvider } from "@/containers/provider/SocketProvider";
import ChatroomContextProvider from "@/containers/provider/ChatroomProvider";

export type NextPageWithProps<P = unknown, IP = P> = NextPage<P, IP> & {
getLayout?: FC<PropsWithChildren>;
getLayout?: (page: React.ReactElement) => React.ReactElement;
Anonymous?: boolean;
};

Expand All @@ -34,9 +34,7 @@ function App({ Component, pageProps }: AppWithProps) {
Component.Anonymous || !!process.env.NEXT_PUBLIC_CI_MODE || false;
const isProduction = env !== Env.PROD ? false : true;

const Layout =
Component.getLayout ??
(({ children }) => <AppLayout>{children}</AppLayout>);
const getLayout = Component.getLayout ?? getAppLayout;

const getHistory = (children: ReactElement) => {
return isProduction ? (
Expand All @@ -58,9 +56,7 @@ function App({ Component, pageProps }: AppWithProps) {
<SocketProvider>
<ChatroomContextProvider>
<Startup isAnonymous={isAnonymous}>
<Layout>
<Component {...pageProps} />
</Layout>
{getLayout(<Component {...pageProps} />)}
{!isProduction && <HistoryList />}
</Startup>
</ChatroomContextProvider>
Expand Down
2 changes: 1 addition & 1 deletion pages/auth/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Login: NextPageWithProps = () => {
return <></>;
};

Login.getLayout = ({ children }) => children;
Login.getLayout = (page) => page;
Login.Anonymous = true;

export default Login;
Expand Down
2 changes: 1 addition & 1 deletion pages/auth/token/[token].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Token: NextPageWithProps = () => {
return <></>;
};

Token.getLayout = ({ children }) => children;
Token.getLayout = (page) => page;
Token.Anonymous = true;

export default Token;
Expand Down
Loading