From 5f4407de8c273e9e669a1799a927019de284f8bd Mon Sep 17 00:00:00 2001 From: sieon Date: Mon, 18 Nov 2024 19:51:37 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20Input=EC=97=90=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/CustomButton/CustomButton.tsx | 28 ++--- .../CustomInput/CustomInput.stories.tsx | 15 ++- .../global/CustomInput/CustomInput.tsx | 117 ++++++++++-------- 3 files changed, 92 insertions(+), 68 deletions(-) diff --git a/src/components/global/CustomButton/CustomButton.tsx b/src/components/global/CustomButton/CustomButton.tsx index 3b2bf79c..5d6a22ec 100644 --- a/src/components/global/CustomButton/CustomButton.tsx +++ b/src/components/global/CustomButton/CustomButton.tsx @@ -1,7 +1,7 @@ // CustomButton.tsx import React from 'react'; import styled, { css, keyframes } from 'styled-components'; - +import { colors } from '../../../style/color'; interface CustomButtonProps { /** * 버튼 내부 텍스트 @@ -49,11 +49,12 @@ const Button = styled.button<{ display: inline-flex; align-items: center; justify-content: center; - border-radius: 20px; + border-radius: 5px; font-weight: 500; transition: all 0.2s ease-in-out; cursor: pointer; width: ${props => props.$fullWidth ? '100%' : 'auto'}; + color: ${colors.white}; // 크기 설정 ${props => { @@ -61,19 +62,19 @@ const Button = styled.button<{ case 'small': return css` padding: 8px 16px; - font-size: 14px; + font-size: 10px; height: 32px; `; case 'large': return css` padding: 16px 32px; - font-size: 18px; + font-size: 12px; height: 48px; `; default: // medium return css` padding: 12px 24px; - font-size: 16px; + font-size: 12px; height: 40px; `; } @@ -85,7 +86,7 @@ const Button = styled.button<{ case 'secondary': return css` background-color: #EEF2FF; - color: #4F46E5; + color: ${colors.blue200}; border: none; &:hover:not(:disabled) { @@ -99,8 +100,8 @@ const Button = styled.button<{ case 'outline': return css` background-color: transparent; - color: #4F46E5; - border: 2px solid #4F46E5; + color: ${colors.blue200}; + border: 2px solid ${colors.blue200}; &:hover:not(:disabled) { background-color: #EEF2FF; @@ -112,15 +113,14 @@ const Button = styled.button<{ `; default: // primary return css` - background-color: #4F46E5; - color: white; + background-color: ${colors.blue200}; border: none; - box-shadow: 0 2px 4px rgba(79, 70, 229, 0.1); + box-shadow: 0 2px 4px ${colors.blue100}; &:hover:not(:disabled) { - background-color: #4338CA; + background-color: ${colors.blue200}; transform: translateY(-1px); - box-shadow: 0 4px 6px rgba(79, 70, 229, 0.2); + box-shadow: 0 4px 6px ${colors.blue100}; } &:active:not(:disabled) { @@ -137,7 +137,7 @@ const Button = styled.button<{ &:focus { outline: none; - box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); + box-shadow: 0 0 0 3px ${colors.blue100}; } `; diff --git a/src/components/global/CustomInput/CustomInput.stories.tsx b/src/components/global/CustomInput/CustomInput.stories.tsx index 02a84ee7..239d909b 100644 --- a/src/components/global/CustomInput/CustomInput.stories.tsx +++ b/src/components/global/CustomInput/CustomInput.stories.tsx @@ -1,7 +1,7 @@ // CustomInput.stories.tsx import type { Meta, StoryObj } from '@storybook/react'; import { CustomInput } from './CustomInput'; - +import { CustomButton } from '../CustomButton/CustomButton'; const meta = { title: 'Components/CustomInput', component: CustomInput, @@ -113,4 +113,17 @@ export const Playground: Story = { error: '', hint: '', }, +}; + +export const WithSuffix: Story = { + args: { + label: '제목', + placeholder: '값을 입력해주세요', + variant: 'underlined', + suffix: ( + + 확인 + + ), + }, }; \ No newline at end of file diff --git a/src/components/global/CustomInput/CustomInput.tsx b/src/components/global/CustomInput/CustomInput.tsx index 9ef624da..f13fc840 100644 --- a/src/components/global/CustomInput/CustomInput.tsx +++ b/src/components/global/CustomInput/CustomInput.tsx @@ -1,7 +1,8 @@ -// CustomInput.tsx -import React, { InputHTMLAttributes } from 'react'; +import React, { InputHTMLAttributes, ReactNode } from 'react'; import styled, { css, keyframes } from 'styled-components'; - +import { typography } from '../../../style/typography'; +import { colors } from '../../../style/color'; +import theme from '../../../style/theme'; interface CustomInputProps extends Omit, 'size'> { /** * 입력 필드 라벨 @@ -19,10 +20,14 @@ interface CustomInputProps extends Omit, ' * 너비를 100%로 설정 */ fullWidth?: boolean; - /** + /** * 입력 필드 스타일 변형 */ variant?: 'outlined' | 'underlined'; + /** + * 입력 필드 오른쪽에 추가할 요소 + */ + suffix?: ReactNode; } const fadeIn = keyframes` @@ -39,63 +44,75 @@ const fadeIn = keyframes` const Container = styled.div<{ $fullWidth: boolean }>` display: flex; flex-direction: column; - width: ${props => props.$fullWidth ? '100%' : 'auto'}; + width: ${theme.size.maxWidth}; + /* width: ${(props) => (props.$fullWidth ? `theme.size.maxWidth` : 'auto')}; */ //우선 적으로 Full로 받고 추후 수정 `; const Label = styled.label` font-weight: 500; color: #374151; - margin-bottom: 4px; + margin-bottom: 5px; + font-size: ${typography.body2}; `; -const StyledInput = styled.input<{ - $error: boolean; - $fullWidth: boolean; - $variant: string; -}>` - display: block; - width: ${props => props.$fullWidth ? '100%' : 'auto'}; - transition: all 0.2s ease-in-out; +const InputWrapper = styled.div<{ $variant: string; $error: boolean; $disabled: boolean }>` + display: flex; + align-items: center; + width: 100%; + background-color: ${(props) => (props.$disabled ? colors.gray100 : 'transparent')}; + ${({ $variant, $error, $disabled }) => + $variant === 'outlined' + ? css` + border-radius: 5px; + border: ${$disabled ? `none`: `1px solid ${$error ? `${colors.red200}` : `${colors.gray300}`}`}; + padding: 0px 10px; + height: 40px; + + &:focus-within { + border-color: ${$error ? `${colors.red200}` : `${colors.blue200}`}; + box-shadow: 0 0 0 3px ${$error ? `${colors.red100}` : `${colors.blue100}`}; + } + ` + : css` + border: none; + border-bottom: 1px solid ${$error ? `${colors.red200}` : `${colors.gray300}`}; + border-radius: 0; + padding: 5px 10px; + height: 32px; + + &:focus-within { + border-bottom-color: ${$error ? `${colors.red200}` : `${colors.blue200}`}; + } + `} +`; + +const StyledInput = styled.input<{ $error: boolean }>` + flex: 1; + border: none; outline: none; - background-color: white; + background-color: transparent; font-size: 14px; &::placeholder { - color: #9CA3AF; + color: #9ca3af; + font-size: ${typography.body6}; } &:disabled { - background-color: #F3F4F6; + color: ${colors.gray300}; cursor: not-allowed; + font-size: 12px; } +`; - ${({ $variant, $error }) => $variant === 'outlined' ? css` - border-radius: 20px; - border: 2px solid ${$error ? '#EF4444' : '#E5E7EB'}; - padding: 8px 16px; - height: 32px; - - &:focus { - border-color: ${$error ? '#EF4444' : '#4F46E5'}; - box-shadow: 0 0 0 3px ${$error ? 'rgba(239, 68, 68, 0.1)' : 'rgba(79, 70, 229, 0.1)'}; - } - ` : css` - border: none; - border-bottom: 2px solid ${$error ? '#EF4444' : '#E5E7EB'}; - border-radius: 0; - padding: 4px 0; - height: 24px; - - &:focus { - border-bottom-color: ${$error ? '#EF4444' : '#4F46E5'}; - } - `} +const SuffixContainer = styled.div` + margin-left: 8px; /* 입력 필드와 suffix 간 간격 */ `; const Message = styled.p<{ $error?: boolean }>` - font-size: 14px; + font-size: ${typography.body6}; margin-top: 4px; - color: ${props => props.$error ? '#EF4444' : '#6B7280'}; + color: ${(props) => (props.$error ? `${colors.red300}` : '#6b7280')}; animation: ${fadeIn} 0.2s ease-in-out; `; @@ -106,23 +123,17 @@ export const CustomInput: React.FC = ({ fullWidth = false, disabled = false, variant = 'outlined', + suffix, ...props }) => { return ( {label && } - - {(error || hint) && ( - - {error || hint} - - )} + + + {suffix && {suffix}} + + {(error || hint) && {error || hint}} ); -}; \ No newline at end of file +}; From b525a3e3acdef471037bb25a9675e5355762af08 Mon Sep 17 00:00:00 2001 From: sieon Date: Tue, 19 Nov 2024 11:23:47 +0900 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomInput/CustomInput.stories.tsx | 1 + .../global/CustomInput/CustomInput.tsx | 79 +++++++++++-------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/components/global/CustomInput/CustomInput.stories.tsx b/src/components/global/CustomInput/CustomInput.stories.tsx index 239d909b..0f104af3 100644 --- a/src/components/global/CustomInput/CustomInput.stories.tsx +++ b/src/components/global/CustomInput/CustomInput.stories.tsx @@ -115,6 +115,7 @@ export const Playground: Story = { }, }; + export const WithSuffix: Story = { args: { label: '제목', diff --git a/src/components/global/CustomInput/CustomInput.tsx b/src/components/global/CustomInput/CustomInput.tsx index bdc34da8..3049017e 100644 --- a/src/components/global/CustomInput/CustomInput.tsx +++ b/src/components/global/CustomInput/CustomInput.tsx @@ -3,6 +3,7 @@ import styled, { css, keyframes } from 'styled-components'; import { typography } from '../../../style/typography'; import { colors } from '../../../style/color'; import theme from '../../../style/theme'; + interface CustomInputProps extends Omit, 'size'> { /** * 입력 필드 라벨 @@ -28,6 +29,10 @@ interface CustomInputProps extends Omit, ' * 입력 필드 오른쪽에 추가할 요소 */ suffix?: ReactNode; + /** + * 버튼을 포함하는 경우, marginBottom 설정 + */ + hasButton?: boolean; } const fadeIn = keyframes` @@ -41,11 +46,13 @@ const fadeIn = keyframes` } `; -const Container = styled.div<{ $fullWidth: boolean }>` +const Container = styled.div<{ $fullWidth: boolean; $hasButton: boolean }>` display: flex; flex-direction: column; - width: ${theme.size.maxWidth}; - /* width: ${(props) => (props.$fullWidth ? `theme.size.maxWidth` : 'auto')}; */ //우선 적으로 Full로 받고 추후 수정 + width: ${({ $fullWidth }) => ($fullWidth ? '100%' : theme.size.maxWidth || 'auto')}; + ${({ $hasButton }) => $hasButton && css` + margin-bottom: 10px; /* 버튼이 있을 때 margin-bottom을 10px 추가 */ + `} `; const Label = styled.label` @@ -64,24 +71,24 @@ const InputWrapper = styled.div<{ $variant: string; $error: boolean; $disabled: $variant === 'outlined' ? css` border-radius: 5px; - border: ${$disabled ? `none`: `1px solid ${$error ? `${colors.red200}` : `${colors.gray300}`}`}; + border: ${$disabled ? 'none' : `1px solid ${$error ? colors.red200 : colors.gray300}`}; padding: 0px 10px; height: 40px; &:focus-within { - border-color: ${$error ? `${colors.red200}` : `${colors.blue200}`}; - box-shadow: 0 0 0 3px ${$error ? `${colors.red100}` : `${colors.blue100}`}; + border-color: ${$error ? colors.red200 : colors.blue200}; + box-shadow: 0 0 0 3px ${$error ? colors.red100 : colors.blue100}; } ` : css` border: none; - border-bottom: 1px solid ${$error ? `${colors.red200}` : `${colors.gray300}`}; + border-bottom: 1px solid ${$error ? colors.red200 : colors.gray300}; border-radius: 0; padding: 5px 10px; height: 32px; &:focus-within { - border-bottom-color: ${$error ? `${colors.red200}` : `${colors.blue200}`}; + border-bottom-color: ${$error ? colors.red200 : colors.blue200}; } `} `; @@ -105,36 +112,39 @@ const StyledInput = styled.input<{ $error: boolean }>` } `; -const SuffixContainer = styled.div` - margin-left: 8px; /* 입력 필드와 suffix 간 간격 */ - ${({ $variant, $error }) => $variant === 'outlined' ? css` - border-radius: 20px; - border: 2px solid ${$error ? colors.red300 : colors.gray200}; - padding: 8px 16px; - height: 32px; +const SuffixContainer = styled.div<{ $variant: string; $error: boolean }>` + margin-left: 8px; + margin-bottom: 10px; + ${({ $variant, $error }) => $variant === 'outlined' + ? css` + border-radius: 20px; + border: 2px solid ${$error ? colors.red300 : colors.gray200}; + padding: 8px 16px; + height: 32px; - &:focus { - border-color: ${$error ? colors.red300 : colors.blue200}; - box-shadow: 0 0 0 3px ${$error ? 'rgba(239, 68, 68, 0.1)' : 'rgba(79, 70, 229, 0.1)'}; - } - ` : css` - border: none; - border-bottom: 2px solid ${$error ? colors.red300 : colors.gray200}; - border-radius: 0; - padding: 4px 0; - height: 24px; + &:focus { + border-color: ${$error ? colors.red300 : colors.blue200}; + box-shadow: 0 0 0 3px ${$error ? 'rgba(239, 68, 68, 0.1)' : 'rgba(79, 70, 229, 0.1)'}; + } + ` + : css` + border: none; + border-bottom: 2px solid ${$error ? colors.red300 : colors.gray200}; + border-radius: 0; + padding: 4px 0; + height: 24px; - &:focus { - border-bottom-color: ${$error ? colors.red300 : colors.blue200}; - } - `} + &:focus { + border-bottom-color: ${$error ? colors.red300 : colors.blue200}; + } + `} `; const Message = styled.p<{ $error?: boolean }>` font-size: ${typography.body6}; margin-top: 4px; - color: ${(props) => (props.$error ? `${colors.red300}` : '#6b7280')}; - animation: ${fadeIn} 0.2s ease-in-out; + color: ${(props) => (props.$error ? colors.red300 : '#6b7280')}; + animation: ${fadeIn} 0.3s ease-in-out; /* 애니메이션 시간 증가 */ `; export const CustomInput: React.FC = ({ @@ -145,14 +155,15 @@ export const CustomInput: React.FC = ({ disabled = false, variant = 'outlined', suffix, + hasButton = false, ...props }) => { return ( - + {label && } - + - {suffix && {suffix}} + {suffix && {suffix}} {(error || hint) && {error || hint}}