Skip to content

Commit

Permalink
Merge pull request #417 from Game-as-a-Service/feature/carousel-v2
Browse files Browse the repository at this point in the history
feature: carousel v2
  • Loading branch information
JohnsonMao authored Oct 27, 2024
2 parents 6dcfc2f + f9bea0f commit 38519ae
Show file tree
Hide file tree
Showing 14 changed files with 489 additions and 131 deletions.
11 changes: 9 additions & 2 deletions components/lobby/CreateRoomModal/CreateRoomModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const initialRoomFormState = {
maxPlayers: 0,
};

export default function CreateRoomModal() {
export default function CreateRoomModal({ tabIndex }: { tabIndex: number }) {
const [showThisModal, setshowThisModal] = useState(false);
const [showGameListModal, setShowGameListModal] = useState(false);
const [gameList, setGameList] = useState<GameType[]>([]);
Expand Down Expand Up @@ -99,7 +99,14 @@ export default function CreateRoomModal() {

return (
<>
<Button onClick={() => setshowThisModal(true)}>開創房間</Button>
<button
type="button"
className="block w-full text-left px-4 py-1 hover:bg-primary-900/20 cursor-pointer"
tabIndex={tabIndex}
onClick={() => setshowThisModal(true)}
>
開設新房間
</button>
<Modalow
title="create-room"
hasTitle={false}
Expand Down
23 changes: 2 additions & 21 deletions components/shared/Button/v2/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { ReactNode, forwardRef, useCallback } from "react";
import React, { forwardRef, useCallback } from "react";
import { cn } from "@/lib/utils";
import BoxFancy, { BoxFancyBorderGradientVariant } from "../../BoxFancy";
import Icon, { IconName } from "@/components/shared/Icon";
import { PolymorphicRef } from "@/lib/types";

export enum ButtonVariant {
Expand Down Expand Up @@ -38,9 +37,6 @@ const buttonSizeClasses: Record<ButtonSize, string> = {
interface BaseButtonProps {
variant?: ButtonVariant | `${ButtonVariant}`;
size?: ButtonSize;
icon?: ReactNode;
iconName?: IconName;
iconClassName?: string;
}

type ButtonProps = BaseButtonProps & React.ComponentPropsWithoutRef<"button">;
Expand All @@ -50,24 +46,12 @@ type InnerButtonComponent = (
ref?: PolymorphicRef<"button">
) => React.ReactElement | null;

const iconTypeClasses: Record<ButtonVariant, string> = {
primary:
"stroke-primary-700 hover:stroke-primary-50 active:stroke-primary-50",
primaryTransparent:
"stroke-primary-700 hover:stroke-primary-700 active:stroke-primary-700",
secondary: "stroke-primary-200 disabled:stroke-grey-500",
highlight: "stroke-primary-50",
};

const InteralButton: InnerButtonComponent = (
{
variant = ButtonVariant.PRIMARY,
size = ButtonSize.REGULAR,
icon,
iconName,
disabled,
className,
iconClassName,
children,
onClick,
...otherButtonAttributes
Expand All @@ -91,10 +75,8 @@ const InteralButton: InnerButtonComponent = (
commonDisabledClasses,
buttonTypeClasses[variant],
buttonSizeClasses[size],
iconTypeClasses[variant],
className
);
const iconClasses = cn("w-6 h-6 stroke-inherit", iconClassName);

const borderGradientColor: BoxFancyBorderGradientVariant =
variant === ButtonVariant.SECONDARY && !disabled ? "purple" : "none";
Expand All @@ -110,8 +92,7 @@ const InteralButton: InnerButtonComponent = (
disabled={disabled}
{...otherButtonAttributes}
>
{icon || (iconName && <Icon name={iconName} className={iconClasses} />)}
<span>{children}</span>
{children}
</BoxFancy>
);
};
Expand Down
7 changes: 5 additions & 2 deletions components/shared/Carousel/v2/Carousel.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ const meta: Meta<typeof Carousel> = {
},
],
args: {
renderKey: (props: any) => props.name,
items: [{ name: "TEST 1" }, { name: "TEST 2" }, { name: "TEST 3" }],
items: [
{ id: 1, name: "TEST 1" },
{ id: 2, name: "TEST 2" },
{ id: 3, name: "TEST 3" },
],
Component: Card,
},
argTypes: {
Expand Down
95 changes: 14 additions & 81 deletions components/shared/Carousel/v2/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,23 @@
import { CSSProperties, FC, Key, useEffect, useRef, useState } from "react";
import Icon from "@/components/shared/Icon";
import CarouselButton from "./CarouselButton";
import CarouselPagination from "./CarouselPagination";
import CarouselMain from "./CarouselMain";
import { CarouselProvider } from "./CarouselContext";
import { CarouselProps, TItem } from "./Carousel.type";

interface CarouselProps<Item extends Record<string, unknown>> {
items: Item[];
renderKey: (item: Item) => Key;
Component: FC<Item>;
}

export default function Carousel<Item extends Record<string, unknown>>({
export default function Carousel<Item extends TItem>({
items,
renderKey,
Component,
}: Readonly<CarouselProps<Item>>) {
const [showIndex, setShowIndex] = useState(0);
const [maxWidth, setMaxWidth] = useState(0);
const carouselRef = useRef<HTMLDivElement>(null);

const handleChangePage = (action: "prev" | "next") => () => {
const maxIndex = items.length - 1;

switch (action) {
case "prev":
setShowIndex((preIndex) => {
const newIndex = preIndex - 1;
return newIndex > -1 ? newIndex : maxIndex;
});
break;
case "next":
setShowIndex((preIndex) => {
const newIndex = preIndex + 1;
return newIndex <= maxIndex ? newIndex : 0;
});
break;
default:
}
};

const buttonClassName =
"p-2.5 shrink-0 bg-white/4 shadow-default rounded-2xl";
const buttonIconClassName = "stroke-white w-6 h-6 pointer-events-none";

useEffect(() => {
setMaxWidth(carouselRef.current?.clientWidth || 0);
}, []);

return (
<div className="flex items-center">
<button
type="button"
className={buttonClassName}
onClick={handleChangePage("prev")}
>
<Icon name="NavArrowLeft" className={buttonIconClassName} />
</button>
<div ref={carouselRef} className="overflow-hidden w-full">
<ul
className="flex transition-transform translate-x-[var(--translate-x)]"
style={
{
"--translate-x": `${showIndex * maxWidth * -1}px`,
} as CSSProperties
}
>
{Array.isArray(items) &&
items.map((item) => (
<li
key={renderKey(item)}
className="shrink-0 w-[var(--max-width)]"
style={
{
"--max-width": `${maxWidth}px`,
} as CSSProperties
}
>
<Component {...item} />
</li>
))}
</ul>
<CarouselProvider items={items} renderKey={renderKey} Component={Component}>
<div className="flex items-center">
<CarouselButton type="previous" />
<CarouselMain>
<CarouselPagination />
</CarouselMain>
<CarouselButton type="next" />
</div>
<button
type="button"
className={buttonClassName}
onClick={handleChangePage("next")}
>
<Icon name="NavArrowRight" className={buttonIconClassName} />
</button>
</div>
</CarouselProvider>
);
}
51 changes: 51 additions & 0 deletions components/shared/Carousel/v2/Carousel.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Dispatch, FC, Key, PropsWithChildren } from "react";
import { IconName } from "@/components/shared/Icon";

export type TItem = Record<string, unknown> & { id: Key };

export type CarouselItemProps<Item extends TItem> = Item & {
showIndex: number;
index: number;
};

export interface ICarouselContext<Item extends TItem = TItem> {
items: Item[];
showIndex: number;
renderKey: (item: Item) => Key;
Component: FC<CarouselItemProps<Item>>;
}

export interface CarouselProps<Item extends TItem = TItem> {
items: ICarouselContext<Item>["items"];
Component: ICarouselContext<Item>["Component"];
renderKey?: ICarouselContext<Item>["renderKey"];
}

export const enum CarouselActionType {
Previous = "previous",
Next = "next",
SetPage = "setPage",
UpdateItems = "updateItems",
}

export type TCarouselAction<Item extends TItem> =
| { type: CarouselActionType.Previous }
| { type: CarouselActionType.Next }
| { type: CarouselActionType.SetPage; payload: { page: number } }
| { type: CarouselActionType.UpdateItems; payload: { items: Item[] } };

export type TCarouselDispatchContext<Item extends TItem = TItem> = Dispatch<
TCarouselAction<Item>
>;

export interface CarouselProviderProps<Item extends TItem>
extends PropsWithChildren {
items: ICarouselContext<Item>["items"];
Component: ICarouselContext<Item>["Component"];
renderKey?: ICarouselContext<Item>["renderKey"];
}

export interface CarouselButtonConfig {
iconName: IconName;
actionType: CarouselActionType.Previous | CarouselActionType.Next;
}
42 changes: 42 additions & 0 deletions components/shared/Carousel/v2/CarouselButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Icon from "@/components/shared/Icon";
import { useCarouselDispatch } from "./CarouselContext";
import { CarouselActionType, CarouselButtonConfig } from "./Carousel.type";

export const enum CarouselButtonType {
Previous = "previous",
Next = "next",
}

interface CarouselButtonProps {
type: CarouselButtonType | `${CarouselButtonType}`;
}

const configs: Record<CarouselButtonType, CarouselButtonConfig> = {
[CarouselButtonType.Previous]: {
iconName: "NavArrowLeft",
actionType: CarouselActionType.Previous,
},
[CarouselButtonType.Next]: {
iconName: "NavArrowRight",
actionType: CarouselActionType.Next,
},
};

export default function CarouselButton({
type,
}: Readonly<CarouselButtonProps>) {
const dispatch = useCarouselDispatch();
const config = configs[type];
return (
<button
type="button"
className="p-2.5 shrink-0 bg-white/4 shadow-default rounded-2xl"
onClick={() => dispatch({ type: config.actionType })}
>
<Icon
name={config.iconName}
className="stroke-white w-6 h-6 pointer-events-none"
/>
</button>
);
}
Loading

0 comments on commit 38519ae

Please sign in to comment.