-
Notifications
You must be signed in to change notification settings - Fork 1
Feature: UI 전면 개선 #47
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
Changes from all commits
135e3bb
43520c7
6907c9a
79f697a
79338ba
6f996b7
6de1cf0
2762037
ddfa5cb
d51d4ab
328fc6d
3c38d36
72f025e
249229d
87cfe76
ea1d043
f4beefa
ad265da
06ab1c0
a29e436
7413ab5
996f747
dc3b876
6a62317
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ interface CardProps { | |
| onPress?: () => void; | ||
| style?: ViewStyle; | ||
| fullWidth?: boolean; | ||
| variant?: 'default' | 'light'; | ||
| } | ||
|
|
||
| export default function Card({ | ||
|
|
@@ -31,6 +32,7 @@ export default function Card({ | |
| onPress, | ||
| style, | ||
| fullWidth = false, | ||
| variant = 'default', | ||
| }: CardProps) { | ||
| const { width: screenWidth } = Dimensions.get('window'); | ||
| const [imageError, setImageError] = useState(false); | ||
|
|
@@ -52,6 +54,7 @@ export default function Card({ | |
| <CardContainer | ||
| fullWidth={fullWidth} | ||
| mode={finalMode} | ||
| variant={variant} | ||
| onPress={onPress} | ||
| style={style} | ||
| activeOpacity={0.8} | ||
|
|
@@ -77,7 +80,7 @@ export default function Card({ | |
| </LoadingContainer> | ||
| )} | ||
| {content?.hasStar && ( | ||
| <StarIcon size={20} color="#FFD700" fill="#FFD700" /> | ||
| <StarIcon size={20} color="#2d2d2d" fill="#2d2d2d" /> | ||
| )} | ||
| </ImageContainer> | ||
| </CardContainer> | ||
|
|
@@ -86,16 +89,19 @@ export default function Card({ | |
|
|
||
| if (finalMode === 'image-with-text') { | ||
| const contentWidth = screenWidth - 64; | ||
| const imageSize = contentWidth * 0.45; | ||
| const imageSize = contentWidth * 0.36; // 0.45에서 0.36으로 변경 (약 20% 감소) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스타일 가이드에서는 매직 넘버 사용을 지양하고 이름 있는 상수로 치환할 것을 권장합니다.1 Style Guide ReferencesFootnotes
|
||
| return ( | ||
| <CardContainer | ||
| fullWidth={fullWidth} | ||
| mode={finalMode} | ||
| variant={variant} | ||
| onPress={onPress} | ||
| style={style} | ||
| activeOpacity={0.8} | ||
| > | ||
| {content?.cardTitle && <CardTitle>{content.cardTitle}</CardTitle>} | ||
| {content?.cardTitle && ( | ||
| <CardTitle variant={variant}>{content.cardTitle}</CardTitle> | ||
| )} | ||
| <ContentRow> | ||
| {imageSource && ( | ||
| <CardImage | ||
|
|
@@ -107,14 +113,18 @@ export default function Card({ | |
| )} | ||
| {content && ( | ||
| <CardContentContainer> | ||
| {content.title && <Title>{content.title}</Title>} | ||
| {content.subtitle && <Subtitle>{content.subtitle}</Subtitle>} | ||
| {content.title && ( | ||
| <Title variant={variant}>{content.title}</Title> | ||
| )} | ||
| {content.subtitle && ( | ||
| <Subtitle variant={variant}>{content.subtitle}</Subtitle> | ||
| )} | ||
| {content.stats && ( | ||
| <StatsContainer> | ||
| {content.stats.map((stat, index) => ( | ||
| <StatItem key={index}> | ||
| <StatLabel>{stat.label}</StatLabel> | ||
| <StatValue>{stat.value}</StatValue> | ||
| <StatLabel variant={variant}>{stat.label}</StatLabel> | ||
| <StatValue variant={variant}>{stat.value}</StatValue> | ||
| </StatItem> | ||
| ))} | ||
| </StatsContainer> | ||
|
|
@@ -130,6 +140,7 @@ export default function Card({ | |
| <CardContainer | ||
| fullWidth={fullWidth} | ||
| mode={finalMode} | ||
| variant={variant} | ||
| onPress={onPress} | ||
| style={style} | ||
| activeOpacity={0.8} | ||
|
|
@@ -142,9 +153,11 @@ export default function Card({ | |
| const CardContainer = styled(TouchableOpacity)<{ | ||
| fullWidth: boolean; | ||
| mode: CardMode; | ||
| }>(({ fullWidth, mode }) => ({ | ||
| backgroundColor: 'rgba(255, 255, 255, 0.1)', | ||
| borderColor: 'rgba(255, 255, 255, 0.2)', | ||
| variant: 'default' | 'light'; | ||
| }>(({ fullWidth, mode, variant }) => ({ | ||
| backgroundColor: variant === 'light' ? '#ffffff' : 'rgba(255, 255, 255, 0.1)', | ||
| borderColor: | ||
| variant === 'light' ? 'rgba(0, 0, 0, 0.1)' : 'rgba(255, 255, 255, 0.2)', | ||
|
Comment on lines
+157
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스타일 가이드에 따라 색상 값은 theme 또는 constants 파일에서 관리하는 것이 좋습니다.1 예시: // theme.ts
export const theme = {
light: {
background: '#ffffff',
borderColor: 'rgba(0, 0, 0, 0.1)',
text: '#000000',
},
dark: {
background: 'rgba(255, 255, 255, 0.1)',
borderColor: 'rgba(255, 255, 255, 0.2)',
text: '#ffffff',
}
}
// CardContainer.tsx
const CardContainer = styled(TouchableOpacity)<...>(({ theme, variant }) => ({
backgroundColor: theme[variant].background,
borderColor: theme[variant].borderColor,
// ...
}));Style Guide ReferencesFootnotes
|
||
| borderWidth: 1, | ||
| borderRadius: 12, | ||
| padding: mode === 'only-image' ? 0 : 16, | ||
|
|
@@ -153,12 +166,14 @@ const CardContainer = styled(TouchableOpacity)<{ | |
| aspectRatio: fullWidth ? undefined : 1, | ||
| })); | ||
|
|
||
| const CardTitle = styled.Text({ | ||
| color: '#ffffff', | ||
| fontSize: 16, | ||
| fontWeight: '600', | ||
| marginBottom: 8, | ||
| }); | ||
| const CardTitle = styled.Text<{ variant?: 'default' | 'light' }>( | ||
| ({ variant }) => ({ | ||
| color: variant === 'light' ? '#000000' : '#ffffff', | ||
| fontSize: 16, | ||
| fontWeight: '600', | ||
| marginBottom: 8, | ||
| }), | ||
| ); | ||
|
|
||
| const ContentRow = styled.View({ | ||
| flexDirection: 'row', | ||
|
|
@@ -185,18 +200,21 @@ const CardContentContainer = styled.View({ | |
| flex: 1, | ||
| }); | ||
|
|
||
| const Title = styled.Text({ | ||
| color: '#ffffff', | ||
| const Title = styled.Text<{ variant?: 'default' | 'light' }>(({ variant }) => ({ | ||
| color: variant === 'light' ? '#000000' : '#ffffff', | ||
| fontSize: 18, | ||
| fontWeight: 'bold', | ||
| marginBottom: 4, | ||
| }); | ||
| })); | ||
|
|
||
| const Subtitle = styled.Text({ | ||
| color: 'rgba(255, 255, 255, 0.7)', | ||
| fontSize: 14, | ||
| marginBottom: 8, | ||
| }); | ||
| const Subtitle = styled.Text<{ variant?: 'default' | 'light' }>( | ||
| ({ variant }) => ({ | ||
| color: | ||
| variant === 'light' ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)', | ||
| fontSize: 14, | ||
| marginBottom: 8, | ||
| }), | ||
| ); | ||
|
|
||
| const StatsContainer = styled.View({ | ||
| flexDirection: 'row', | ||
|
|
@@ -207,17 +225,23 @@ const StatItem = styled.View({ | |
| alignItems: 'center', | ||
| }); | ||
|
|
||
| const StatLabel = styled.Text({ | ||
| color: 'rgba(255, 255, 255, 0.8)', | ||
| fontSize: 12, | ||
| marginBottom: 2, | ||
| }); | ||
| const StatLabel = styled.Text<{ variant?: 'default' | 'light' }>( | ||
| ({ variant }) => ({ | ||
| color: | ||
| variant === 'light' ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)', | ||
| fontSize: 12, | ||
| marginBottom: 2, | ||
| }), | ||
| ); | ||
|
|
||
| const StatValue = styled.Text({ | ||
| color: 'rgba(255, 255, 255, 0.8)', | ||
| fontSize: 14, | ||
| fontWeight: '600', | ||
| }); | ||
| const StatValue = styled.Text<{ variant?: 'default' | 'light' }>( | ||
| ({ variant }) => ({ | ||
| color: | ||
| variant === 'light' ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)', | ||
| fontSize: 14, | ||
| fontWeight: '600', | ||
| }), | ||
| ); | ||
|
|
||
| const StarIcon = styled(Star)({ | ||
| position: 'absolute', | ||
|
|
@@ -254,7 +278,7 @@ const LoadingContainer = styled.View({ | |
| }); | ||
|
|
||
| const LoadingText = styled.Text({ | ||
| color: '#007AFF', | ||
| color: '#2d2d2d', | ||
| fontSize: 12, | ||
| fontWeight: '500', | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import { Modal as RNModal, Text, TouchableOpacity, View } from 'react-native'; | ||
| import styled from '@emotion/native'; | ||
| import { LinearGradient } from 'expo-linear-gradient'; | ||
| import { theme } from '@/styles/theme'; | ||
|
|
||
| interface ModalProps { | ||
|
|
@@ -38,13 +39,23 @@ export default function Modal({ | |
| <ModalMessage>{message}</ModalMessage> | ||
| <ButtonContainer> | ||
| <ModalButton onPress={onCancel} disabled={disabled || loading}> | ||
| <ModalButtonGradient | ||
| colors={['#1a1a1a', '#2d2d2d', '#404040']} | ||
| start={{ x: 0, y: 0 }} | ||
| end={{ x: 1, y: 1 }} | ||
| /> | ||
|
Comment on lines
+42
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| <ModalButtonText>{cancelText}</ModalButtonText> | ||
| </ModalButton> | ||
| <ModalButton | ||
| onPress={onConfirm} | ||
| isPrimary | ||
| disabled={disabled || loading} | ||
| > | ||
| <ModalButtonGradient | ||
| colors={['#1a1a1a', '#2d2d2d', '#404040']} | ||
| start={{ x: 0, y: 0 }} | ||
| end={{ x: 1, y: 1 }} | ||
| /> | ||
| <ModalButtonText isPrimary> | ||
| {loading ? '저장 중...' : confirmText} | ||
| </ModalButtonText> | ||
|
|
@@ -101,16 +112,24 @@ const ModalButton = styled(TouchableOpacity)<{ | |
| flex: 1; | ||
| padding: 12px 16px; | ||
| border-radius: 8px; | ||
| background-color: ${({ isPrimary, disabled }) => { | ||
| if (disabled) return theme.colors.gray[300]; | ||
| return isPrimary ? theme.colors.primary[500] : theme.colors.gray[100]; | ||
| }}; | ||
| align-items: center; | ||
| position: relative; | ||
| overflow: hidden; | ||
| opacity: ${({ disabled }) => (disabled ? 0.6 : 1)}; | ||
| `; | ||
|
|
||
| const ModalButtonGradient = styled(LinearGradient)` | ||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| right: 0; | ||
| bottom: 0; | ||
| border-radius: 8px; | ||
| `; | ||
|
|
||
| const ModalButtonText = styled(Text)<{ isPrimary?: boolean }>` | ||
| font-size: 16px; | ||
| font-weight: 500; | ||
| color: ${({ isPrimary }) => (isPrimary ? '#ffffff' : theme.colors.gray[700])}; | ||
| color: #ffffff; | ||
| z-index: 1; | ||
| `; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스타일 가이드에 따르면 색상 값은 theme 또는 constants 파일로 관리해야 합니다.1
#2d2d2d와 같은 색상 값을 직접 사용하기보다,colors.ts에 정의된 테마 색상을 사용하도록 수정하는 것이 좋겠습니다.Style Guide References
Footnotes
재사용 가능한 색상, 폰트, spacing은 theme 또는 constants 파일로 관리해야 합니다. ↩