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

[ Feat ] Item 컴포넌트에 수정 기능 Edit 아이콘 추가 #122

Merged
merged 4 commits into from
Oct 22, 2024
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
3 changes: 2 additions & 1 deletion src/common/component/CheckBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { boxStyle } from "@/common/component/CheckBox/index.css";
import clsx from "clsx";
import type { HTMLAttributes } from "react";

interface CheckBoxProps
Expand All @@ -11,7 +12,7 @@ const CheckBox = ({ checked, onChange, ...props }: CheckBoxProps) => {
return (
<input
{...props}
className={boxStyle}
className={clsx(boxStyle, props.className)}
type="checkbox"
checked={checked}
onChange={() => onChange?.(!checked)}
Expand Down
47 changes: 41 additions & 6 deletions src/shared/component/ProblemList/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
"use client";

import { IcnEdit } from "@/asset/svg";
import CheckBox from "@/common/component/CheckBox";
import {
checkboxStyle,
editIconStyle,
itemStyle,
textStyle,
titleStyle,
wrongCheckBoxStyle,
} from "@/shared/component/ProblemList/index.css";
import useA11yHoverHandler from "@/shared/hook/useA11yHandler";
import type { Problem } from "@/shared/type";
import { getTierImage } from "@/shared/util/img";
import clsx from "clsx";

import { format } from "date-fns";
import Link from "next/link";

type ProblemListItemProps = Problem & { className?: string };
type ProblemListItemProps = Problem & {
onEdit?: () => void;
isOwner?: boolean;
className?: string;
};

const JSX_BY_STATUS = {
wrong: <input type="checkbox" disabled className={wrongCheckBoxStyle} />,
unsolved: <CheckBox checked={false} style={{ cursor: "default" }} />,
solved: <CheckBox checked={true} style={{ cursor: "default" }} />,
wrong: (
<input
type="checkbox"
disabled
className={clsx(checkboxStyle, wrongCheckBoxStyle)}
/>
),
unsolved: <CheckBox checked={false} className={checkboxStyle} />,
solved: <CheckBox checked={true} className={checkboxStyle} />,
};

const ProblemListItem = ({
Expand All @@ -32,17 +46,29 @@ const ProblemListItem = ({
accuracy,
memberCount,
submitMemberCount,
onEdit,
isOwner = false,
}: ProblemListItemProps) => {
const Icon = getTierImage(level);

const isExpired = new Date(endDate).getTime() - new Date().getTime() <= 0;

const status = solved ? "solved" : isExpired ? "wrong" : "unsolved";

const { isActive, handleBlur, handleFocus, handleMouseOut, handleMouseOver } =
useA11yHoverHandler();

return (
<li
aria-label={`${level}: ${title}`}
className={clsx(itemStyle, className)}
onFocus={handleFocus}
onBlur={handleBlur}
onMouseOver={handleMouseOver}
onMouseLeave={handleMouseOut}
aria-label={`문제: ${title}`}
className={clsx(
itemStyle({ isActive: isOwner ? isActive : false }),
className,
)}
>
<Icon width={25} height={32} />
<Link
Expand All @@ -57,6 +83,15 @@ const ProblemListItem = ({
<span className={textStyle}>{`${submitMemberCount}/${memberCount}`}</span>
<span className={textStyle}>{accuracy}</span>
{JSX_BY_STATUS[status]}

{isOwner && (
<IcnEdit
onClick={onEdit}
className={editIconStyle({ isActive })}
width={24}
height={24}
/>
)}
</li>
);
};
Expand Down
110 changes: 74 additions & 36 deletions src/shared/component/ProblemList/ProblemList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,89 @@ import type { Problem, TierDetail } from "@/shared/type";
import type { Meta } from "@storybook/react";
import ProblemList from ".";

const data: Problem[] = [
{
problemId: 1,
title: "트리에서의 동적 계획법",
startDate: "2024-10-10",
endDate: "2024-11-01",
level: "silver 1",
solved: false,
submitMemberCount: 50,
memberCount: 200,
accuracy: 25,
},
{
problemId: 2,
title: "트리에서의 동적 계획법",
startDate: "2024-10-10",
endDate: "2024-10-14",
level: "diamond 1",
solved: false,
submitMemberCount: 50,
memberCount: 200,
accuracy: 25,
},
{
problemId: 3,
title: "트리에서의 동적 계획법",
startDate: "2024-10-10",
endDate: "2024-11-01",
level: "gold 1",
solved: true,
submitMemberCount: 50,
memberCount: 200,
accuracy: 25,
},
];

const meta: Meta<typeof ProblemList> = {
title: "Shared/ProblemList",
component: ProblemList,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
args: {},
} satisfies Meta<typeof ProblemList>;
export default meta;

export const Default = {
render: () => {
const data: Problem[] = [
{
problemId: 1,
title: "트리에서의 동적 계획법",
startDate: "2024-10-10",
endDate: "2024-11-01",
level: "silver 1",
solved: false,
submitMemberCount: 50,
memberCount: 200,
accuracy: 25,
},
{
problemId: 2,
title: "트리에서의 동적 계획법",
startDate: "2024-10-10",
endDate: "2024-10-14",
level: "diamond 1",
solved: false,
submitMemberCount: 50,
memberCount: 200,
accuracy: 25,
},
{
problemId: 3,
title: "트리에서의 동적 계획법",
startDate: "2024-10-10",
endDate: "2024-11-01",
level: "gold 1",
solved: true,
submitMemberCount: 50,
memberCount: 200,
accuracy: 25,
},
];
return (
<ProblemList>
{data.map(
({
problemId,
title,
startDate,
endDate,
level,
solved,
submitMemberCount,
memberCount,
accuracy,
}) => (
<ProblemList.Item
key={problemId}
problemId={problemId}
title={title}
startDate={startDate}
endDate={endDate}
level={level as TierDetail}
solved={solved}
memberCount={memberCount}
submitMemberCount={submitMemberCount}
accuracy={accuracy}
/>
),
)}
</ProblemList>
);
},
};

export const Owner = {
render: () => {
return (
<ProblemList>
{data.map(
Expand All @@ -73,6 +110,7 @@ export const Default = {
memberCount={memberCount}
submitMemberCount={submitMemberCount}
accuracy={accuracy}
isOwner={true}
/>
),
)}
Expand Down
69 changes: 58 additions & 11 deletions src/shared/component/ProblemList/index.css.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import { theme } from "@/styles/themes.css";
import { style } from "@vanilla-extract/css";
import { recipe } from "@vanilla-extract/recipes";

export const itemStyle = style({
display: "grid",
gridTemplateColumns: "0.6fr 2.7fr 2fr 2fr 2fr 2fr",
alignItems: "center",
justifyContent: "space-between",
gap: "2.4rem",
export const itemStyle = recipe({
base: {
position: "relative",

display: "grid",
gridTemplateColumns: "0.6fr 2.7fr 2fr 2fr 2fr 2fr",
alignItems: "center",
gap: "2.4rem",

width: "100%",

width: "100%",
padding: "0.8rem 1.6rem",

padding: "0.8rem 1.6rem",
transition: "all 0.3s ease",
},
variants: {
isActive: {
true: {
backgroundColor: "rgba(34, 39, 52, 1)",
},
},
},
});

export const textStyle = style({
Expand All @@ -19,9 +32,12 @@ export const textStyle = style({

overflow: "hidden",
textOverflow: "ellipsis",
textAlign: "center",
});

export const titleStyle = style({
textAlign: "start",

whiteSpace: "nowrap",
});

Expand All @@ -32,9 +48,6 @@ export const wrongCheckBoxStyle = style({
alignItems: "center",
justifyContent: "center",

width: "14px",
height: "14px",

border: "none",
borderRadius: "1px",

Expand All @@ -46,3 +59,37 @@ export const wrongCheckBoxStyle = style({
color: theme.color.white,
},
});

export const checkboxStyle = style({
width: "1.4rem",
height: "1.4rem",

justifySelf: "start",

cursor: "pointer",
});

export const editIconStyle = recipe({
base: {
position: "absolute",

top: 0,
bottom: 0,
right: 20,

margin: "auto 0",

opacity: 0,

cursor: "pointer",

transition: "all 0.3s ease",
},
variants: {
isActive: {
true: {
opacity: 1,
},
},
},
});