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] 주제설정 변경사항 구현, GradientAnimatedText ui 패키지로 이관 및 개선, Label 컴포넌트 옵션 추가 #127

Merged
merged 6 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
29 changes: 15 additions & 14 deletions apps/web/src/app/create/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -144,7 +144,9 @@ export default function Create() {

<Spacing size={80} />

<GradientAnimatedTitle>어떤 글을 생성할까요?</GradientAnimatedTitle>
<GradientAnimatedText className={styles.titleStyle}>
어떤 글을 생성할까요?
</GradientAnimatedText>

<AnimatedContainer>
<form className={styles.contentStyle}>
Expand Down Expand Up @@ -253,23 +255,22 @@ export default function Create() {

{/* 본문 길이 */}
<section className={styles.sectionStyle}>
<Label>본문 길이</Label>
<Label description="3문장 이상의 긴 게시물을 업로드 하려면 X 유료 구독 플랜에 가입해주세요">
본문 길이
</Label>
<Controller
name="length"
control={control}
render={({ field: { onChange, value } }) => (
<RadioCards value={value} onChange={onChange} columns={3}>
{LENGTH_OPTIONS.map(
({ value, label, description, badge }) => (
<RadioCards.Item key={value} value={value}>
<RadioCards.Badge>{badge}</RadioCards.Badge>
<RadioCards.Label>{label}</RadioCards.Label>
<RadioCards.Description>
{description}
</RadioCards.Description>
</RadioCards.Item>
)
)}
{LENGTH_OPTIONS.map(({ value, label, description }) => (
<RadioCards.Item key={value} value={value}>
<RadioCards.Label>{label}</RadioCards.Label>
<RadioCards.Description>
{description}
</RadioCards.Description>
</RadioCards.Item>
))}
</RadioCards>
)}
/>
Expand Down

This file was deleted.

3 changes: 0 additions & 3 deletions apps/web/src/app/create/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
6 changes: 6 additions & 0 deletions apps/web/src/app/create/pageStyle.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
4 changes: 4 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { vars } from '@repo/theme';
import { keyframes, style } from '@vanilla-extract/css';

const flowingGradient = keyframes({
Expand All @@ -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',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { motion, HTMLMotionProps } 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'>;

/**
* 그라디언트 애니메이션이 적용된 텍스트 컴포넌트입니다.
*
* @example
* // 기본 사용 (h1)
* <GradientAnimatedText>제목</GradientAnimatedText>
*
* // 다른 태그로 사용
* <GradientAnimatedText asChild>
* <Text.P>제목</Text.P>
* </GradientAnimatedText>
*
*/
export const GradientAnimatedText = forwardRef<
HTMLHeadingElement,
GradientAnimatedTextProps
>(({ children, asChild, className = '', ...props }, ref) => {
if (asChild) {
return (
<Slot ref={ref}>
<motion.div
className={`${styles.gradientTitleStyle} ${className}`}
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,
}}
{...props}
>
{children}
</motion.div>
</Slot>
);
}

return (
<motion.h1
ref={ref}
className={`${styles.gradientTitleStyle} ${className}`}
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,
}}
{...props}
>
{children}
</motion.h1>
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

애니메이션 코드 중복을 제거해주세요.

motion 설정이 Sloth1 렌더링 경로에서 중복되어 있습니다.

다음과 같이 리팩토링하는 것을 추천드립니다:

+const animationProps = {
+  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,
+  },
+};

 export const GradientAnimatedText = forwardRef<
   HTMLHeadingElement,
   GradientAnimatedTextProps
 >(({ children, asChild, className = '', ...props }, ref) => {
   if (asChild) {
     return (
       <Slot ref={ref}>
         <motion.div
           className={`${styles.gradientTitleStyle} ${className}`}
-          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,
-          }}
+          {...animationProps}
           {...props}
         >
           {children}
         </motion.div>
       </Slot>
     );
   }

   return (
     <motion.h1
       ref={ref}
       className={`${styles.gradientTitleStyle} ${className}`}
-      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,
-      }}
+      {...animationProps}
       {...props}
     >
       {children}
     </motion.h1>
   );
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (asChild) {
return (
<Slot ref={ref}>
<motion.div
className={`${styles.gradientTitleStyle} ${className}`}
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,
}}
{...props}
>
{children}
</motion.div>
</Slot>
);
}
return (
<motion.h1
ref={ref}
className={`${styles.gradientTitleStyle} ${className}`}
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,
}}
{...props}
>
{children}
</motion.h1>
);
const animationProps = {
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,
},
};
export const GradientAnimatedText = forwardRef<
HTMLHeadingElement,
GradientAnimatedTextProps
>(({ children, asChild, className = '', ...props }, ref) => {
if (asChild) {
return (
<Slot ref={ref}>
<motion.div
className={`${styles.gradientTitleStyle} ${className}`}
{...animationProps}
{...props}
>
{children}
</motion.div>
</Slot>
);
}
return (
<motion.h1
ref={ref}
className={`${styles.gradientTitleStyle} ${className}`}
{...animationProps}
{...props}
>
{children}
</motion.h1>
);
});

});

GradientAnimatedText.displayName = 'GradientAnimatedText';
2 changes: 2 additions & 0 deletions packages/ui/src/components/GradientAnimatedText/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { GradientAnimatedText } from './GradientAnimatedText';
export type { GradientAnimatedTextProps } from './GradientAnimatedText';
6 changes: 6 additions & 0 deletions packages/ui/src/components/Label/Label.css.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
84 changes: 60 additions & 24 deletions packages/ui/src/components/Label/Label.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLLabelElement> & {
variant?: LabelVariant;
description?: ReactNode;
};

const LABEL_CLASS = {
Expand All @@ -14,32 +20,62 @@ const LABEL_CLASS = {
optional: optionalStyle,
} as const;

/**
* 폼 요소의 레이블을 표시하는 컴포넌트입니다.
*
* @example
* // 기본 사용
* <Label>이름</Label>
*
* // 필수 항목
* <Label variant="required">이름</Label>
*
* // 선택 항목
* <Label variant="optional">이름</Label>
*
* // 설명 추가
* <Label
* description="2~10자 이내로 입력해주세요"
* >
* 이름
* </Label>
*/
export const Label = forwardRef<HTMLLabelElement, LabelProps>(
({ children, variant = 'default', className = '', ...props }, ref) => {
(
{ children, variant = 'default', description, className = '', ...props },
ref
) => {
return (
<label
ref={ref}
className={`${labelStyle} ${LABEL_CLASS[variant]} ${className}`}
aria-required={variant === 'required'}
{...props}
>
{children}
{variant === 'required' && (
<Text
as="span"
fontSize={20}
fontWeight="semibold"
color="primary600"
>
*
<div className={labelWrapperStyle}>
<label
ref={ref}
className={`${labelStyle} ${LABEL_CLASS[variant]} ${className}`}
aria-required={variant === 'required'}
{...props}
>
{children}
{variant === 'required' && (
<Text
as="span"
fontSize={20}
fontWeight="semibold"
color="primary600"
>
*
</Text>
)}
{variant === 'optional' && (
<Text as="span" fontSize={16} fontWeight="semibold" color="grey300">
선택
</Text>
)}
</label>
{description && (
<Text fontSize={16} fontWeight="medium" color="grey400">
{description}
</Text>
)}
{variant === 'optional' && (
<Text as="span" fontSize={16} fontWeight="semibold" color="grey300">
선택
</Text>
)}
</label>
</div>
);
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const RadioCardsDescription = ({
<Text.P
fontSize={14}
fontWeight="medium"
color={isDisabled ? 'grey400' : 'grey600'}
color={isDisabled ? 'grey200' : 'grey600'}
{...props}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/RadioCards/RadioCardsLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const RadioCardsLabel = ({
<Text.Span
fontSize={18}
fontWeight="semibold"
color={isDisabled ? 'grey400' : 'grey800'}
color={isDisabled ? 'grey300' : 'grey800'}
{...props}
>
{children}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from './Modal';
export * from './Skeleton';
export * from './Chip';
export * from './Accordion';
export * from './GradientAnimatedText';
Loading