diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index 9eda501..e66754d 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -5,6 +5,7 @@ import { Button } from "@/shared/button"; import { FeatureButton } from "@/shared/FeatureButton"; import { Icon } from "@/shared/Icon"; import { Input } from "@/shared/input"; +import { SelectModuleCard } from "@/shared/SelectModuleCard"; import { SpecialFeatureCard } from "@/shared/SpecialFeatureCard"; import { InputStatus } from "@/types/input"; import { useState } from "react"; @@ -101,6 +102,25 @@ export default function Home() { + + console.log("일간 플래너 클릭")} + /> + + console.log("일간 플래너 클릭")} + /> ); } diff --git a/src/lib/variants/card.presets.ts b/src/lib/variants/card.presets.ts deleted file mode 100644 index 716f720..0000000 --- a/src/lib/variants/card.presets.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { CardPreset } from "@/types/card"; -import type { VariantProps } from "class-variance-authority"; -import { specialFeatureCardVariants } from "./card.specialFeatureCard"; - -/** cva VariantProps 원본에서 노출할 옵션만 선택 */ -type SpecialFeatureVariantProps = VariantProps; -type SpecialFeatureOpts = Partial>; - -/** 프리셋별 오버로드 (자동완성/가드 정확) */ -export function getCardClasses(preset: "specialFeature", opts?: SpecialFeatureOpts): string; - -// 구현체 -export function getCardClasses(preset: CardPreset, opts: SpecialFeatureOpts = {}): string { - switch (preset) { - case "specialFeature": { - const { size } = opts; - return specialFeatureCardVariants({ size }); - } - } -} diff --git a/src/lib/variants/card.selectModule.ts b/src/lib/variants/card.selectModule.ts new file mode 100644 index 0000000..d67b95f --- /dev/null +++ b/src/lib/variants/card.selectModule.ts @@ -0,0 +1,54 @@ +import { cva, type VariantProps } from "class-variance-authority"; + +export const selectModuleCardRoot = cva( + [ + "rounded-[2rem] border bg-[var(--color-white)] shadow-md", + "transition-all duration-200", + "p-8 h-[35rem]", + "cursor-pointer hover:shadow-md focus-visible:outline-none", + "focus-visible:ring-2 focus-visible:ring-[var(--color-ring)]", + "hover:scale-[1.02] hover:-translate-y-1", + "hover:[box-shadow:var(--shadow-hover)]", + ].join(" "), + { + variants: { + kind: { + module: "w-[28rem]", + design: "w-[23rem] p-10", + }, + }, + defaultVariants: { kind: "module" }, + }, +); + +export const selectModuleCardTitle = cva("text-[var(--color-gray-900)]", { + variants: { + kind: { module: "t-22-b", design: "t-22-b" }, + }, + defaultVariants: { kind: "module" }, +}); + +export const selectModuleCardSubtitle = cva("t-14-m", { + variants: { + state: { + select: "text-[var(--color-blue-600)]", + noSelect: "text-[var(--color-danger-600)]", + }, + }, + defaultVariants: { state: "select" }, +}); + +export const selectModuleCardDescription = cva("text-[var(--color-gray-600)]", { + variants: { + kind: { module: "t-14-m", design: "t-14-m" }, + }, + defaultVariants: { kind: "module" }, +}); + +export const selectModuleCardImageWrap = cva( + ["mt-15 w-full overflow-hidden", "aspect-[18/12] grid place-items-center", "relative"].join(" "), +); + +export const selectModuleCardImage = cva("h-full w-full object-contain"); + +export type SelectModuleCardRootVariants = VariantProps; diff --git a/src/shared/SelectModuleCard.tsx b/src/shared/SelectModuleCard.tsx new file mode 100644 index 0000000..3390b36 --- /dev/null +++ b/src/shared/SelectModuleCard.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { + selectModuleCardDescription, + selectModuleCardImage, + selectModuleCardImageWrap, + selectModuleCardRoot, + selectModuleCardSubtitle, + selectModuleCardTitle, +} from "@/lib/variants/card.selectModule"; +import type { SelectModuleCardProps } from "@/types/card"; +import Image from "next/image"; +import * as React from "react"; + +export const SelectModuleCard = React.forwardRef( + ( + { + className, + title, + subtitle, + description, + imageSrc, + imageAlt, + kind = "module", + subtitleState = "select", + children, + ...native + }, + ref, + ) => { + return ( +
+
+

{title}

+ {subtitle ? ( +

{subtitle}

+ ) : null} + {description ? ( +

{description}

+ ) : null} +
+ + {children} + + {imageSrc ? ( +
+ {imageAlt +
+ ) : null} +
+ ); + }, +); + +SelectModuleCard.displayName = "SelectModuleCard"; diff --git a/src/shared/SpecialFeatureCard.tsx b/src/shared/SpecialFeatureCard.tsx index 6653d11..c3a55a1 100644 --- a/src/shared/SpecialFeatureCard.tsx +++ b/src/shared/SpecialFeatureCard.tsx @@ -1,14 +1,14 @@ "use client"; import { cn } from "@/lib/utils"; import { specialFeatureCardVariants } from "@/lib/variants/card.specialFeatureCard"; -import type { SpecialFeatureCardProps } from "@/types/special-feature-card"; +import type { SpecialFeatureCardProps } from "@/types/card"; import * as React from "react"; export const SpecialFeatureCard = React.forwardRef( (props, ref) => { const { className, icon, title, description, children, ...native } = props; - const classes = specialFeatureCardVariants({}); + const classes = specialFeatureCardVariants({ size: "lg" }); return (
diff --git a/src/shared/card.tsx b/src/shared/card.tsx deleted file mode 100644 index 2b79407..0000000 --- a/src/shared/card.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client"; - -import { cn } from "@/lib/utils"; -import { getCardClasses } from "@/lib/variants/card.presets"; -import type { CardProps } from "@/types/card"; -import * as React from "react"; - -export const Card = React.forwardRef((props, ref) => { - const { className, preset, size = "lg", children, ...native } = props; - - if (preset === "specialFeature") { - const classes = getCardClasses("specialFeature", { size }); - return ( -
- {children} -
- ); - } - - // 기본 카드 (fallback) - return ( -
- {children} -
- ); -}); -Card.displayName = "Card"; - -// 카드 내부 컴포넌트들 -function CardIcon({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function CardTitle({ className, ...props }: React.ComponentProps<"h3">) { - return ( -

- ); -} - -function CardDescription({ className, ...props }: React.ComponentProps<"p">) { - return ( -

- ); -} - -function CardContent({ className, ...props }: React.ComponentProps<"div">) { - return

; -} - -function CardFooter({ className, ...props }: React.ComponentProps<"div">) { - return
; -} - -export { CardContent, CardDescription, CardFooter, CardIcon, CardTitle }; diff --git a/src/styles/globals.css b/src/styles/globals.css index 12c4d0c..a6061ac 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -74,6 +74,7 @@ /* === Radius & Shadow === */ --radius-2xl: 1rem; --shadow-soft: 0 6px 16px rgba(0, 0, 0, 0.08); + --shadow-hover: 0 4px 24px rgba(0, 0, 0, 0.12); } /* === Tailwind/shadcn 매핑 (Light) ============================ diff --git a/src/types/card.ts b/src/types/card.ts index 83d0756..6262e90 100644 --- a/src/types/card.ts +++ b/src/types/card.ts @@ -1,16 +1,33 @@ import type { HTMLAttributes } from "react"; -export type CardPreset = "specialFeature"; -export type CardSize = "sm" | "md" | "lg" | "xl"; - +// 공통 베이스 export interface BaseCardProps extends HTMLAttributes { className?: string; } /** specialFeature 전용 */ -export type SpecialFeatureCardProps = BaseCardProps & { - preset: "specialFeature"; - size?: CardSize; -}; +export interface SpecialFeatureCardProps extends Omit, "title"> { + icon?: React.ReactNode; + title?: React.ReactNode; + description?: React.ReactNode; + children?: React.ReactNode; +} + +/** selectModule 전용 */ +import * as React from "react"; + +export type SelectModuleCardKind = "module" | "design"; +export type SubtitleState = "select" | "noSelect"; + +export interface SelectModuleCardProps extends React.HTMLAttributes { + title: string; // 한글 제목 + subtitle?: string; // 영문 서브타이틀 + description?: string; // 설명 + imageSrc?: string; // 섬네일 경로 + imageAlt?: string; // 대체 텍스트 + kind?: SelectModuleCardKind; // module | design (타이포만 달라짐) + subtitleState?: SubtitleState; + children?: React.ReactNode; +} -export type CardProps = SpecialFeatureCardProps; +export type CardProps = SpecialFeatureCardProps | SelectModuleCardProps; diff --git a/src/types/special-feature-card.ts b/src/types/special-feature-card.ts deleted file mode 100644 index 95383a9..0000000 --- a/src/types/special-feature-card.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { HTMLAttributes } from "react"; - -export interface SpecialFeatureCardProps extends Omit, "title"> { - icon?: React.ReactNode; - title?: React.ReactNode; - description?: React.ReactNode; - children?: React.ReactNode; -}