Skip to content
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
20 changes: 20 additions & 0 deletions src/app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -101,6 +102,25 @@ export default function Home() {
</section>

<SignupGroupButton />

<SelectModuleCard
kind="module" // "module" | "design"
title="์ผ๊ฐ„ ํ”Œ๋ž˜๋„ˆ"
subtitle="Daily Intelligence"
description="ํ•˜๋ฃจ๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜์„ธ์š”"
imageSrc="/images/daily-planner.png"
onClick={() => console.log("์ผ๊ฐ„ ํ”Œ๋ž˜๋„ˆ ํด๋ฆญ")}
/>

<SelectModuleCard
kind="design" // "module" | "design"
title="์ผ๊ฐ„ ํ”Œ๋ž˜๋„ˆ"
subtitle="Daily Intelligence"
subtitleState="noSelect"
description="ํ•˜๋ฃจ๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜์„ธ์š”"
imageSrc="/images/daily-planner.png"
onClick={() => console.log("์ผ๊ฐ„ ํ”Œ๋ž˜๋„ˆ ํด๋ฆญ")}
/>
</div>
);
}
20 changes: 0 additions & 20 deletions src/lib/variants/card.presets.ts

This file was deleted.

54 changes: 54 additions & 0 deletions src/lib/variants/card.selectModule.ts
Original file line number Diff line number Diff line change
@@ -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<typeof selectModuleCardRoot>;
69 changes: 69 additions & 0 deletions src/shared/SelectModuleCard.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement, SelectModuleCardProps>(
(
{
className,
title,
subtitle,
description,
imageSrc,
imageAlt,
kind = "module",
subtitleState = "select",
children,
...native
},
ref,
) => {
return (
<div
ref={ref}
role="button"
tabIndex={0}
className={cn(selectModuleCardRoot({ kind }), className)}
{...native}
>
<div className="space-y-1">
<h3 className={selectModuleCardTitle({ kind })}>{title}</h3>
{subtitle ? (
<p className={selectModuleCardSubtitle({ state: subtitleState })}>{subtitle}</p>
) : null}
{description ? (
<p className={selectModuleCardDescription({ kind })}>{description}</p>
) : null}
</div>

{children}

{imageSrc ? (
<div className={selectModuleCardImageWrap()}>
<Image
src={imageSrc}
alt={imageAlt ?? ""}
fill
className={selectModuleCardImage()}
sizes="(max-width: 520px) 100vw, 520px"
priority={false}
/>
</div>
) : null}
</div>
);
},
);

SelectModuleCard.displayName = "SelectModuleCard";
4 changes: 2 additions & 2 deletions src/shared/SpecialFeatureCard.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement, SpecialFeatureCardProps>(
(props, ref) => {
const { className, icon, title, description, children, ...native } = props;

const classes = specialFeatureCardVariants({});
const classes = specialFeatureCardVariants({ size: "lg" });

return (
<div ref={ref} className={cn(classes, className)} {...native}>
Expand Down
75 changes: 0 additions & 75 deletions src/shared/card.tsx

This file was deleted.

1 change: 1 addition & 0 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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) ============================
Expand Down
33 changes: 25 additions & 8 deletions src/types/card.ts
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> {
className?: string;
}

/** specialFeature ์ „์šฉ */
export type SpecialFeatureCardProps = BaseCardProps & {
preset: "specialFeature";
size?: CardSize;
};
export interface SpecialFeatureCardProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
icon?: React.ReactNode;
title?: React.ReactNode;
description?: React.ReactNode;
children?: React.ReactNode;
}

/** selectModule ์ „์šฉ */
import * as React from "react";

Comment on lines 8 to +18

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Move React import before declarations

The new React import is placed after exported interfaces. TypeScript requires all import declarations to appear before any other statements, so the compiler will stop with a syntax error when it encounters import * as React from "react" in the middle of the file. Move this import to the top alongside the existing HTMLAttributes import so the module can compile.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

export type SelectModuleCardKind = "module" | "design";
export type SubtitleState = "select" | "noSelect";

export interface SelectModuleCardProps extends React.HTMLAttributes<HTMLDivElement> {
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;
8 changes: 0 additions & 8 deletions src/types/special-feature-card.ts

This file was deleted.