diff --git a/apps/web/src/app/create/Create.tsx b/apps/web/src/app/create/Create.tsx index d10c2e07..6936e90b 100644 --- a/apps/web/src/app/create/Create.tsx +++ b/apps/web/src/app/create/Create.tsx @@ -9,10 +9,10 @@ import { Icon, Button, Modal, + GradientAnimatedText, } from '@repo/ui'; import { ImageManager, MainBreadcrumbItem } from '@web/components/common'; import { KeywordChipGroup } from './_components/KeywordChip/KeywordChipGroup'; -import { GradientAnimatedTitle } from './_components/GradientAnimatedTitle/GradientAnimatedTitle'; import { AnimatedContainer } from './_components/AnimatedContainer/AnimatedContainer'; import { useForm, Controller } from 'react-hook-form'; import { isEmptyStringOrNil } from '@web/utils'; @@ -144,7 +144,9 @@ export default function Create() { - 어떤 글을 생성할까요? + + 어떤 글을 생성할까요? +
@@ -253,23 +255,22 @@ export default function Create() { {/* 본문 길이 */}
- + ( - {LENGTH_OPTIONS.map( - ({ value, label, description, badge }) => ( - - {badge} - {label} - - {description} - - - ) - )} + {LENGTH_OPTIONS.map(({ value, label, description }) => ( + + {label} + + {description} + + + ))} )} /> diff --git a/apps/web/src/app/create/_components/GradientAnimatedTitle/GradientAnimatedTitle.tsx b/apps/web/src/app/create/_components/GradientAnimatedTitle/GradientAnimatedTitle.tsx deleted file mode 100644 index 1444e3ab..00000000 --- a/apps/web/src/app/create/_components/GradientAnimatedTitle/GradientAnimatedTitle.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { motion } from 'motion/react'; -import * as styles from './GradientAnimatedTitle.css'; -import { ReactNode } from 'react'; - -type GradientAnimatedTitleProps = { - children: ReactNode; -}; - -export function GradientAnimatedTitle({ - children, -}: GradientAnimatedTitleProps) { - return ( - - {children} - - ); -} diff --git a/apps/web/src/app/create/constants.ts b/apps/web/src/app/create/constants.ts index ead87027..9e60e0bb 100644 --- a/apps/web/src/app/create/constants.ts +++ b/apps/web/src/app/create/constants.ts @@ -66,18 +66,15 @@ export const LENGTH_OPTIONS = [ value: LENGTH_TYPE.SHORT, label: '짧은 게시물', description: '약 1~2문장, 최대 140자', - badge: '누구나 이용 가능', }, { value: LENGTH_TYPE.MEDIUM, label: '보통 게시물', description: '약 3~4문장, 최대 300자', - badge: 'X 유료 구독 전용', }, { value: LENGTH_TYPE.LONG, label: '긴 게시물', description: '약 7~8문장, 최대 1000자', - badge: 'X 유료 구독 전용', }, ] as const; diff --git a/apps/web/src/app/create/pageStyle.css.ts b/apps/web/src/app/create/pageStyle.css.ts index 2c6e38bf..80149086 100644 --- a/apps/web/src/app/create/pageStyle.css.ts +++ b/apps/web/src/app/create/pageStyle.css.ts @@ -25,3 +25,9 @@ export const labelDiscriptionWrapperStyle = style({ display: 'flex', flexDirection: 'column', }); + +export const titleStyle = style({ + width: '100%', + padding: `${vars.space[24]} 0`, + textAlign: 'center', +}); diff --git a/packages/ui/package.json b/packages/ui/package.json index c7debd6f..a2cd4aae 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -91,6 +91,10 @@ "types": "./dist/components/Dropdown/index.d.ts", "import": "./dist/components/Dropdown/index.js" }, + "./GradientAnimatedText": { + "types": "./dist/components/GradientAnimatedText/index.d.ts", + "import": "./dist/components/GradientAnimatedText/index.js" + }, "./utils": "./dist/utils/index.js" }, "scripts": { diff --git a/apps/web/src/app/create/_components/GradientAnimatedTitle/GradientAnimatedTitle.css.ts b/packages/ui/src/components/GradientAnimatedText/GradientAnimatedText.css.ts similarity index 86% rename from apps/web/src/app/create/_components/GradientAnimatedTitle/GradientAnimatedTitle.css.ts rename to packages/ui/src/components/GradientAnimatedText/GradientAnimatedText.css.ts index 52db0369..3117fb4a 100644 --- a/apps/web/src/app/create/_components/GradientAnimatedTitle/GradientAnimatedTitle.css.ts +++ b/packages/ui/src/components/GradientAnimatedText/GradientAnimatedText.css.ts @@ -1,4 +1,3 @@ -import { vars } from '@repo/theme'; import { keyframes, style } from '@vanilla-extract/css'; const flowingGradient = keyframes({ @@ -11,14 +10,13 @@ const flowingGradient = keyframes({ }); export const gradientTitleStyle = style({ + width: 'fit-content', background: 'linear-gradient(90deg, #1F3761 0%, #2646C5 10%, #615BE7 30%, #615BE7 70%, #2646C5 93%, #1F3761 97%, #1F3761 100%)', backgroundSize: '200% 100%', backgroundClip: 'text', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', - textAlign: 'center', - padding: `${vars.space[24]} 0`, animation: `${flowingGradient} 4s linear infinite`, backgroundRepeat: 'repeat', }); diff --git a/packages/ui/src/components/GradientAnimatedText/GradientAnimatedText.tsx b/packages/ui/src/components/GradientAnimatedText/GradientAnimatedText.tsx new file mode 100644 index 00000000..59094cbe --- /dev/null +++ b/packages/ui/src/components/GradientAnimatedText/GradientAnimatedText.tsx @@ -0,0 +1,76 @@ +import { motion, HTMLMotionProps, MotionProps } from 'motion/react'; +import * as styles from './GradientAnimatedText.css'; +import { ReactNode, forwardRef } from 'react'; +import { Slot } from '@radix-ui/react-slot'; + +export type GradientAnimatedTextProps = { + children: ReactNode; + asChild?: boolean; +} & HTMLMotionProps<'h1'>; + +const animationOptions = { + initial: { + y: '35vh', + scale: 2, + x: '-50%', + left: '50%', + position: 'absolute', + }, + animate: { + y: 0, + scale: 1, + x: 0, + left: 'auto', + position: 'relative', + }, + transition: { + type: 'spring', + duration: 0.6, + bounce: 0.22, + }, +} as MotionProps; + +/** + * 그라디언트 애니메이션이 적용된 텍스트 컴포넌트입니다. + * + * @example + * // 기본 사용 (h1) + * 제목 + * + * // 다른 태그로 사용 + * + * 제목 + * + * + */ +export const GradientAnimatedText = forwardRef< + HTMLHeadingElement, + GradientAnimatedTextProps +>(({ children, asChild, className = '', ...props }, ref) => { + if (asChild) { + return ( + + + {children} + + + ); + } + + return ( + + {children} + + ); +}); + +GradientAnimatedText.displayName = 'GradientAnimatedText'; diff --git a/packages/ui/src/components/GradientAnimatedText/index.ts b/packages/ui/src/components/GradientAnimatedText/index.ts new file mode 100644 index 00000000..a8355ed1 --- /dev/null +++ b/packages/ui/src/components/GradientAnimatedText/index.ts @@ -0,0 +1,2 @@ +export { GradientAnimatedText } from './GradientAnimatedText'; +export type { GradientAnimatedTextProps } from './GradientAnimatedText'; diff --git a/packages/ui/src/components/Label/Label.css.ts b/packages/ui/src/components/Label/Label.css.ts index bcf893a2..d8b4633f 100644 --- a/packages/ui/src/components/Label/Label.css.ts +++ b/packages/ui/src/components/Label/Label.css.ts @@ -1,6 +1,12 @@ import { vars } from '@repo/theme'; import { style } from '@vanilla-extract/css'; +export const labelWrapperStyle = style({ + display: 'flex', + flexDirection: 'row', + gap: vars.space[12], +}); + export const labelStyle = style({ display: 'flex', alignItems: 'center', diff --git a/packages/ui/src/components/Label/Label.tsx b/packages/ui/src/components/Label/Label.tsx index 8eb2ed2e..1e8082d3 100644 --- a/packages/ui/src/components/Label/Label.tsx +++ b/packages/ui/src/components/Label/Label.tsx @@ -1,11 +1,17 @@ -import { forwardRef, LabelHTMLAttributes } from 'react'; +import { forwardRef, LabelHTMLAttributes, ReactNode } from 'react'; import { Text } from '../Text/Text'; -import { labelStyle, requiredStyle, optionalStyle } from './Label.css'; +import { + labelStyle, + labelWrapperStyle, + requiredStyle, + optionalStyle, +} from './Label.css'; export type LabelVariant = 'default' | 'required' | 'optional'; export type LabelProps = LabelHTMLAttributes & { variant?: LabelVariant; + description?: ReactNode; }; const LABEL_CLASS = { @@ -14,32 +20,62 @@ const LABEL_CLASS = { optional: optionalStyle, } as const; +/** + * 폼 요소의 레이블을 표시하는 컴포넌트입니다. + * + * @example + * // 기본 사용 + * + * + * // 필수 항목 + * + * + * // 선택 항목 + * + * + * // 설명 추가 + * + */ export const Label = forwardRef( - ({ children, variant = 'default', className = '', ...props }, ref) => { + ( + { children, variant = 'default', description, className = '', ...props }, + ref + ) => { return ( -