From f16fbb27bb8630dd6f5bfe7c3a24a5047768ae3d Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Sun, 31 May 2026 01:16:57 +0900 Subject: [PATCH 1/6] =?UTF-8?q?design:=20=ED=8F=B4=EB=8D=94=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20UI=20=EA=B0=9C=ED=8E=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ui/folder-card.tsx | 182 ++++++++++++++++++++-------------- 1 file changed, 109 insertions(+), 73 deletions(-) diff --git a/components/ui/folder-card.tsx b/components/ui/folder-card.tsx index 81ffacc..36be964 100644 --- a/components/ui/folder-card.tsx +++ b/components/ui/folder-card.tsx @@ -1,11 +1,25 @@ import { useRef } from 'react'; -import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { LinearGradient } from 'expo-linear-gradient'; +import { Pressable, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + import { Colors, Typography } from '@/constants/theme'; import { useGuardedPress } from '@/utils/press-guard'; -import { AppIcon } from './app-icon'; +import { IconSymbol } from './icon-symbol'; + +type FolderCardVariant = 'tabbed' | 'plain'; const DEFAULT_CARD_WIDTH = 144; +const CARD_HEIGHT = 128; +const CARD_BODY_TOP = 16; +const CARD_BODY_MIN_HEIGHT = 112; +const CARD_TAB_WIDTH = 72; +const CARD_TAB_HEIGHT = 30; +const CARD_TAB_LEFT = 10; +const CARD_HORIZONTAL_PADDING = 14; +const CARD_VERTICAL_PADDING = 18; +const CARD_MENU_SIZE = 22; +const CARD_RADIUS = 12; +const TAB_RADIUS = 8; +const TOUCH_HIT_SLOP = 8; export interface AnchorPosition { x: number; @@ -21,6 +35,8 @@ export interface FolderCardProps { onPress?: () => void; onMorePress?: (anchor: AnchorPosition) => void; disabled?: boolean; + icon?: boolean; + variant?: FolderCardVariant; } export function FolderCard({ @@ -30,11 +46,14 @@ export function FolderCard({ onPress, onMorePress, disabled = false, + icon = true, + variant = 'tabbed', }: FolderCardProps) { const moreRef = useRef(null); const cardWidth = width ?? DEFAULT_CARD_WIDTH; const guardedOnPress = useGuardedPress(onPress, { disabled }); const guardedOnMorePress = useGuardedPress(onMorePress, { disabled }); + const isTabbed = variant === 'tabbed'; const handleMorePress = () => { moreRef.current?.measure((_fx, _fy, measuredWidth, measuredHeight, px, py) => { @@ -44,55 +63,52 @@ export function FolderCard({ return ( [styles.root, { width: cardWidth }, pressed && !disabled && styles.pressed]} + style={({ pressed }) => [ + styles.root, + { width: cardWidth }, + isTabbed ? styles.rootTabbed : styles.rootPlain, + pressed && !disabled && styles.pressed, + ]} onPress={guardedOnPress} accessibilityRole="button" accessibilityLabel={`${folderName} 폴더, ${urlCount}개`} accessibilityState={{ disabled }} > - {disabled ? ( - - - - {urlCount}개 - - + ) : null} + + + {urlCount}개 + {icon ? ( + + + - + - - {folderName} - - + ) : null} - ) : ( - - - - {urlCount}개 - - - - - - {folderName} - - - - )} + + {folderName} + + ); } @@ -101,54 +117,74 @@ const styles = StyleSheet.create({ root: { flexShrink: 0, }, - shadow: { - borderRadius: 16, - shadowColor: Colors.brand.shadow, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.08, - shadowRadius: 8, - elevation: 4, + rootTabbed: { + height: CARD_HEIGHT, + paddingTop: CARD_BODY_TOP, }, - shadowActive: { - backgroundColor: Colors.brand.folderGradientEnd, + rootPlain: { + minHeight: CARD_BODY_MIN_HEIGHT, }, - shadowDisabled: { + tab: { + position: 'absolute', + top: 0, + left: CARD_TAB_LEFT, + width: CARD_TAB_WIDTH, + height: CARD_TAB_HEIGHT, + borderTopLeftRadius: TAB_RADIUS, + borderTopRightRadius: TAB_RADIUS, + borderWidth: 1, + borderBottomWidth: 0, + borderColor: Colors.brand.line, backgroundColor: Colors.brand.softMint, }, - card: { - borderRadius: 16, - paddingHorizontal: 12, - paddingVertical: 12, - width: 144, - minHeight: 96, - justifyContent: 'space-between', - }, - pressed: { - opacity: 0.8, + tabDisabled: { + backgroundColor: Colors.brand.softMint, + borderColor: Colors.brand.line, }, - cardDisabled: { - borderRadius: 16, - paddingHorizontal: 12, - paddingVertical: 12, - width: 144, - minHeight: 96, + body: { + flex: 1, + minHeight: CARD_BODY_MIN_HEIGHT, justifyContent: 'space-between', + borderRadius: CARD_RADIUS, + borderWidth: 1, + borderColor: Colors.brand.line, + backgroundColor: Colors.brand.surface, + paddingHorizontal: CARD_HORIZONTAL_PADDING, + paddingVertical: CARD_VERTICAL_PADDING, overflow: 'hidden', }, + bodyPlain: { + flex: 0, + }, + bodyDisabled: { + borderColor: Colors.brand.line, + backgroundColor: Colors.brand.surface, + }, + pressed: { + opacity: 0.82, + }, header: { + minHeight: CARD_MENU_SIZE, flexDirection: 'row', justifyContent: 'space-between', - alignItems: 'flex-start', + alignItems: 'center', + gap: 8, }, count: { - ...Typography.regular12, + ...Typography.body, color: Colors.brand.textSecondary, }, countDisabled: { color: Colors.brand.textHint, }, + moreButton: { + width: CARD_MENU_SIZE, + height: CARD_MENU_SIZE, + alignItems: 'center', + justifyContent: 'center', + }, folderName: { - ...Typography.section, + ...Typography.title, color: Colors.brand.text, }, folderNameDisabled: { From ff490e4ed5ef478f11aff7bd47e9dcbbebcc41d8 Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Sun, 31 May 2026 01:24:46 +0900 Subject: [PATCH 2/6] =?UTF-8?q?design:=20=ED=8F=B4=EB=8D=94=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=9D=80=20=ED=99=94=EB=A9=B4=20=ED=81=AC?= =?UTF-8?q?=EA=B8=B0=20=EB=B3=B4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ui/folder-card.tsx | 69 ++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/components/ui/folder-card.tsx b/components/ui/folder-card.tsx index 36be964..e561705 100644 --- a/components/ui/folder-card.tsx +++ b/components/ui/folder-card.tsx @@ -20,6 +20,14 @@ const CARD_MENU_SIZE = 22; const CARD_RADIUS = 12; const TAB_RADIUS = 8; const TOUCH_HIT_SLOP = 8; +const COMPACT_CARD_HEIGHT = 116; +const COMPACT_CARD_BODY_TOP = 14; +const COMPACT_CARD_BODY_MIN_HEIGHT = 102; +const COMPACT_CARD_TAB_WIDTH = 64; +const COMPACT_CARD_TAB_HEIGHT = 26; +const COMPACT_CARD_HORIZONTAL_PADDING = 12; +const COMPACT_CARD_VERTICAL_PADDING = 14; +const COMPACT_CARD_MENU_SIZE = 20; export interface AnchorPosition { x: number; @@ -54,6 +62,15 @@ export function FolderCard({ const guardedOnPress = useGuardedPress(onPress, { disabled }); const guardedOnMorePress = useGuardedPress(onMorePress, { disabled }); const isTabbed = variant === 'tabbed'; + const isCompact = cardWidth <= DEFAULT_CARD_WIDTH; + const cardHeight = isCompact ? COMPACT_CARD_HEIGHT : CARD_HEIGHT; + const bodyTop = isCompact ? COMPACT_CARD_BODY_TOP : CARD_BODY_TOP; + const bodyMinHeight = isCompact ? COMPACT_CARD_BODY_MIN_HEIGHT : CARD_BODY_MIN_HEIGHT; + const tabWidth = isCompact ? COMPACT_CARD_TAB_WIDTH : CARD_TAB_WIDTH; + const tabHeight = isCompact ? COMPACT_CARD_TAB_HEIGHT : CARD_TAB_HEIGHT; + const horizontalPadding = isCompact ? COMPACT_CARD_HORIZONTAL_PADDING : CARD_HORIZONTAL_PADDING; + const verticalPadding = isCompact ? COMPACT_CARD_VERTICAL_PADDING : CARD_VERTICAL_PADDING; + const menuSize = isCompact ? COMPACT_CARD_MENU_SIZE : CARD_MENU_SIZE; const handleMorePress = () => { moreRef.current?.measure((_fx, _fy, measuredWidth, measuredHeight, px, py) => { @@ -66,7 +83,9 @@ export function FolderCard({ style={({ pressed }) => [ styles.root, { width: cardWidth }, - isTabbed ? styles.rootTabbed : styles.rootPlain, + isTabbed + ? [styles.rootTabbed, { height: cardHeight, paddingTop: bodyTop }] + : [styles.rootPlain, { minHeight: bodyMinHeight }], pressed && !disabled && styles.pressed, ]} onPress={guardedOnPress} @@ -75,12 +94,26 @@ export function FolderCard({ accessibilityState={{ disabled }} > {isTabbed ? ( - + ) : null} ) : null} - + {folderName} @@ -117,19 +157,12 @@ const styles = StyleSheet.create({ root: { flexShrink: 0, }, - rootTabbed: { - height: CARD_HEIGHT, - paddingTop: CARD_BODY_TOP, - }, - rootPlain: { - minHeight: CARD_BODY_MIN_HEIGHT, - }, + rootTabbed: {}, + rootPlain: {}, tab: { position: 'absolute', top: 0, left: CARD_TAB_LEFT, - width: CARD_TAB_WIDTH, - height: CARD_TAB_HEIGHT, borderTopLeftRadius: TAB_RADIUS, borderTopRightRadius: TAB_RADIUS, borderWidth: 1, @@ -143,14 +176,11 @@ const styles = StyleSheet.create({ }, body: { flex: 1, - minHeight: CARD_BODY_MIN_HEIGHT, justifyContent: 'space-between', borderRadius: CARD_RADIUS, borderWidth: 1, borderColor: Colors.brand.line, backgroundColor: Colors.brand.surface, - paddingHorizontal: CARD_HORIZONTAL_PADDING, - paddingVertical: CARD_VERTICAL_PADDING, overflow: 'hidden', }, bodyPlain: { @@ -164,7 +194,7 @@ const styles = StyleSheet.create({ opacity: 0.82, }, header: { - minHeight: CARD_MENU_SIZE, + minHeight: COMPACT_CARD_MENU_SIZE, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', @@ -187,6 +217,9 @@ const styles = StyleSheet.create({ ...Typography.title, color: Colors.brand.text, }, + folderNameCompact: { + ...Typography.section, + }, folderNameDisabled: { color: Colors.brand.textHint, }, From f886323176f10d8f9de3bfa4ef3b5fcf6f963a78 Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Sun, 31 May 2026 01:45:59 +0900 Subject: [PATCH 3/6] =?UTF-8?q?design:=20=ED=8F=B4=EB=8D=94=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=88=8C=EB=A6=BC=20=EC=83=81=ED=83=9C=20UI=20?= =?UTF-8?q?=EB=B3=B4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ui/folder-card.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/ui/folder-card.tsx b/components/ui/folder-card.tsx index e561705..01ebacf 100644 --- a/components/ui/folder-card.tsx +++ b/components/ui/folder-card.tsx @@ -12,7 +12,6 @@ const CARD_HEIGHT = 128; const CARD_BODY_TOP = 16; const CARD_BODY_MIN_HEIGHT = 112; const CARD_TAB_WIDTH = 72; -const CARD_TAB_HEIGHT = 30; const CARD_TAB_LEFT = 10; const CARD_HORIZONTAL_PADDING = 14; const CARD_VERTICAL_PADDING = 18; @@ -24,10 +23,10 @@ const COMPACT_CARD_HEIGHT = 116; const COMPACT_CARD_BODY_TOP = 14; const COMPACT_CARD_BODY_MIN_HEIGHT = 102; const COMPACT_CARD_TAB_WIDTH = 64; -const COMPACT_CARD_TAB_HEIGHT = 26; const COMPACT_CARD_HORIZONTAL_PADDING = 12; const COMPACT_CARD_VERTICAL_PADDING = 14; const COMPACT_CARD_MENU_SIZE = 20; +const PRESSED_SCALE = 0.98; export interface AnchorPosition { x: number; @@ -67,7 +66,6 @@ export function FolderCard({ const bodyTop = isCompact ? COMPACT_CARD_BODY_TOP : CARD_BODY_TOP; const bodyMinHeight = isCompact ? COMPACT_CARD_BODY_MIN_HEIGHT : CARD_BODY_MIN_HEIGHT; const tabWidth = isCompact ? COMPACT_CARD_TAB_WIDTH : CARD_TAB_WIDTH; - const tabHeight = isCompact ? COMPACT_CARD_TAB_HEIGHT : CARD_TAB_HEIGHT; const horizontalPadding = isCompact ? COMPACT_CARD_HORIZONTAL_PADDING : CARD_HORIZONTAL_PADDING; const verticalPadding = isCompact ? COMPACT_CARD_VERTICAL_PADDING : CARD_VERTICAL_PADDING; const menuSize = isCompact ? COMPACT_CARD_MENU_SIZE : CARD_MENU_SIZE; @@ -99,7 +97,7 @@ export function FolderCard({ styles.tab, { width: tabWidth, - height: tabHeight, + height: bodyTop, }, disabled && styles.tabDisabled, ]} @@ -191,7 +189,7 @@ const styles = StyleSheet.create({ backgroundColor: Colors.brand.surface, }, pressed: { - opacity: 0.82, + transform: [{ scale: PRESSED_SCALE }], }, header: { minHeight: COMPACT_CARD_MENU_SIZE, From 0b4bb7d502ad277d795cc39a3e9181c1d1cdfc20 Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Sun, 31 May 2026 02:30:46 +0900 Subject: [PATCH 4/6] =?UTF-8?q?design:=20=ED=8F=B4=EB=8D=94=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=94=94=EC=9E=90=EC=9D=B8=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ui/folder-card.tsx | 80 +++++++++++++++-------------------- constants/theme.ts | 36 ++++++++++++++++ 2 files changed, 69 insertions(+), 47 deletions(-) diff --git a/components/ui/folder-card.tsx b/components/ui/folder-card.tsx index 01ebacf..0f04ed0 100644 --- a/components/ui/folder-card.tsx +++ b/components/ui/folder-card.tsx @@ -1,32 +1,13 @@ import { useRef } from 'react'; import { Pressable, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import { Colors, Typography } from '@/constants/theme'; +import { Colors, ComponentTokens, Typography } from '@/constants/theme'; import { useGuardedPress } from '@/utils/press-guard'; import { IconSymbol } from './icon-symbol'; type FolderCardVariant = 'tabbed' | 'plain'; -const DEFAULT_CARD_WIDTH = 144; -const CARD_HEIGHT = 128; -const CARD_BODY_TOP = 16; -const CARD_BODY_MIN_HEIGHT = 112; -const CARD_TAB_WIDTH = 72; -const CARD_TAB_LEFT = 10; -const CARD_HORIZONTAL_PADDING = 14; -const CARD_VERTICAL_PADDING = 18; -const CARD_MENU_SIZE = 22; -const CARD_RADIUS = 12; -const TAB_RADIUS = 8; -const TOUCH_HIT_SLOP = 8; -const COMPACT_CARD_HEIGHT = 116; -const COMPACT_CARD_BODY_TOP = 14; -const COMPACT_CARD_BODY_MIN_HEIGHT = 102; -const COMPACT_CARD_TAB_WIDTH = 64; -const COMPACT_CARD_HORIZONTAL_PADDING = 12; -const COMPACT_CARD_VERTICAL_PADDING = 14; -const COMPACT_CARD_MENU_SIZE = 20; -const PRESSED_SCALE = 0.98; +const FOLDER_CARD = ComponentTokens.folderCard; export interface AnchorPosition { x: number; @@ -57,18 +38,18 @@ export function FolderCard({ variant = 'tabbed', }: FolderCardProps) { const moreRef = useRef(null); - const cardWidth = width ?? DEFAULT_CARD_WIDTH; + const cardWidth = width ?? FOLDER_CARD.defaultWidth; const guardedOnPress = useGuardedPress(onPress, { disabled }); const guardedOnMorePress = useGuardedPress(onMorePress, { disabled }); const isTabbed = variant === 'tabbed'; - const isCompact = cardWidth <= DEFAULT_CARD_WIDTH; - const cardHeight = isCompact ? COMPACT_CARD_HEIGHT : CARD_HEIGHT; - const bodyTop = isCompact ? COMPACT_CARD_BODY_TOP : CARD_BODY_TOP; - const bodyMinHeight = isCompact ? COMPACT_CARD_BODY_MIN_HEIGHT : CARD_BODY_MIN_HEIGHT; - const tabWidth = isCompact ? COMPACT_CARD_TAB_WIDTH : CARD_TAB_WIDTH; - const horizontalPadding = isCompact ? COMPACT_CARD_HORIZONTAL_PADDING : CARD_HORIZONTAL_PADDING; - const verticalPadding = isCompact ? COMPACT_CARD_VERTICAL_PADDING : CARD_VERTICAL_PADDING; - const menuSize = isCompact ? COMPACT_CARD_MENU_SIZE : CARD_MENU_SIZE; + const isCompact = cardWidth <= FOLDER_CARD.defaultWidth; + const cardHeight = isCompact ? FOLDER_CARD.compact.height : FOLDER_CARD.height; + const bodyTop = isCompact ? FOLDER_CARD.compact.bodyTop : FOLDER_CARD.bodyTop; + const bodyMinHeight = isCompact ? FOLDER_CARD.compact.bodyMinHeight : FOLDER_CARD.bodyMinHeight; + const tabWidth = isCompact ? FOLDER_CARD.compact.tabWidth : FOLDER_CARD.tabWidth; + const horizontalPadding = isCompact ? FOLDER_CARD.compact.horizontalPadding : FOLDER_CARD.horizontalPadding; + const verticalPadding = isCompact ? FOLDER_CARD.compact.verticalPadding : FOLDER_CARD.verticalPadding; + const menuSize = isCompact ? FOLDER_CARD.compact.menuSize : FOLDER_CARD.menuSize; const handleMorePress = () => { moreRef.current?.measure((_fx, _fy, measuredWidth, measuredHeight, px, py) => { @@ -123,7 +104,12 @@ export function FolderCard({ @@ -142,7 +128,7 @@ export function FolderCard({ isCompact && styles.folderNameCompact, disabled && styles.folderNameDisabled, ]} - numberOfLines={2} + numberOfLines={FOLDER_CARD.folderNameLines} > {folderName} @@ -159,12 +145,12 @@ const styles = StyleSheet.create({ rootPlain: {}, tab: { position: 'absolute', - top: 0, - left: CARD_TAB_LEFT, - borderTopLeftRadius: TAB_RADIUS, - borderTopRightRadius: TAB_RADIUS, - borderWidth: 1, - borderBottomWidth: 0, + top: FOLDER_CARD.origin, + left: FOLDER_CARD.tabLeft, + borderTopLeftRadius: FOLDER_CARD.tabRadius, + borderTopRightRadius: FOLDER_CARD.tabRadius, + borderWidth: FOLDER_CARD.borderWidth, + borderBottomWidth: FOLDER_CARD.hiddenBorderWidth, borderColor: Colors.brand.line, backgroundColor: Colors.brand.softMint, }, @@ -175,10 +161,10 @@ const styles = StyleSheet.create({ body: { flex: 1, justifyContent: 'space-between', - borderRadius: CARD_RADIUS, - borderWidth: 1, + borderRadius: FOLDER_CARD.radius, + borderWidth: FOLDER_CARD.borderWidth, borderColor: Colors.brand.line, - backgroundColor: Colors.brand.surface, + backgroundColor: Colors.brand.folderCard.body, overflow: 'hidden', }, bodyPlain: { @@ -186,17 +172,17 @@ const styles = StyleSheet.create({ }, bodyDisabled: { borderColor: Colors.brand.line, - backgroundColor: Colors.brand.surface, + backgroundColor: Colors.brand.folderCard.body, }, pressed: { - transform: [{ scale: PRESSED_SCALE }], + transform: [{ scale: FOLDER_CARD.pressedScale }], }, header: { - minHeight: COMPACT_CARD_MENU_SIZE, + minHeight: FOLDER_CARD.compact.menuSize, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - gap: 8, + gap: FOLDER_CARD.headerGap, }, count: { ...Typography.body, @@ -206,8 +192,8 @@ const styles = StyleSheet.create({ color: Colors.brand.textHint, }, moreButton: { - width: CARD_MENU_SIZE, - height: CARD_MENU_SIZE, + width: FOLDER_CARD.menuSize, + height: FOLDER_CARD.menuSize, alignItems: 'center', justifyContent: 'center', }, diff --git a/constants/theme.ts b/constants/theme.ts index 07c0dc9..8810bf8 100644 --- a/constants/theme.ts +++ b/constants/theme.ts @@ -25,6 +25,7 @@ const dangerBackgroundColor = '#F5C8C8'; const selectedOverlayColor = 'rgba(0,0,0,0.08)'; const modalBackdropColor = 'rgba(0,0,0,0.4)'; const inverseSubtleOverlayColor = 'rgba(255,255,255,0.08)'; +const folderCardBodyColor = '#FAFCFB'; const socialButtonBackgroundColor = '#FFFFFF'; const socialButtonBorderColor = lineColor; @@ -90,6 +91,9 @@ export const Colors = { }, folderGradientStart: '#8FE2C6', folderGradientEnd: '#499B80', + folderCard: { + body: folderCardBodyColor, + }, }, social: { buttonBackground: socialButtonBackgroundColor, @@ -165,3 +169,35 @@ export const Typography = { bold12: { fontSize: fontSizeBold12, fontWeight: fontWeightBold }, regular12: { fontSize: fontSizeRegular12, fontWeight: fontWeightRegular }, }; + +export const ComponentTokens = { + folderCard: { + defaultWidth: 144, + height: 128, + bodyTop: 16, + bodyMinHeight: 112, + tabWidth: 72, + tabLeft: 10, + horizontalPadding: 14, + verticalPadding: 18, + menuSize: 22, + radius: 12, + tabRadius: 8, + touchHitSlop: 8, + pressedScale: 0.98, + borderWidth: 1, + hiddenBorderWidth: 0, + origin: 0, + headerGap: 8, + folderNameLines: 2, + compact: { + height: 116, + bodyTop: 14, + bodyMinHeight: 102, + tabWidth: 64, + horizontalPadding: 12, + verticalPadding: 14, + menuSize: 20, + }, + }, +} as const; From 30fd678a3b379bcd674ced2ffed141666a64daab Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Sun, 31 May 2026 14:34:25 +0900 Subject: [PATCH 5/6] =?UTF-8?q?design:=20=ED=8F=B4=EB=8D=94=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/(folder)/index.tsx | 71 ++++++++++++++++++++--------------- components/ui/folder-card.tsx | 3 +- constants/theme.ts | 23 +++++++++--- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/app/(tabs)/(folder)/index.tsx b/app/(tabs)/(folder)/index.tsx index 690aed0..68edfc6 100644 --- a/app/(tabs)/(folder)/index.tsx +++ b/app/(tabs)/(folder)/index.tsx @@ -11,6 +11,7 @@ import { Text, TextInput, TouchableOpacity, + useWindowDimensions, View, type KeyboardEvent, } from 'react-native'; @@ -20,9 +21,8 @@ import { useLocalSearchParams, useRouter } from 'expo-router'; import { AddFolderButton } from '@/components/ui/add-folder-button'; import { FolderCard } from '@/components/ui/folder-card'; import { FolderContextMenu } from '@/components/ui/folder-context-menu'; -import { SectionHeader } from '@/components/ui/section-header'; import { Toast } from '@/components/ui/toast'; -import { Colors, Typography } from '@/constants/theme'; +import { Colors, ComponentTokens, Typography } from '@/constants/theme'; import type { AnchorPosition } from '@/components/ui/folder-card'; import { useSavedLinks } from '@/context/saved-links-context'; import { getFolderErrorMessage, useFolders } from '@/context/folders-context'; @@ -35,11 +35,14 @@ type MenuState = { folderId?: number; }; -const CONTENT_HORIZONTAL_PADDING = 24; -const CANVAS_PADDING = 16; -const FOLDER_GRID_GAP = 12; -const DEFAULT_FOLDER_CARD_WIDTH = 144; -const MIN_TWO_COLUMN_CARD_WIDTH = 120; +const FOLDER_SCREEN = ComponentTokens.folderScreen; +const FOLDER_CARD = ComponentTokens.folderCard; +const CONTENT_HORIZONTAL_PADDING = FOLDER_SCREEN.contentHorizontalPadding; +const CANVAS_PADDING_HORIZONTAL = FOLDER_SCREEN.canvasPaddingHorizontal; +const CANVAS_PADDING_VERTICAL = FOLDER_SCREEN.canvasPaddingVertical; +const FOLDER_GRID_GAP = FOLDER_SCREEN.gridGap; +const DEFAULT_FOLDER_CARD_WIDTH = FOLDER_CARD.defaultWidth; +const MIN_TWO_COLUMN_CARD_WIDTH = FOLDER_SCREEN.minTwoColumnCardWidth; const RENAME_MODAL_BOTTOM_GAP = 16; const RENAME_KEYBOARD_TOP_GAP = 8; @@ -65,6 +68,7 @@ const syncKeyboardLayoutAnimation = (event: KeyboardEvent) => { export default function FolderScreen() { const insets = useSafeAreaInsets(); const router = useRouter(); + const { width: windowWidth } = useWindowDimensions(); const tabBarHeight = useBottomTabBarHeight(); const { folderCreated: folderCreatedParam } = useLocalSearchParams<{ folderCreated?: string | string[]; @@ -99,6 +103,7 @@ export default function FolderScreen() { const renameInputRef = useRef(null); const renameFocusTimerRef = useRef | null>(null); const folderCreated = typeof folderCreatedParam === 'string' ? folderCreatedParam : undefined; + const isCompactWidth = windowWidth < FOLDER_SCREEN.compactWidth; const folders = useMemo( () => rawFolders.map((folder) => ({ ...folder })), @@ -111,16 +116,10 @@ export default function FolderScreen() { return DEFAULT_FOLDER_CARD_WIDTH; } - const defaultTwoColumnWidth = DEFAULT_FOLDER_CARD_WIDTH * 2 + FOLDER_GRID_GAP; + const twoColumnCardWidth = Math.floor((availableWidth - FOLDER_GRID_GAP) / 2); - if (availableWidth >= defaultTwoColumnWidth) { - return DEFAULT_FOLDER_CARD_WIDTH; - } - - const compactTwoColumnWidth = Math.floor((availableWidth - FOLDER_GRID_GAP) / 2); - - if (compactTwoColumnWidth >= MIN_TWO_COLUMN_CARD_WIDTH) { - return compactTwoColumnWidth; + if (twoColumnCardWidth >= MIN_TWO_COLUMN_CARD_WIDTH) { + return twoColumnCardWidth; } return availableWidth; @@ -283,17 +282,15 @@ export default function FolderScreen() { > {/* 상단 헤더 */} - 폴더 - 저장한 링크를 폴더별로 정리해요 + 폴더 + + 저장한 링크를 폴더별로 정리해요 + + {/* 폴더 캔버스 */} - } - /> - {errorMessage ? ( {errorMessage} @@ -315,6 +312,7 @@ export default function FolderScreen() { folderName={folder.name} urlCount={folder.linkCount} width={folderCardWidth} + variant="plain" onPress={() => router.push({ pathname: '/(tabs)/(folder)/[id]' as any, params: { id: folder.id } })} onMorePress={(anchor) => handleMorePress(folder.id, anchor)} /> @@ -446,21 +444,34 @@ const styles = StyleSheet.create({ gap: 4, }, title: { - ...Typography.pageTitle, + ...Typography.displayMedium, color: Colors.brand.text, }, + titleCompact: { + ...Typography.pageTitle, + }, subtitle: { + flex: 1, + flexShrink: 1, ...Typography.caption, color: Colors.brand.textSecondary, }, + subtitleRow: { + flexDirection: 'row', + alignItems: 'flex-end', + justifyContent: 'space-between', + gap: FOLDER_SCREEN.headerRowGap, + marginTop: FOLDER_SCREEN.subtitleRowOffsetTop, + }, canvas: { - backgroundColor: Colors.brand.surface, - borderRadius: 20, - borderWidth: 1, - borderColor: Colors.brand.line, - padding: CANVAS_PADDING, - gap: 16, + backgroundColor: Colors.brand.transparent, + borderRadius: FOLDER_SCREEN.canvasRadius, + borderWidth: FOLDER_SCREEN.canvasBorderWidth, + borderColor: Colors.brand.transparent, + paddingHorizontal: CANVAS_PADDING_HORIZONTAL, + paddingVertical: CANVAS_PADDING_VERTICAL, + gap: FOLDER_SCREEN.canvasGap, }, grid: { width: '100%', diff --git a/components/ui/folder-card.tsx b/components/ui/folder-card.tsx index 0f04ed0..64c8017 100644 --- a/components/ui/folder-card.tsx +++ b/components/ui/folder-card.tsx @@ -97,7 +97,7 @@ export function FolderCard({ !isTabbed && styles.bodyPlain, ]} > - + {urlCount}개 {icon ? ( @@ -178,7 +178,6 @@ const styles = StyleSheet.create({ transform: [{ scale: FOLDER_CARD.pressedScale }], }, header: { - minHeight: FOLDER_CARD.compact.menuSize, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', diff --git a/constants/theme.ts b/constants/theme.ts index 8810bf8..6ed0df0 100644 --- a/constants/theme.ts +++ b/constants/theme.ts @@ -25,7 +25,7 @@ const dangerBackgroundColor = '#F5C8C8'; const selectedOverlayColor = 'rgba(0,0,0,0.08)'; const modalBackdropColor = 'rgba(0,0,0,0.4)'; const inverseSubtleOverlayColor = 'rgba(255,255,255,0.08)'; -const folderCardBodyColor = '#FAFCFB'; +const folderCardBodyColor = surfaceColor; const socialButtonBackgroundColor = '#FFFFFF'; const socialButtonBorderColor = lineColor; @@ -171,15 +171,28 @@ export const Typography = { }; export const ComponentTokens = { + folderScreen: { + contentHorizontalPadding: 24, + canvasPaddingHorizontal: 0, + canvasPaddingVertical: 16, + canvasGap: 16, + canvasRadius: 20, + canvasBorderWidth: 0, + gridGap: 12, + minTwoColumnCardWidth: 120, + headerRowGap: 10, + compactWidth: 380, + subtitleRowOffsetTop: -2, + }, folderCard: { defaultWidth: 144, - height: 128, + height: 172, bodyTop: 16, - bodyMinHeight: 112, + bodyMinHeight: 156, tabWidth: 72, tabLeft: 10, - horizontalPadding: 14, - verticalPadding: 18, + horizontalPadding: 18, + verticalPadding: 20, menuSize: 22, radius: 12, tabRadius: 8, From 677a1c5a78cb8fcfe164bed85196f6d38121cf94 Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Sun, 31 May 2026 15:59:50 +0900 Subject: [PATCH 6/6] =?UTF-8?q?design:=20=ED=8F=B4=EB=8D=94=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EA=B7=B8=EB=A6=BC=EC=9E=90=EC=99=80=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=81=AC=EA=B8=B0=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/(folder)/index.tsx | 11 +++-------- components/ui/folder-card.tsx | 15 +++++++++++---- constants/theme.ts | 15 ++++++++------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/app/(tabs)/(folder)/index.tsx b/app/(tabs)/(folder)/index.tsx index 68edfc6..717f807 100644 --- a/app/(tabs)/(folder)/index.tsx +++ b/app/(tabs)/(folder)/index.tsx @@ -38,7 +38,6 @@ type MenuState = { const FOLDER_SCREEN = ComponentTokens.folderScreen; const FOLDER_CARD = ComponentTokens.folderCard; const CONTENT_HORIZONTAL_PADDING = FOLDER_SCREEN.contentHorizontalPadding; -const CANVAS_PADDING_HORIZONTAL = FOLDER_SCREEN.canvasPaddingHorizontal; const CANVAS_PADDING_VERTICAL = FOLDER_SCREEN.canvasPaddingVertical; const FOLDER_GRID_GAP = FOLDER_SCREEN.gridGap; const DEFAULT_FOLDER_CARD_WIDTH = FOLDER_CARD.defaultWidth; @@ -313,6 +312,7 @@ export default function FolderScreen() { urlCount={folder.linkCount} width={folderCardWidth} variant="plain" + compactFolderName={isCompactWidth} onPress={() => router.push({ pathname: '/(tabs)/(folder)/[id]' as any, params: { id: folder.id } })} onMorePress={(anchor) => handleMorePress(folder.id, anchor)} /> @@ -444,11 +444,11 @@ const styles = StyleSheet.create({ gap: 4, }, title: { - ...Typography.displayMedium, + ...Typography.pageTitle, color: Colors.brand.text, }, titleCompact: { - ...Typography.pageTitle, + ...Typography.title, }, subtitle: { flex: 1, @@ -465,11 +465,6 @@ const styles = StyleSheet.create({ }, canvas: { - backgroundColor: Colors.brand.transparent, - borderRadius: FOLDER_SCREEN.canvasRadius, - borderWidth: FOLDER_SCREEN.canvasBorderWidth, - borderColor: Colors.brand.transparent, - paddingHorizontal: CANVAS_PADDING_HORIZONTAL, paddingVertical: CANVAS_PADDING_VERTICAL, gap: FOLDER_SCREEN.canvasGap, }, diff --git a/components/ui/folder-card.tsx b/components/ui/folder-card.tsx index 64c8017..fc0520c 100644 --- a/components/ui/folder-card.tsx +++ b/components/ui/folder-card.tsx @@ -25,6 +25,7 @@ export interface FolderCardProps { disabled?: boolean; icon?: boolean; variant?: FolderCardVariant; + compactFolderName?: boolean; } export function FolderCard({ @@ -36,6 +37,7 @@ export function FolderCard({ disabled = false, icon = true, variant = 'tabbed', + compactFolderName, }: FolderCardProps) { const moreRef = useRef(null); const cardWidth = width ?? FOLDER_CARD.defaultWidth; @@ -43,6 +45,7 @@ export function FolderCard({ const guardedOnMorePress = useGuardedPress(onMorePress, { disabled }); const isTabbed = variant === 'tabbed'; const isCompact = cardWidth <= FOLDER_CARD.defaultWidth; + const shouldUseCompactFolderName = compactFolderName ?? isCompact; const cardHeight = isCompact ? FOLDER_CARD.compact.height : FOLDER_CARD.height; const bodyTop = isCompact ? FOLDER_CARD.compact.bodyTop : FOLDER_CARD.bodyTop; const bodyMinHeight = isCompact ? FOLDER_CARD.compact.bodyMinHeight : FOLDER_CARD.bodyMinHeight; @@ -125,7 +128,7 @@ export function FolderCard({