Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"react-error-boundary": "6.0.0",
"react-hook-form": "^7.54.2",
"react-router-dom": "^7.1.5",
"tailwind-merge": "^3.6.0",
"tailwindcss": "^4.0.0",
"zod": "^3.24.2",
"zustand": "^5.0.3"
Expand Down
50 changes: 50 additions & 0 deletions apps/web/src/components/common/typography/Hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { HTMLAttributes, ReactNode } from "react";

import { cn } from "@/utils/cn";

type HeroSize = "lg" | "md" | "sm" | "xs";
type HeroAlign = "center" | "left" | "right";

interface HeroProps extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
size?: HeroSize;
textAlign?: HeroAlign;
children: ReactNode;
}

const sizeClassName: Record<HeroSize, string> = {
lg: "semantic-textStyle-title-4",
md: "semantic-textStyle-title-3",
sm: "semantic-textStyle-title-2",
xs: "semantic-textStyle-title-1",
};

const alignClassName: Record<HeroAlign, string> = {
center: "justify-center text-center",
left: "justify-start text-left",
right: "justify-end text-right",
};
Comment on lines +21 to +25
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Title, Hero, Label 모두 textAlign이 현재 flex 정렬만 설정하고 실제 CSS text-align을 설정하지 않는데 의도된 동작인지 궁금합니다

Copy link
Copy Markdown
Member Author

@ccconac ccconac May 26, 2026

Choose a reason for hiding this comment

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

레이아웃만 생각해서 생긴 단순 누락입니다. 😅 반영해 두었습니다. c1a92a6


function Hero({
size = "lg",
textAlign = "center",
className,
children,
...props
}: HeroProps) {
return (
<div
className={cn(
"flex cursor-default items-center",
sizeClassName[size],
alignClassName[textAlign],
"text-(--semantic-object-boldest)",
className,
)}
{...props}
>
{children}
</div>
);
}

export default Hero;
78 changes: 78 additions & 0 deletions apps/web/src/components/common/typography/Label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { ElementType, HTMLAttributes, ReactNode } from "react";

import { cn } from "@/utils/cn";

type LabelSize = "lg" | "md" | "sm" | "xs";
type LabelAlign = "center" | "left" | "right";
type LabelWeight = "bold" | "normal" | "subtle";
type LabelCursor = "pointer" | "default";

interface LabelProps extends Omit<HTMLAttributes<HTMLElement>, "style"> {
as?: ElementType;
size?: LabelSize;
textAlign?: LabelAlign;
weight?: LabelWeight;
cursor?: LabelCursor;
htmlFor?: string;
children: ReactNode;
}

const sizeWeightClassName: Record<LabelSize, Record<LabelWeight, string>> = {
lg: {
bold: "semantic-textStyle-label-lg-bold",
normal: "semantic-textStyle-label-lg-normal",
subtle: "semantic-textStyle-label-lg-subtle",
},
md: {
bold: "semantic-textStyle-label-md-bold",
normal: "semantic-textStyle-label-md-normal",
subtle: "semantic-textStyle-label-md-subtle",
},
sm: {
bold: "semantic-textStyle-label-sm-bold",
normal: "semantic-textStyle-label-sm-normal",
subtle: "semantic-textStyle-label-sm-subtle",
},
xs: {
bold: "semantic-textStyle-label-xs-bold",
normal: "semantic-textStyle-label-xs-normal",
subtle: "semantic-textStyle-label-xs-subtle",
},
};

const alignClassName: Record<LabelAlign, string> = {
center: "justify-center text-center",
left: "justify-start text-left",
right: "justify-end text-right",
};

function Label({
as,
size = "md",
textAlign = "left",
weight = "normal",
cursor = "default",
className,
children,
...props
}: LabelProps) {
const Component = as ?? "label";

return (
<Component
className={cn(
"flex items-center",
sizeWeightClassName[size][weight],
alignClassName[textAlign],
cursor === "pointer" ? "cursor-pointer" : "cursor-default",
"text-(--semantic-object-bold)",
className,
)}
{...props}
>
{children}
</Component>
);
}

export default Label;
50 changes: 50 additions & 0 deletions apps/web/src/components/common/typography/Title.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { HTMLAttributes, ReactNode } from "react";

import { cn } from "@/utils/cn";

type TitleSize = "lg" | "md" | "sm" | "xs";
type TitleTextAlign = "center" | "left" | "right";

interface TitleProps extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
size?: TitleSize;
textAlign?: TitleTextAlign;
children: ReactNode;
}

const sizeClassName: Record<TitleSize, string> = {
lg: "semantic-textStyle-title-4",
md: "semantic-textStyle-title-3",
sm: "semantic-textStyle-title-2",
xs: "semantic-textStyle-title-1",
};

const alignClassName: Record<TitleTextAlign, string> = {
center: "justify-center text-center",
left: "justify-start text-left",
right: "justify-end text-right",
};

function Title({
size = "md",
textAlign = "left",
className,
children,
...props
}: TitleProps) {
return (
<div
className={cn(
"flex cursor-default items-center",
sizeClassName[size],
alignClassName[textAlign],
"text-(--semantic-object-bolder)",
className,
)}
{...props}
>
{children}
</div>
);
}

export default Title;
3 changes: 3 additions & 0 deletions apps/web/src/components/common/typography/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as Hero } from "./Hero";
export { default as Label } from "./Label";
export { default as Title } from "./Title";
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ContentBadge, EmptyState, Tab, Title } from "@jects/jds";
import { ContentBadge, EmptyState, Tab } from "@jects/jds";

import { Title } from "@/components/common/typography";
import { figmaGuideCurriculumData, teamProjectScheduleData } from "@/constants/curriculumData";
import type { FigmaGuideItem, TeamProjectItem } from "@/types/ui/curriculum";

Expand Down Expand Up @@ -63,11 +64,11 @@ const CurriculumTabSection = () => {
</Tab.List>

<Tab.Content value='team-project-schedule'>
<div className='grid w-full grid-cols-1 gap-(--semantic-spacing-16) pt-(--semantic-spacing-48) tablet:grid-cols-2'>
<div className='tablet:grid-cols-2 grid w-full grid-cols-1 gap-(--semantic-spacing-16) pt-(--semantic-spacing-48)'>
{teamProjectScheduleData.map(item => (
<TeamProjectCard key={item.id} item={item} />
))}
<div className='[&>div]:h-full [&>div]:min-w-0! [&>div]:max-w-full!'>
<div className='[&>div]:h-full [&>div]:max-w-full! [&>div]:min-w-0!'>
<EmptyState
variant='outlined'
header='그 밖에 더 많은 활동들이 기다리고 있어요...'
Expand All @@ -78,7 +79,7 @@ const CurriculumTabSection = () => {
</Tab.Content>

<Tab.Content value='figma-guide-curriculum'>
<div className='grid w-full grid-cols-1 gap-(--semantic-spacing-16) pt-(--semantic-spacing-48) tablet:grid-cols-2'>
<div className='tablet:grid-cols-2 grid w-full grid-cols-1 gap-(--semantic-spacing-16) pt-(--semantic-spacing-48)'>
{figmaGuideCurriculumData.map(item => (
<FigmaGuideCard key={item.id} item={item} />
))}
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/gnb/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Divider, IconButton, Label, MenuItem } from "@jects/jds";
import { Divider, IconButton, MenuItem } from "@jects/jds";
import type { Dispatch, MouseEvent, SetStateAction } from "react";
import { useEffect } from "react";
import { createPortal } from "react-dom";
import { useNavigate } from "react-router-dom";

import { Label } from "@/components/common/typography";
import { PATH } from "@/constants/path";

interface SidebarMenusProps {
Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/components/main/sections/HeroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Hero, Icon, Label } from "@jects/jds";
import { Icon } from "@jects/jds";
import { useEffect, useState } from "react";

import heroBackground from "@/assets/images/hero-background.png";
import { Hero, Label } from "@/components/common/typography";

const ANIMATION_DELAY_MS = 800;
const ANIMATION_DURATION_MS = 500;
Expand Down Expand Up @@ -100,7 +101,7 @@ const HeroSection = () => {
background: "radial-gradient(33.77% 43.9% at 50% 50%, #191B2480 0%, #191B2400 100%)",
}}
>
<Hero size='xs' textAlign='center' color='white'>
<Hero size='xs' textAlign='center' className='text-white'>
<span className='flex flex-col items-center'>
<span>젝트에서</span>
<RotatingText />
Expand All @@ -110,7 +111,7 @@ const HeroSection = () => {
</div>

<div className='absolute bottom-10 flex flex-col items-center gap-2'>
<Label as='span' size='md' weight='bold' className='text-white!'>
<Label as='span' size='md' weight='bold' className='text-white'>
아래로 스크롤해주세요
</Label>
<Icon name='arrow-down-wide-line' size='md' color='white' />
Expand Down
24 changes: 10 additions & 14 deletions apps/web/src/components/main/sections/IntroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import {
Callout,
ContentBadge,
EmptyState,
Hero,
Icon,
Image,
Label,
LabelButton,
Title as JdsTitle,
} from "@jects/jds";
import { Callout, ContentBadge, EmptyState, Icon, Image, LabelButton } from "@jects/jds";
import { useNavigate } from "react-router-dom";

import introTeamMeetingImage from "@/assets/images/intro-team-meeting.png";
import { Hero, Label, Title } from "@/components/common/typography";
import { positionData, programData, statData } from "@/constants/mainPageData";

const wrapperClassName =
Expand Down Expand Up @@ -77,7 +68,12 @@ const IntroSection = () => {
</Callout.Basic>
))}
</div>
<Label as='span' size='sm' weight='normal' color='var(--semantic-object-assistive)'>
<Label
as='span'
size='sm'
weight='normal'
className='text-(--semantic-object-assistive)'
>
*현 4기 기준, 진행 완료 및 진행중 프로젝트 포함.
</Label>
</div>
Expand Down Expand Up @@ -107,9 +103,9 @@ const IntroSection = () => {
>
<div className='flex items-center gap-(--semantic-spacing-8)'>
<Icon name={icon} size='2xl' />
<JdsTitle size='sm' textAlign='left'>
<Title size='sm' textAlign='left'>
{title}
</JdsTitle>
</Title>
</div>
<p className='body-md font-(--primitive-font-weight-body-bold) text-(--semantic-object-normal)'>
{description}
Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/components/main/sections/JoinSection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { BlockButton, Hero, Image, Label } from "@jects/jds";
import { BlockButton, Image } from "@jects/jds";
import { useNavigate } from "react-router-dom";

import joinTeamMeetingImage from "@/assets/images/join-team-meeting.png";
import { Hero, Label } from "@/components/common/typography";
import { PATH } from "@/constants/path";
import { trackApplyStart } from "@/utils/analytics";

Expand All @@ -20,15 +21,15 @@ const JoinSection = () => {
<div className='flex flex-col gap-(--semantic-spacing-48)'>
<div className='flex flex-col gap-(--semantic-spacing-16)'>
<div className='whitespace-pre-line'>
<Hero size='xs' textAlign='left' color='white'>
<Hero size='xs' textAlign='left' className='text-white'>
{"젝트의 구성원으로\n함께해주세요"}
</Hero>
</div>
<Label
size='lg'
textAlign='left'
weight='bold'
color='var(--semantic-object-static-inverse-normal)'
className='text-(--semantic-object-static-inverse-normal)'
>
모든 구성원들의 몰입과 성장을 위해.
</Label>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/vision/sections/GoalSection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Title } from "@jects/jds";
import { Title } from "@/components/common/typography";

const GoalSection = () => {
return (
Expand All @@ -8,7 +8,7 @@ const GoalSection = () => {
<span className='text-(--semantic-accent-normal)'>젝트</span>의 목표
</Title>

<Title size='md' textAlign='left' color='var(--semantic-object-boldest)'>
<Title size='md' textAlign='left' className='text-(--semantic-object-boldest)'>
우리는 IT 생태계의 선순환을 목표로 활동하고 있습니다.
</Title>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Title } from "@jects/jds";
import { Title } from "@/components/common/typography";

const GrowthStorySection = () => {
return (
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/components/vision/sections/MemberSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ContentBadge, Icon, Image, Tab, Title } from "@jects/jds";
import { ContentBadge, Icon, Image, Tab } from "@jects/jds";
import { useState } from "react";

import { Title } from "@/components/common/typography";
import {
roleBadgeVariantMap,
roleIconMap,
Expand Down Expand Up @@ -135,7 +136,7 @@ const MemberSection = () => {
<span className='text-(--semantic-accent-normal)'>젝트</span>를 만드는 사람들
</Title>

<Title size='md' textAlign='left' color='var(--semantic-object-boldest)'>
<Title size='md' textAlign='left' className='text-(--semantic-object-boldest)'>
열정 넘치는 구성원들이 젝트에 직접 기여하며 많은 가치를 창출하고 있습니다.
</Title>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Title } from "@jects/jds";
import { Title } from "@/components/common/typography";

const ProjectStartSection = () => {
return (
Expand All @@ -8,7 +8,7 @@ const ProjectStartSection = () => {
프로<span className='text-(--semantic-accent-normal)'>젝트</span>&nbsp;시작
</Title>

<Title size='md' textAlign='left' color='var(--semantic-object-boldest)'>
<Title size='md' textAlign='left' className='text-(--semantic-object-boldest)'>
젝트는 개발자들의 소규모 사이드 프로젝트 모임으로 시작했습니다.
</Title>

Expand Down
Loading
Loading