From 037c46f96153f799088897e1484ff223d74a5e3c Mon Sep 17 00:00:00 2001 From: "hang.nguyen" Date: Thu, 23 Oct 2025 15:30:51 +0700 Subject: [PATCH 01/13] feat: add props for code input --- src/components/CodeInput/CodeInput.tsx | 231 +++++++++++++++--- .../CodeInput/components/ErrorText.tsx | 18 ++ .../CodeInput/components/HelperText.tsx | 29 +++ src/components/CodeInput/components/index.ts | 2 + src/theme/components/CodeInput.ts | 47 ++++ 5 files changed, 295 insertions(+), 32 deletions(-) create mode 100644 src/components/CodeInput/components/ErrorText.tsx create mode 100644 src/components/CodeInput/components/HelperText.tsx create mode 100644 src/components/CodeInput/components/index.ts diff --git a/src/components/CodeInput/CodeInput.tsx b/src/components/CodeInput/CodeInput.tsx index 85d268a..ec9b932 100644 --- a/src/components/CodeInput/CodeInput.tsx +++ b/src/components/CodeInput/CodeInput.tsx @@ -7,12 +7,21 @@ import React, { useRef, useState, } from 'react' -import {KeyboardTypeOptions, StyleProp, TextInput, TextInputProps, TextStyle, ViewStyle} from 'react-native' +import { + KeyboardTypeOptions, + StyleProp, + TextInput, + TextInputProps, + TextProps, + TextStyle, + ViewStyle, +} from 'react-native' import styled from 'styled-components/native' import {useTheme} from '../../hooks' import {metrics} from '../../helpers' import {Cursor} from './Cursor' import {Text} from '../Text/Text' +import {ErrorText, HelperText} from './components' // Types type CodeInputValue = string @@ -102,6 +111,63 @@ interface CodeInputProps extends Omit + + /** Label text displayed above the code input */ + label?: string + + /** Custom label component to replace default label text */ + labelComponent?: ReactNode + + /** Styling for the label */ + labelStyle?: StyleProp + + /** Props to be passed to the label Text component */ + labelProps?: TextProps + + /** Show asterisk beside label for required fields */ + isRequire?: boolean + + /** Helper text displayed below the code input */ + helperText?: string + + /** Custom helper component to replace default helper text */ + helperComponent?: ReactNode + + /** Props to be passed to the helper text component */ + helperTextProps?: TextProps + + /** Error text displayed below the code input */ + errorText?: string + + /** Props to be passed to the error text component */ + errorProps?: TextProps + + /** Enable error state styling */ + error?: boolean + + /** Style for cell in error state */ + errorCellStyle?: StyleProp + + /** Enable success state styling */ + success?: boolean + + /** Style for cell in success state */ + successCellStyle?: StyleProp + + /** Style for cell in disabled state */ + disabledCellStyle?: StyleProp + + /** Style for cell in active state */ + activeCellStyle?: StyleProp + + /** React node to be rendered on the left side of the code input */ + leftComponent?: ReactNode + + /** React node to be rendered on the right side of the code input */ + rightComponent?: ReactNode } // Default constants moved to theme configuration @@ -135,6 +201,25 @@ export const CodeInput = forwardRef( autoFocus, disabled, testID = 'code-input', + containerStyle, + label, + labelComponent, + labelStyle, + labelProps, + isRequire, + helperText, + helperComponent, + helperTextProps, + errorText, + errorProps, + error, + errorCellStyle, + success, + successCellStyle, + disabledCellStyle, + activeCellStyle, + leftComponent, + rightComponent, ...textInputProps }, ref, @@ -295,7 +380,15 @@ export const CodeInput = forwardRef( const isCellFocused = isFocused && cellIndex === focusedCellIndex const hasCellValue = Boolean(cellValue) - const cellStyles = [cellStyle, hasCellValue && filledCellStyle, isCellFocused && focusCellStyle] + // Apply styles in priority order: disabled > error > success > focused > filled > default + const cellStyles = [ + cellStyle, + hasCellValue && filledCellStyle, + isCellFocused && (activeCellStyle ?? focusCellStyle), + success && (successCellStyle ?? CodeInputTheme.successCellStyle), + error && (errorCellStyle ?? CodeInputTheme.errorCellStyle), + disabled && (disabledCellStyle ?? CodeInputTheme.disabledCellStyle), + ] const wrapperStyles = [cellWrapperStyle, isCellFocused && focusCellWrapperStyle] @@ -311,7 +404,11 @@ export const CodeInput = forwardRef( accessibilityLabel={`Code input cell ${cellIndex + 1} of ${length}${ cellValue ? `, contains ${cellValue}` : ', empty' }`} - accessibilityHint={`Tap to ${cellValue ? 'clear and ' : ''}enter code digit`}> + accessibilityHint={`Tap to ${cellValue ? 'clear and ' : ''}enter code digit`} + accessibilityState={{ + disabled: !!disabled, + selected: isCellFocused, + }}> {renderCellContent(cellIndex, cellValue)} @@ -324,13 +421,20 @@ export const CodeInput = forwardRef( cellStyle, filledCellStyle, focusCellStyle, + activeCellStyle, cellWrapperStyle, focusCellWrapperStyle, handleCellPress, disabled, + error, + success, + errorCellStyle, + successCellStyle, + disabledCellStyle, length, testID, renderCellContent, + CodeInputTheme, ], ) @@ -349,34 +453,74 @@ export const CodeInput = forwardRef( } return ( - - - - {cells} - + + {labelComponent ? ( + + {labelComponent} + {isRequire && *} + + ) : ( + label && ( + + {label} + {isRequire && *} + + ) + )} + + + + {!!leftComponent && leftComponent} + + {cells} + + {!!rightComponent && rightComponent} + + + {!!errorText && } + {!errorText && (!!helperText || !!helperComponent) && ( + + )} ) }, @@ -385,10 +529,33 @@ export const CodeInput = forwardRef( CodeInput.displayName = 'CodeInput' // Styled components -const Container = styled.View({ +const Container = styled.View({}) + +const InputWrapper = styled.View({ position: 'relative', }) +const LabelContainer = styled.View(({theme}) => ({ + marginBottom: theme?.spacing?.tiny || 8, + flexDirection: 'row', + alignItems: 'center', +})) + +const LabelText = styled.Text(({theme}) => ({ + fontSize: theme?.fontSizes?.sm || 14, + color: theme?.colors?.darkText || '#333', + marginBottom: theme?.spacing?.tiny || 8, +})) + +const RequiredStar = styled.Text(({theme}) => ({ + color: theme?.colors?.errorText || '#ff0000', +})) + +const ComponentRow = styled.View({ + flexDirection: 'row', + alignItems: 'center', +}) + const CellWrapperStyled = styled.View({}) const Cell = styled.Pressable<{disabled?: boolean}>(({theme, disabled}) => ({ diff --git a/src/components/CodeInput/components/ErrorText.tsx b/src/components/CodeInput/components/ErrorText.tsx new file mode 100644 index 0000000..391aa62 --- /dev/null +++ b/src/components/CodeInput/components/ErrorText.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import type {TextProps} from 'react-native' +import styled from 'styled-components/native' + +interface ErrorTextProps { + errorText?: string + errorProps?: TextProps +} + +export const ErrorText: React.FC = ({errorText, errorProps}) => ( + {errorText} +) + +const ErrorTextStyled = styled.Text(({theme}) => ({ + fontSize: theme?.fontSizes?.['2xs'], + color: theme?.colors?.errorText, + marginTop: theme?.spacing?.tiny, +})) diff --git a/src/components/CodeInput/components/HelperText.tsx b/src/components/CodeInput/components/HelperText.tsx new file mode 100644 index 0000000..7d73916 --- /dev/null +++ b/src/components/CodeInput/components/HelperText.tsx @@ -0,0 +1,29 @@ +import React, {ReactNode} from 'react' +import type {TextProps} from 'react-native' +import styled from 'styled-components/native' + +interface HelperTextProps { + helperText?: string + helperComponent?: ReactNode + helperTextProps?: TextProps +} + +export const HelperText: React.FC = ({helperText, helperComponent, helperTextProps}) => { + // Prioritize custom component over text + if (helperComponent) { + return {helperComponent} + } + + // Fall back to text rendering + return {helperText} +} + +const HelperContainer = styled.View(({theme}) => ({ + marginTop: theme?.spacing?.tiny, +})) + +const HelperTextStyled = styled.Text(({theme}) => ({ + fontSize: theme?.fontSizes?.['2xs'], + color: theme?.colors?.gray, + marginTop: theme?.spacing?.tiny, +})) diff --git a/src/components/CodeInput/components/index.ts b/src/components/CodeInput/components/index.ts new file mode 100644 index 0000000..6026a2b --- /dev/null +++ b/src/components/CodeInput/components/index.ts @@ -0,0 +1,2 @@ +export {ErrorText} from './ErrorText' +export {HelperText} from './HelperText' diff --git a/src/theme/components/CodeInput.ts b/src/theme/components/CodeInput.ts index d53a7be..bf53643 100644 --- a/src/theme/components/CodeInput.ts +++ b/src/theme/components/CodeInput.ts @@ -79,6 +79,30 @@ export type CodeInputThemeProps = { * Disable input */ disabled: boolean + /** + * Style for outer container + */ + containerStyle?: StyleProp + /** + * Styling for the label + */ + labelStyle?: StyleProp + /** + * Style for cell in error state + */ + errorCellStyle?: StyleProp + /** + * Style for cell in success state + */ + successCellStyle?: StyleProp + /** + * Style for cell in disabled state + */ + disabledCellStyle?: StyleProp + /** + * Style for cell in active state (alias for focusCellStyle) + */ + activeCellStyle?: StyleProp } export const CodeInputTheme: CodeInputThemeProps = { @@ -134,4 +158,27 @@ export const CodeInputTheme: CodeInputThemeProps = { }, autoFocus: false, disabled: false, + containerStyle: undefined, + labelStyle: { + fontSize: 14, + color: base.colors.darkText, + marginBottom: 8, + }, + errorCellStyle: { + borderColor: base.colors.error, + borderWidth: 2, + }, + successCellStyle: { + borderColor: base.colors.success, + borderWidth: 2, + }, + disabledCellStyle: { + backgroundColor: '#f5f5f5', + borderColor: base.colors.primaryBorder, + opacity: 0.5, + }, + activeCellStyle: { + borderColor: base.colors.primary, + borderWidth: 2, + }, } From 9462fc666ce2fab8e1cacdfe9a95a15326e061a8 Mon Sep 17 00:00:00 2001 From: "hang.nguyen" Date: Thu, 23 Oct 2025 16:07:45 +0700 Subject: [PATCH 02/13] feat: update CodeInput readme --- src/components/CodeInput/CodeInput.tsx | 2 +- src/components/CodeInput/README.md | 276 +++++++++++++++++++++++-- 2 files changed, 257 insertions(+), 21 deletions(-) diff --git a/src/components/CodeInput/CodeInput.tsx b/src/components/CodeInput/CodeInput.tsx index ec9b932..ef42a23 100644 --- a/src/components/CodeInput/CodeInput.tsx +++ b/src/components/CodeInput/CodeInput.tsx @@ -460,7 +460,7 @@ export const CodeInput = forwardRef( {isRequire && *} ) : ( - label && ( + !!label && ( void` | `undefined` | Callback when code changes | -| `onSubmit` | `(code: string) => void` | `undefined` | Callback when code is complete | -| `onClear` | `() => void` | `undefined` | Callback when code is cleared | -| `cellStyle` | `StyleProp` | Theme | Style for individual cells (overrides theme) | -| `filledCellStyle` | `StyleProp` | Theme | Style for cells with values (overrides theme) | -| `focusCellStyle` | `StyleProp` | Theme | Style for focused cell (overrides theme) | -| `textStyle` | `StyleProp` | Theme | Style for cell text (overrides theme) | -| `focusTextStyle` | `StyleProp` | Theme | Style for focused cell text (overrides theme) | -| `secureTextEntry` | `boolean` | Theme | Enable secure input mode (overrides theme) | -| `keyboardType` | `KeyboardTypeOptions` | Theme | Keyboard type to show (overrides theme) | -| `withCursor` | `boolean` | Theme | Show cursor in focused cell (overrides theme) | -| `placeholder` | `string` | Theme | Placeholder text for empty cells | -| `placeholderTextColor` | `string` | Theme | Color for placeholder text (overrides theme) | -| `placeholderAsDot` | `boolean` | Theme | Render placeholder as dot (overrides theme) | -| `autoFocus` | `boolean` | Theme | Auto focus on mount (overrides theme) | -| `disabled` | `boolean` | Theme | Disable input (overrides theme) | +#### Core Props + +| Prop | Type | Default | Description | +| -------------- | ------------------------ | ----------- | ------------------------------ | +| `length` | `number` | Theme | Number of input cells | +| `value` | `string` | `''` | Current input value | +| `onChangeText` | `(code: string) => void` | `undefined` | Callback when code changes | +| `onSubmit` | `(code: string) => void` | `undefined` | Callback when code is complete | +| `onClear` | `() => void` | `undefined` | Callback when code is cleared | +| `autoFocus` | `boolean` | Theme | Auto focus on mount | +| `disabled` | `boolean` | Theme | Disable input | +| `keyboardType` | `KeyboardTypeOptions` | Theme | Keyboard type to show | +| `testID` | `string` | `undefined` | Test ID for the component | + +#### Styling Props + +| Prop | Type | Default | Description | +| ----------------------- | ---------------------- | ----------- | ------------------------------------- | +| `containerStyle` | `StyleProp` | `undefined` | Style for outer container | +| `cellContainerStyle` | `StyleProp` | `undefined` | Style for container holding all cells | +| `cellStyle` | `StyleProp` | Theme | Style for individual cells | +| `cellWrapperStyle` | `StyleProp` | `undefined` | Style for wrapper around each cell | +| `filledCellStyle` | `StyleProp` | Theme | Style for cells with values | +| `focusCellStyle` | `StyleProp` | Theme | Style for focused cell | +| `focusCellWrapperStyle` | `StyleProp` | `undefined` | Style for wrapper around focused cell | +| `activeCellStyle` | `StyleProp` | `undefined` | Style for cell in active state | +| `errorCellStyle` | `StyleProp` | `undefined` | Style for cell in error state | +| `successCellStyle` | `StyleProp` | `undefined` | Style for cell in success state | +| `disabledCellStyle` | `StyleProp` | `undefined` | Style for cell in disabled state | + +#### Text & Secure Entry Props + +| Prop | Type | Default | Description | +| ---------------------- | ---------------------- | ----------- | -------------------------------- | +| `textStyle` | `StyleProp` | Theme | Style for cell text | +| `focusTextStyle` | `StyleProp` | Theme | Style for focused cell text | +| `secureTextEntry` | `boolean` | Theme | Enable secure input mode | +| `secureViewStyle` | `StyleProp` | `undefined` | Style for secure text entry dots | +| `placeholder` | `string` | Theme | Placeholder text for empty cells | +| `placeholderTextColor` | `string` | Theme | Color for placeholder text | +| `placeholderAsDot` | `boolean` | Theme | Render placeholder as dot | +| `placeholderDotStyle` | `StyleProp` | `undefined` | Style for placeholder dot | + +#### Cursor Props + +| Prop | Type | Default | Description | +| -------------- | ----------------- | ----------- | --------------------------- | +| `withCursor` | `boolean` | Theme | Show cursor in focused cell | +| `customCursor` | `() => ReactNode` | `undefined` | Custom cursor component | + +#### Label Props + +| Prop | Type | Default | Description | +| ---------------- | ---------------------- | ----------- | ----------------------------------------- | +| `label` | `string` | `undefined` | Label text displayed above the code input | +| `labelComponent` | `ReactNode` | `undefined` | Custom label component to replace default | +| `labelStyle` | `StyleProp` | `undefined` | Styling for the label | +| `labelProps` | `TextProps` | `undefined` | Props passed to the label Text component | +| `isRequire` | `boolean` | `undefined` | Show asterisk beside label for required | + +#### Helper & Error Text Props + +| Prop | Type | Default | Description | +| ----------------- | ----------- | ----------- | ------------------------------------------ | +| `helperText` | `string` | `undefined` | Helper text displayed below the code input | +| `helperComponent` | `ReactNode` | `undefined` | Custom helper component to replace default | +| `helperTextProps` | `TextProps` | `undefined` | Props passed to the helper text component | +| `errorText` | `string` | `undefined` | Error text displayed below the code input | +| `errorProps` | `TextProps` | `undefined` | Props passed to the error text component | + +#### State Props + +| Prop | Type | Default | Description | +| --------- | --------- | ----------- | ---------------------------- | +| `error` | `boolean` | `undefined` | Enable error state styling | +| `success` | `boolean` | `undefined` | Enable success state styling | + +#### Component Props + +| Prop | Type | Default | Description | +| ---------------- | ----------- | ----------- | ---------------------------------------------- | +| `leftComponent` | `ReactNode` | `undefined` | React node rendered on the left side of input | +| `rightComponent` | `ReactNode` | `undefined` | React node rendered on the right side of input | ## Usage Patterns @@ -284,6 +347,132 @@ const OTPInput = () => { } ``` +### Error State Handling + +```tsx +const CodeInputWithError = () => { + const [code, setCode] = useState('') + const [error, setError] = useState(false) + const [errorMessage, setErrorMessage] = useState('') + + const handleCodeChange = (newCode: string) => { + setCode(newCode) + // Clear error when user types + if (error) { + setError(false) + setErrorMessage('') + } + } + + const handleCodeSubmit = async (finalCode: string) => { + try { + await verifyCode(finalCode) + // Success handling + navigation.navigate('Success') + } catch (err) { + setError(true) + setErrorMessage('Invalid code. Please try again.') + setCode('') + } + } + + return ( + + ) +} + +const styles = StyleSheet.create({ + errorCell: { + borderColor: '#FF3B30', + borderWidth: 2, + backgroundColor: '#FFF5F5', + }, +}) +``` + +### Success State Handling + +```tsx +const CodeInputWithSuccess = () => { + const [code, setCode] = useState('') + const [success, setSuccess] = useState(false) + + const handleCodeSubmit = async (finalCode: string) => { + const isValid = await verifyCode(finalCode) + + if (isValid) { + setSuccess(true) + setTimeout(() => { + navigation.navigate('NextScreen') + }, 1000) + } + } + + return ( + + ) +} + +const styles = StyleSheet.create({ + successCell: { + borderColor: '#34C759', + borderWidth: 2, + backgroundColor: '#F0FFF4', + }, +}) +``` + +### With Left & Right Components + +```tsx +import {View, TouchableOpacity} from 'react-native' +import Icon from 'react-native-vector-icons/Ionicons' + +const CodeInputWithComponents = () => { + const [code, setCode] = useState('') + + const handleClear = () => { + setCode('') + } + + return ( + } + rightComponent={ + code.length > 0 && ( + + + + ) + } + /> + ) +} +``` + ### Bank PIN with Secure Display ```tsx @@ -371,9 +560,31 @@ const customTheme = extendTheme({ fontSize: 18, fontWeight: '600', }, + labelStyle: { + // Label text styling + fontSize: 14, + fontWeight: '500', + color: '#333', + }, + errorCellStyle: { + // Style for cells in error state + borderColor: '#FF3B30', + borderWidth: 2, + }, + successCellStyle: { + // Style for cells in success state + borderColor: '#34C759', + borderWidth: 2, + }, + disabledCellStyle: { + // Style for cells in disabled state + opacity: 0.5, + backgroundColor: '#F5F5F5', + }, secureTextEntry: false, // Secure input mode keyboardType: 'number-pad', // Keyboard type autoFocus: false, // Auto focus behavior + disabled: false, // Disabled state placeholderTextColor: '#999999', // Placeholder color }, }, @@ -409,15 +620,40 @@ CodeInputTheme: { borderRadius: 8, // metrics.borderRadius backgroundColor: '#FFFFFF', // base.colors.white }, + filledCellStyle: { + borderColor: '#007AFF', // Highlight when filled + }, + focusCellStyle: { + borderColor: '#007AFF', // Highlight when focused + borderWidth: 2, + }, textStyle: { fontSize: 18, fontWeight: '600', color: '#000000', // base.colors.black }, + labelStyle: { + fontSize: 14, + fontWeight: '500', + color: '#333333', // Label text color + }, + errorCellStyle: { + borderColor: '#FF3B30', // Error state + borderWidth: 2, + }, + successCellStyle: { + borderColor: '#34C759', // Success state + borderWidth: 2, + }, + disabledCellStyle: { + opacity: 0.5, // Disabled state + backgroundColor: '#F5F5F5', + }, secureTextEntry: false, keyboardType: 'number-pad', autoFocus: false, disabled: false, + placeholderTextColor: '#999999', } ``` From 9edc176da05189be8dc48d6532fb55ad2b737fe5 Mon Sep 17 00:00:00 2001 From: "hang.nguyen" Date: Thu, 23 Oct 2025 15:22:03 +0700 Subject: [PATCH 03/13] feat: update example layout components --- example/app/_layout.tsx | 23 +- example/app/code-input.tsx | 8 + example/app/index.tsx | 320 +++++++++++++-- .../CodeInputDemo/AdditionalFeatures.tsx | 32 ++ .../CodeInputDemo/AdvancedCellStyling.tsx | 181 +++++++++ .../CodeInputDemo/BasicExamples.tsx | 32 ++ .../CodeInputDemo/DisabledState.tsx | 23 ++ .../CodeInputDemo/IconsAndComponents.tsx | 45 ++ .../CodeInputDemo/InteractivePlayground.tsx | 94 +++++ .../CodeInputDemo/SecurityAndStyling.tsx | 53 +++ .../CodeInputDemo/ValidationStates.tsx | 34 ++ example/components/CodeInputDemo/index.tsx | 32 ++ example/components/CodeInputDemo/styles.ts | 384 ++++++++++++++++++ example/components/CodeInputDemo/utils.tsx | 38 ++ example/constants/components.ts | 139 +++++++ example/theme/demoColors.ts | 132 ++++++ example/theme/demoMetrics.ts | 210 ++++++++++ 17 files changed, 1734 insertions(+), 46 deletions(-) create mode 100644 example/app/code-input.tsx create mode 100644 example/components/CodeInputDemo/AdditionalFeatures.tsx create mode 100644 example/components/CodeInputDemo/AdvancedCellStyling.tsx create mode 100644 example/components/CodeInputDemo/BasicExamples.tsx create mode 100644 example/components/CodeInputDemo/DisabledState.tsx create mode 100644 example/components/CodeInputDemo/IconsAndComponents.tsx create mode 100644 example/components/CodeInputDemo/InteractivePlayground.tsx create mode 100644 example/components/CodeInputDemo/SecurityAndStyling.tsx create mode 100644 example/components/CodeInputDemo/ValidationStates.tsx create mode 100644 example/components/CodeInputDemo/index.tsx create mode 100644 example/components/CodeInputDemo/styles.ts create mode 100644 example/components/CodeInputDemo/utils.tsx create mode 100644 example/constants/components.ts create mode 100644 example/theme/demoColors.ts create mode 100644 example/theme/demoMetrics.ts diff --git a/example/app/_layout.tsx b/example/app/_layout.tsx index a463057..6531587 100644 --- a/example/app/_layout.tsx +++ b/example/app/_layout.tsx @@ -10,7 +10,28 @@ export default function RootLayout() { return ( - + + diff --git a/example/app/code-input.tsx b/example/app/code-input.tsx new file mode 100644 index 0000000..2832ce8 --- /dev/null +++ b/example/app/code-input.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import { CodeInputDemo } from '../components/CodeInputDemo' + +const CodeInputDemoScreen = () => { + return +} + +export default CodeInputDemoScreen diff --git a/example/app/index.tsx b/example/app/index.tsx index e238590..da3bbf6 100644 --- a/example/app/index.tsx +++ b/example/app/index.tsx @@ -1,50 +1,119 @@ -import dayjs from 'dayjs' -import React, { useRef } from 'react' -import { Alert, StyleSheet, Text, View } from 'react-native' -import { Button, CountDown, CountDownRef } from 'rn-base-component' +import React from 'react' +import { ScrollView, StyleSheet, Text, View, Pressable, SafeAreaView } from 'react-native' +import { useRouter } from 'expo-router' +import { COMPONENTS, ComponentItem } from '../constants/components' +import { demoColors } from '../theme/demoColors' +import { demoMetrics, componentMetrics } from '../theme/demoMetrics' const HomeScreen = () => { - const countdownRef = useRef(null) - const handleRestart = () => { - countdownRef.current?.restart() - } - const handleStop = () => { - countdownRef.current?.stopCountDown() - } - const handleGetCurrentTime = () => { - console.log(countdownRef.current?.getCurrentTime()) - Alert.alert('Current Time', countdownRef.current?.getCurrentTime().toString() || '0') + const router = useRouter() + + const handleComponentPress = (item: ComponentItem) => { + if (item.route && item.status === 'Complete') { + router.push(item.route as any) + } } - const handleResume = () => { - countdownRef.current?.resumeCountDown() + + const renderComponentCard = (item: ComponentItem) => { + const isAvailable = item.status === 'Complete' + + return ( + [ + styles.card, + !isAvailable && styles.cardDisabled, + pressed && isAvailable && styles.cardPressed, + ]} + onPress={() => handleComponentPress(item)} + disabled={!isAvailable} + > + + + {item.icon} + + + + {item.status} + + + + + + {item.name} + {item.description} + + + {item.category} + + + + {isAvailable && ( + + View Demo → + + )} + + ) } - const handleGetCountDownStatus = () => { - Alert.alert('Countdown Status', countdownRef.current?.getCountDownStatus() || '0') + + const groupedComponents = { + Input: COMPONENTS.filter(c => c.category === 'Input'), + Display: COMPONENTS.filter(c => c.category === 'Display'), + Navigation: COMPONENTS.filter(c => c.category === 'Navigation'), + Feedback: COMPONENTS.filter(c => c.category === 'Feedback'), } + return ( - - index - - - - - - - - alert('Happy New Year!')} - textStyle={{color:'red', fontSize:35, fontFamily:'Arial'}} - unitTextStyle={{color:'blue', fontSize:30, fontFamily:'Arial'}} - /> - + + + + RN Base Component + + Explore comprehensive component library with interactive demos + + + + + + {COMPONENTS.length} + Components + + + + + {COMPONENTS.filter(c => c.status === 'Complete').length} + + Ready + + + + 4 + Categories + + + + {Object.entries(groupedComponents).map( + ([category, components]) => + components.length > 0 && ( + + {category} Components + {components.map(renderComponentCard)} + + ) + )} + + + Built with React Native & Expo + v1.0.0 + + + ) } @@ -53,12 +122,173 @@ export default HomeScreen const styles = StyleSheet.create({ container: { flex: 1, - justifyContent: 'center', + backgroundColor: demoColors.background, + }, + contentContainer: { + padding: componentMetrics.containerPadding, + paddingBottom: componentMetrics.containerPaddingBottom, + }, + header: { + marginBottom: demoMetrics.margin.xlarge, + alignItems: 'center', + }, + title: { + fontSize: demoMetrics.fontSize.hero, + fontWeight: 'bold', + color: demoColors.textPrimary, + marginBottom: demoMetrics.spacing.small, + textAlign: 'center', + }, + subtitle: { + fontSize: demoMetrics.fontSize.normal, + color: demoColors.textSecondary, + textAlign: 'center', + lineHeight: demoMetrics.lineHeight.normal, + paddingHorizontal: demoMetrics.padding.large, + }, + statsContainer: { + flexDirection: 'row', + backgroundColor: demoColors.surface, + borderRadius: demoMetrics.borderRadius.xxlarge, + padding: demoMetrics.padding.large, + marginBottom: demoMetrics.margin.xlarge, + shadowColor: demoColors.shadow, + ...demoMetrics.shadow.normal, + }, + statItem: { + flex: 1, alignItems: 'center', }, - buttonContainer: { - gap: 10, + statNumber: { + fontSize: demoMetrics.fontSize.heading, + fontWeight: 'bold', + color: demoColors.primary, + marginBottom: demoMetrics.spacing.tiny, + }, + statLabel: { + fontSize: demoMetrics.fontSize.caption, + color: demoColors.textSecondary, + fontWeight: '500', + }, + statDivider: { + width: componentMetrics.statDividerWidth, + backgroundColor: demoColors.border, + marginHorizontal: componentMetrics.statDividerMargin, + }, + categorySection: { + marginBottom: demoMetrics.margin.xlarge, + }, + categoryTitle: { + fontSize: demoMetrics.fontSize.large, + fontWeight: '700', + color: demoColors.textDark, + marginBottom: demoMetrics.spacing.normal, + paddingLeft: demoMetrics.padding.tiny, + }, + cardsGrid: { + gap: componentMetrics.cardGap, + }, + card: { + backgroundColor: demoColors.surface, + borderRadius: componentMetrics.cardBorderRadius, + padding: componentMetrics.cardPadding, + shadowColor: demoColors.shadow, + ...demoMetrics.shadow.normal, + borderWidth: demoMetrics.borderWidth.normal, + borderColor: demoColors.border, + }, + cardDisabled: { + opacity: demoMetrics.opacity.disabled, + }, + cardPressed: { + transform: [{ scale: 0.98 }], + shadowOpacity: 0.12, + }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: demoMetrics.margin.medium, + }, + iconContainer: { + width: demoMetrics.iconSize.huge, + height: demoMetrics.iconSize.huge, + backgroundColor: demoColors.primaryBackgroundLight, + borderRadius: demoMetrics.borderRadius.xlarge, justifyContent: 'center', alignItems: 'center', }, -}) \ No newline at end of file + icon: { + fontSize: demoMetrics.fontSize.heading, + }, + statusBadge: { + paddingHorizontal: demoMetrics.padding.medium, + paddingVertical: demoMetrics.padding.tiny, + borderRadius: demoMetrics.borderRadius.medium, + backgroundColor: demoColors.badgeBackground, + }, + statusText: { + fontSize: demoMetrics.fontSize.tiny, + fontWeight: '600', + }, + statusComplete: { + color: demoColors.success, + }, + statusComingSoon: { + color: demoColors.textTertiary, + }, + cardContent: { + marginBottom: demoMetrics.margin.normal, + }, + componentName: { + fontSize: demoMetrics.fontSize.large, + fontWeight: '700', + color: demoColors.textDark, + marginBottom: demoMetrics.spacing.small, + }, + componentDescription: { + fontSize: demoMetrics.fontSize.body, + color: demoColors.textSecondary, + lineHeight: demoMetrics.lineHeight.tight, + marginBottom: demoMetrics.margin.medium, + }, + categoryBadge: { + alignSelf: 'flex-start', + paddingHorizontal: demoMetrics.padding.medium, + paddingVertical: demoMetrics.padding.tiny, + backgroundColor: demoColors.primaryBackground, + borderRadius: demoMetrics.borderRadius.small, + }, + categoryText: { + fontSize: demoMetrics.fontSize.small, + fontWeight: '600', + color: demoColors.primary, + }, + cardFooter: { + paddingTop: demoMetrics.padding.normal, + borderTopWidth: demoMetrics.borderWidth.normal, + borderTopColor: demoColors.borderLight, + }, + viewDemoText: { + fontSize: demoMetrics.fontSize.body, + fontWeight: '600', + color: demoColors.primary, + textAlign: 'center', + }, + footer: { + marginTop: demoMetrics.spacing.xxxlarge, + paddingTop: demoMetrics.padding.large, + borderTopWidth: demoMetrics.borderWidth.normal, + borderTopColor: demoColors.border, + alignItems: 'center', + }, + footerText: { + fontSize: demoMetrics.fontSize.body, + color: demoColors.textSecondary, + marginBottom: demoMetrics.spacing.tiny, + }, + footerSubtext: { + fontSize: demoMetrics.fontSize.small, + color: demoColors.textTertiary, + }, +}) diff --git a/example/components/CodeInputDemo/AdditionalFeatures.tsx b/example/components/CodeInputDemo/AdditionalFeatures.tsx new file mode 100644 index 0000000..6206796 --- /dev/null +++ b/example/components/CodeInputDemo/AdditionalFeatures.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { CodeInput } from 'rn-base-component' +import { demoStyles } from './styles' + +export const AdditionalFeatures = () => { + return ( + + 💭 Additional Features + + + With Cursor Animation + + + + + Auto Focus Enabled + + + + ) +} diff --git a/example/components/CodeInputDemo/AdvancedCellStyling.tsx b/example/components/CodeInputDemo/AdvancedCellStyling.tsx new file mode 100644 index 0000000..3822258 --- /dev/null +++ b/example/components/CodeInputDemo/AdvancedCellStyling.tsx @@ -0,0 +1,181 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { CodeInput } from 'rn-base-component' +import { demoStyles, customCellStyles } from './styles' + +export const AdvancedCellStyling = () => { + return ( + + 🎨 Advanced Cell Styling + + Explore different visual styles for code input cells + + + + Circular Cells + + + + + Square/Sharp Cells + + + + + Gradient Background + + + + + Large Size Variant + + + + + Small/Compact Size + + + + + Elevated with Shadow + + + + + Outlined Style + + + + + Filled Style + + + + + Minimalist Style + + + + + Bold/Heavy Style + + + + + Custom Error State + + + + + Custom Success State + + + + + Light Subtle Style (Figma Design) + + + + ) +} diff --git a/example/components/CodeInputDemo/BasicExamples.tsx b/example/components/CodeInputDemo/BasicExamples.tsx new file mode 100644 index 0000000..f35e415 --- /dev/null +++ b/example/components/CodeInputDemo/BasicExamples.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { CodeInput } from 'rn-base-component' +import { demoStyles } from './styles' + +export const BasicExamples = () => { + return ( + + 📝 Basic Examples + + + Default with Label + + + + + Required Field + + + + + Different Length (5 digits) + + + + ) +} diff --git a/example/components/CodeInputDemo/DisabledState.tsx b/example/components/CodeInputDemo/DisabledState.tsx new file mode 100644 index 0000000..0d1d9f1 --- /dev/null +++ b/example/components/CodeInputDemo/DisabledState.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { CodeInput } from 'rn-base-component' +import { demoStyles } from './styles' + +export const DisabledState = () => { + return ( + + 🚫 Disabled State + + + Pre-filled Disabled Input + + + + ) +} diff --git a/example/components/CodeInputDemo/IconsAndComponents.tsx b/example/components/CodeInputDemo/IconsAndComponents.tsx new file mode 100644 index 0000000..0468188 --- /dev/null +++ b/example/components/CodeInputDemo/IconsAndComponents.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { CodeInput } from 'rn-base-component' +import { demoStyles } from './styles' +import { LockIcon, CheckIcon, ClearButton } from './utils' + +export const IconsAndComponents = () => { + return ( + + 🎨 Icons & Components + + + Left Icon Component + } + secureTextEntry + /> + + + + Right Icon Component + } + success + /> + + + + Both Left & Right Components + } + rightComponent={} + helperText="Tap X to clear" + /> + + + ) +} diff --git a/example/components/CodeInputDemo/InteractivePlayground.tsx b/example/components/CodeInputDemo/InteractivePlayground.tsx new file mode 100644 index 0000000..6f97200 --- /dev/null +++ b/example/components/CodeInputDemo/InteractivePlayground.tsx @@ -0,0 +1,94 @@ +import React, { useRef, useState } from 'react' +import { Alert, Text, View } from 'react-native' +import { Button, CodeInput, CodeInputRef } from 'rn-base-component' +import { demoStyles } from './styles' + +export const InteractivePlayground = () => { + const playgroundRef = useRef(null) + const [playgroundValue, setPlaygroundValue] = useState('') + const [hasError, setHasError] = useState(false) + const [hasSuccess, setHasSuccess] = useState(false) + const [isDisabled, setIsDisabled] = useState(false) + const [isSecure, setIsSecure] = useState(false) + + const handlePlaygroundChange = (code: string) => { + setPlaygroundValue(code) + } + + const handlePlaygroundSubmit = (code: string) => { + Alert.alert('Code Submitted', `You entered: ${code}`) + setHasSuccess(true) + setHasError(false) + } + + const toggleError = () => { + setHasError(!hasError) + setHasSuccess(false) + } + + const toggleSuccess = () => { + setHasSuccess(!hasSuccess) + setHasError(false) + } + + const toggleDisabled = () => setIsDisabled(!isDisabled) + const toggleSecure = () => setIsSecure(!isSecure) + + const clearInput = () => { + playgroundRef.current?.clear() + setPlaygroundValue('') + setHasError(false) + setHasSuccess(false) + } + + const getCurrentValue = () => { + const value = playgroundRef.current?.getValue() + Alert.alert('Current Value', value || '(empty)') + } + + return ( + + 🎮 Interactive Playground + + Test different states and configurations + + + + + + + + + + + + + + ) +} diff --git a/example/components/CodeInputDemo/SecurityAndStyling.tsx b/example/components/CodeInputDemo/SecurityAndStyling.tsx new file mode 100644 index 0000000..0d958e1 --- /dev/null +++ b/example/components/CodeInputDemo/SecurityAndStyling.tsx @@ -0,0 +1,53 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { CodeInput } from 'rn-base-component' +import { demoStyles, customCellStyles } from './styles' + +export const SecurityAndStyling = () => { + return ( + + 🔐 Security & Styling + + + Secure Text Entry + + + + + Placeholder as Dots + + + + + Custom Placeholder + + + + + Custom Cell Styling + + + + ) +} diff --git a/example/components/CodeInputDemo/ValidationStates.tsx b/example/components/CodeInputDemo/ValidationStates.tsx new file mode 100644 index 0000000..d4e9182 --- /dev/null +++ b/example/components/CodeInputDemo/ValidationStates.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { CodeInput } from 'rn-base-component' +import { demoStyles } from './styles' + +export const ValidationStates = () => { + return ( + + ✓ Validation States + + + Success State + + + + + Error State + + + + ) +} diff --git a/example/components/CodeInputDemo/index.tsx b/example/components/CodeInputDemo/index.tsx new file mode 100644 index 0000000..28fb956 --- /dev/null +++ b/example/components/CodeInputDemo/index.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { ScrollView, Text, View } from 'react-native' +import { demoStyles } from './styles' +import { InteractivePlayground } from './InteractivePlayground' +import { BasicExamples } from './BasicExamples' +import { ValidationStates } from './ValidationStates' +import { IconsAndComponents } from './IconsAndComponents' +import { SecurityAndStyling } from './SecurityAndStyling' +import { AdvancedCellStyling } from './AdvancedCellStyling' +import { DisabledState } from './DisabledState' +import { AdditionalFeatures } from './AdditionalFeatures' + +export const CodeInputDemo = () => { + return ( + + CodeInput Component Demo + + + + + + + + + + + + 🎯 Explore all CodeInput features above! + + + ) +} diff --git a/example/components/CodeInputDemo/styles.ts b/example/components/CodeInputDemo/styles.ts new file mode 100644 index 0000000..c849515 --- /dev/null +++ b/example/components/CodeInputDemo/styles.ts @@ -0,0 +1,384 @@ +import { StyleSheet } from 'react-native' +import { demoColors } from '../../theme/demoColors' +import { demoMetrics, componentMetrics } from '../../theme/demoMetrics' + +export const demoStyles = StyleSheet.create({ + // Container styles + container: { + flex: 1, + backgroundColor: demoColors.backgroundLight, + }, + contentContainer: { + padding: componentMetrics.containerPadding, + paddingBottom: componentMetrics.containerPaddingBottom, + }, + + // Header styles + mainTitle: { + fontSize: demoMetrics.fontSize.heading, + fontWeight: 'bold', + color: demoColors.textPrimary, + marginBottom: demoMetrics.margin.medium, + textAlign: 'center', + }, + + // Section styles + section: { + backgroundColor: demoColors.surface, + borderRadius: componentMetrics.sectionBorderRadius, + padding: componentMetrics.sectionPadding, + marginVertical: componentMetrics.sectionMargin, + shadowColor: demoColors.shadow, + ...demoMetrics.shadow.normal, + }, + sectionTitle: { + fontSize: demoMetrics.fontSize.large, + fontWeight: '700', + color: demoColors.textDark, + marginBottom: demoMetrics.margin.small, + }, + sectionDescription: { + fontSize: demoMetrics.fontSize.body, + color: demoColors.textSecondary, + marginBottom: demoMetrics.spacing.large, + }, + + // Example styles + example: { + marginVertical: demoMetrics.margin.normal, + paddingVertical: demoMetrics.padding.medium, + borderBottomWidth: demoMetrics.borderWidth.normal, + borderBottomColor: demoColors.borderLight, + }, + exampleTitle: { + fontSize: demoMetrics.fontSize.subheading, + fontWeight: '600', + color: demoColors.textSecondary, + marginBottom: demoMetrics.margin.medium, + }, + + // Control buttons + controlButtons: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: demoMetrics.gap.small, + marginTop: demoMetrics.spacing.large, + justifyContent: 'center', + }, + smallButton: { + paddingHorizontal: componentMetrics.buttonPaddingHorizontal, + paddingVertical: componentMetrics.buttonPaddingVertical, + minWidth: componentMetrics.buttonMinWidth, + }, + + // Footer styles + footer: { + marginTop: demoMetrics.margin.xlarge, + padding: demoMetrics.padding.large, + backgroundColor: demoColors.surface, + borderRadius: demoMetrics.borderRadius.large, + alignItems: 'center', + }, + footerText: { + fontSize: demoMetrics.fontSize.normal, + color: demoColors.textSecondary, + textAlign: 'center', + }, +}) + +// Custom cell styling examples +export const customCellStyles = StyleSheet.create({ + // Basic custom example + customCell: { + borderRadius: demoMetrics.borderRadius.large, + borderWidth: demoMetrics.borderWidth.thick, + borderColor: demoColors.blue, + backgroundColor: demoColors.blueLight, + }, + customFocusCell: { + borderColor: demoColors.blueDark, + backgroundColor: demoColors.blueLighter, + transform: [{ scale: demoMetrics.scale.normal }], + }, + customText: { + fontSize: demoMetrics.fontSize.xxlarge, + fontWeight: 'bold', + color: demoColors.textDark, + }, + + // Circular cells + circularCell: { + width: demoMetrics.cellSize.large, + height: demoMetrics.cellSize.large, + borderRadius: demoMetrics.borderRadius.round, + borderWidth: demoMetrics.borderWidth.thick, + borderColor: demoColors.purple, + backgroundColor: demoColors.purpleLight, + }, + circularFocusCell: { + borderColor: demoColors.purpleDark, + backgroundColor: demoColors.purpleLighter, + transform: [{ scale: demoMetrics.scale.medium }], + }, + circularFilledCell: { + borderColor: demoColors.purple, + backgroundColor: demoColors.purpleFilled, + }, + circularText: { + fontSize: demoMetrics.fontSize.xlarge, + fontWeight: '600', + color: demoColors.purpleDark, + }, + + // Square cells + squareCell: { + width: demoMetrics.cellSize.large, + height: demoMetrics.cellSize.large, + borderRadius: 0, + borderWidth: demoMetrics.borderWidth.thick, + borderColor: demoColors.gray, + backgroundColor: demoColors.surface, + }, + squareFocusCell: { + borderColor: demoColors.grayDark, + backgroundColor: demoColors.grayLight, + borderWidth: demoMetrics.borderWidth.xthick, + }, + squareText: { + fontSize: demoMetrics.fontSize.xlarge, + fontWeight: '700', + color: demoColors.textDark, + }, + + // Gradient cells + gradientCell: { + width: demoMetrics.cellSize.medium, + height: demoMetrics.cellSize.medium, + borderRadius: demoMetrics.borderRadius.normal, + borderWidth: demoMetrics.borderWidth.thick, + borderColor: demoColors.orange, + backgroundColor: demoColors.orangeLight, + }, + gradientFocusCell: { + borderColor: demoColors.orangeDark, + backgroundColor: demoColors.yellowLight, + transform: [{ scale: demoMetrics.scale.normal }], + }, + gradientFilledCell: { + backgroundColor: demoColors.yellow, + borderColor: demoColors.orange, + }, + gradientText: { + fontSize: demoMetrics.fontSize.large, + fontWeight: 'bold', + color: demoColors.orangeDark, + }, + + // Large cells + largeCell: { + width: demoMetrics.cellSize.xhuge, + height: demoMetrics.cellSize.xhuge, + borderRadius: demoMetrics.borderRadius.large, + borderWidth: demoMetrics.borderWidth.thick, + borderColor: demoColors.blue, + backgroundColor: demoColors.surface, + }, + largeFocusCell: { + borderColor: demoColors.blueDark, + backgroundColor: demoColors.blueLight, + borderWidth: demoMetrics.borderWidth.xthick, + }, + largeText: { + fontSize: demoMetrics.fontSize.heading, + fontWeight: 'bold', + color: demoColors.textDark, + }, + + // Small cells + smallCell: { + width: demoMetrics.cellSize.small, + height: demoMetrics.cellSize.small, + borderRadius: demoMetrics.borderRadius.small, + borderWidth: demoMetrics.borderWidth.medium, + borderColor: demoColors.teal, + backgroundColor: demoColors.surface, + }, + smallFocusCell: { + borderColor: demoColors.tealLight, + backgroundColor: demoColors.tealLighter, + borderWidth: demoMetrics.borderWidth.thick, + }, + smallText: { + fontSize: demoMetrics.fontSize.normal, + fontWeight: '600', + color: demoColors.teal, + }, + + // Shadow cells + shadowCell: { + width: demoMetrics.cellSize.xlarge, + height: demoMetrics.cellSize.xlarge, + borderRadius: demoMetrics.borderRadius.normal, + borderWidth: demoMetrics.borderWidth.normal, + borderColor: demoColors.grayBorder, + backgroundColor: demoColors.surface, + shadowColor: demoColors.shadow, + ...demoMetrics.shadow.medium, + }, + shadowFocusCell: { + borderColor: demoColors.blue, + backgroundColor: demoColors.surface, + shadowColor: demoColors.shadowBlue, + ...demoMetrics.shadow.large, + }, + shadowText: { + fontSize: demoMetrics.fontSize.large, + fontWeight: '600', + color: demoColors.textSecondary, + }, + + // Outlined cells + outlinedCell: { + width: demoMetrics.cellSize.large, + height: demoMetrics.cellSize.large, + borderRadius: demoMetrics.borderRadius.medium, + borderWidth: demoMetrics.borderWidth.xthick, + borderColor: demoColors.green, + backgroundColor: demoColors.transparent, + }, + outlinedFocusCell: { + borderColor: demoColors.greenDark, + borderWidth: demoMetrics.borderWidth.xxthick, + transform: [{ scale: demoMetrics.scale.normal }], + }, + outlinedFilledCell: { + borderColor: demoColors.green, + backgroundColor: demoColors.greenLight, + }, + outlinedText: { + fontSize: demoMetrics.fontSize.xlarge, + fontWeight: 'bold', + color: demoColors.green, + }, + + // Filled cells + filledCell: { + width: demoMetrics.cellSize.medium, + height: demoMetrics.cellSize.medium, + borderRadius: demoMetrics.borderRadius.normal, + borderWidth: demoMetrics.borderWidth.normal, + borderColor: demoColors.blueFilled, + backgroundColor: demoColors.blueLighter, + }, + filledFocusCell: { + borderColor: demoColors.blue, + backgroundColor: demoColors.blueActive, + borderWidth: demoMetrics.borderWidth.thick, + }, + filledFilledCell: { + backgroundColor: demoColors.blueFilled, + borderColor: demoColors.blue, + }, + filledText: { + fontSize: demoMetrics.fontSize.large, + fontWeight: '600', + color: demoColors.blueText, + }, + + // Minimalist cells + minimalistCell: { + width: demoMetrics.cellSize.normal, + height: demoMetrics.cellSize.normal, + borderRadius: demoMetrics.borderRadius.medium, + borderWidth: demoMetrics.borderWidth.normal, + borderColor: demoColors.grayPlaceholder, + backgroundColor: demoColors.surface, + }, + minimalistFocusCell: { + borderColor: demoColors.grayBorder, + backgroundColor: demoColors.surfaceHover, + }, + minimalistText: { + fontSize: demoMetrics.fontSize.medium, + fontWeight: '400', + color: demoColors.grayText, + }, + + // Bold cells + boldCell: { + width: demoMetrics.cellSize.xxlarge, + height: demoMetrics.cellSize.xxlarge, + borderRadius: demoMetrics.borderRadius.medium, + borderWidth: demoMetrics.borderWidth.xxthick, + borderColor: demoColors.red, + backgroundColor: demoColors.surface, + }, + boldFocusCell: { + borderColor: demoColors.error, + backgroundColor: demoColors.redLight, + borderWidth: demoMetrics.borderWidth.xxxthick, + transform: [{ scale: demoMetrics.scale.small }], + }, + boldFilledCell: { + borderColor: demoColors.red, + backgroundColor: demoColors.redFilled, + }, + boldText: { + fontSize: demoMetrics.fontSize.title, + fontWeight: '900', + color: demoColors.red, + }, + + // Validation cells + validationCell: { + width: demoMetrics.cellSize.large, + height: demoMetrics.cellSize.large, + borderRadius: demoMetrics.borderRadius.normal, + borderWidth: demoMetrics.borderWidth.thick, + borderColor: demoColors.grayBorder, + backgroundColor: demoColors.surface, + }, + customErrorCell: { + borderColor: demoColors.errorBorder, + borderWidth: demoMetrics.borderWidth.xthick, + backgroundColor: demoColors.errorLight, + transform: [{ scale: demoMetrics.scale.normal }], + }, + customSuccessCell: { + borderColor: demoColors.successBorder, + borderWidth: demoMetrics.borderWidth.xthick, + backgroundColor: demoColors.successLight, + transform: [{ scale: demoMetrics.scale.normal }], + }, + validationText: { + fontSize: demoMetrics.fontSize.large, + fontWeight: '600', + color: demoColors.textSecondary, + }, + + // Figma light cells + figmaLightCell: { + width: demoMetrics.cellSize.tiny, + height: demoMetrics.cellSize.tiny, + borderRadius: demoMetrics.borderRadius.tiny + 0.5, + borderWidth: demoMetrics.borderWidth.thin, + borderColor: demoColors.figmaLight, + backgroundColor: 'rgba(250, 250, 249, 0.7)', + justifyContent: 'center', + alignItems: 'center', + }, + figmaLightFocusCell: { + borderColor: demoColors.figmaLightBorder, + borderWidth: demoMetrics.borderWidth.normal, + backgroundColor: demoColors.figmaLight, + }, + figmaLightFilledCell: { + borderColor: demoColors.figmaLightFilled, + backgroundColor: demoColors.figmaLight, + }, + figmaLightText: { + fontSize: demoMetrics.fontSize.normal, + fontWeight: '500', + color: demoColors.figmaLightText, + }, +}) diff --git a/example/components/CodeInputDemo/utils.tsx b/example/components/CodeInputDemo/utils.tsx new file mode 100644 index 0000000..2c196f4 --- /dev/null +++ b/example/components/CodeInputDemo/utils.tsx @@ -0,0 +1,38 @@ +import React from 'react' +import { StyleSheet, Text, View } from 'react-native' +import { demoColors } from '../../theme/demoColors' +import { demoMetrics } from '../../theme/demoMetrics' + +// Icon Components for demos +export const LockIcon = () => ( + + 🔒 + +) + +export const CheckIcon = () => ( + + + +) + +export const ClearButton = () => ( + + + +) + +const styles = StyleSheet.create({ + iconContainer: { + width: demoMetrics.iconSize.xlarge, + height: demoMetrics.iconSize.xlarge, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: demoColors.iconBackground, + borderRadius: demoMetrics.borderRadius.medium, + marginHorizontal: demoMetrics.spacing.tiny, + }, + icon: { + fontSize: demoMetrics.fontSize.large, + }, +}) diff --git a/example/constants/components.ts b/example/constants/components.ts new file mode 100644 index 0000000..4fd9eff --- /dev/null +++ b/example/constants/components.ts @@ -0,0 +1,139 @@ +/** + * Component Library Demo Constants + * List of all components available in the demo app + */ + +export interface ComponentItem { + id: string + name: string + description: string + route?: string + icon: string + category: 'Input' | 'Display' | 'Navigation' | 'Feedback' + status: 'Complete' | 'Coming Soon' +} + +export const COMPONENTS: ComponentItem[] = [ + { + id: 'code-input', + name: 'CodeInput', + description: 'Verification code input with multiple styling options', + route: '/code-input', + icon: '🔢', + category: 'Input', + status: 'Complete', + }, + { + id: 'text-input', + name: 'TextInput', + description: 'Text input fields with flat and outlined variants', + icon: '📝', + category: 'Input', + status: 'Coming Soon', + }, + { + id: 'button', + name: 'Button', + description: 'Various button styles and variants', + icon: '🔘', + category: 'Input', + status: 'Coming Soon', + }, + { + id: 'checkbox', + name: 'Checkbox', + description: 'Checkboxes with customizable styles', + icon: '☑️', + category: 'Input', + status: 'Coming Soon', + }, + { + id: 'radio-button', + name: 'RadioButton', + description: 'Radio button selections', + icon: '🔘', + category: 'Input', + status: 'Coming Soon', + }, + { + id: 'slider', + name: 'Slider', + description: 'Range and fixed sliders', + icon: '🎚️', + category: 'Input', + status: 'Coming Soon', + }, + { + id: 'card', + name: 'Card', + description: 'Card container component', + icon: '🃏', + category: 'Display', + status: 'Coming Soon', + }, + { + id: 'text', + name: 'Text', + description: 'Themed text component', + icon: '📄', + category: 'Display', + status: 'Coming Soon', + }, + { + id: 'typography', + name: 'Typography', + description: 'Typography styles and variants', + icon: '✍️', + category: 'Display', + status: 'Coming Soon', + }, + { + id: 'icon', + name: 'Icon', + description: 'Icon component system', + icon: '⭐', + category: 'Display', + status: 'Coming Soon', + }, + { + id: 'accordion', + name: 'Accordion', + description: 'Expandable accordion panels', + icon: '📋', + category: 'Navigation', + status: 'Coming Soon', + }, + { + id: 'progress', + name: 'Progress', + description: 'Progress indicators and bars', + icon: '📊', + category: 'Feedback', + status: 'Coming Soon', + }, + { + id: 'countdown', + name: 'CountDown', + description: 'Countdown timer component', + icon: '⏱️', + category: 'Feedback', + status: 'Coming Soon', + }, +] + +// Helper functions +export const getComponentById = (id: string): ComponentItem | undefined => { + return COMPONENTS.find(component => component.id === id) +} + +export const getComponentsByCategory = (category: ComponentItem['category']): ComponentItem[] => { + return COMPONENTS.filter(component => component.category === category) +} + +export const getCompletedComponents = (): ComponentItem[] => { + return COMPONENTS.filter(component => component.status === 'Complete') +} + +export const getComponentsByStatus = (status: ComponentItem['status']): ComponentItem[] => { + return COMPONENTS.filter(component => component.status === status) +} diff --git a/example/theme/demoColors.ts b/example/theme/demoColors.ts new file mode 100644 index 0000000..a4a13a7 --- /dev/null +++ b/example/theme/demoColors.ts @@ -0,0 +1,132 @@ +/** + * Demo App Color System + * All colors used in the component demo application + */ + +export const demoColors = { + // Primary Colors + primary: '#3b82f6', + primaryLight: '#60a5fa', + primaryDark: '#2563eb', + primaryBackground: '#eff6ff', + primaryBackgroundLight: '#f0f9ff', + + // Text Colors + textPrimary: '#1a1a1a', + textSecondary: '#64748b', + textTertiary: '#94a3b8', + textDark: '#1e293b', + textLight: '#ffffff', + + // Background Colors + background: '#f5f7fa', + backgroundLight: '#f8f9fa', + surface: '#ffffff', + surfaceHover: '#fafafa', + + // Border Colors + border: '#e2e8f0', + borderLight: '#f1f5f9', + borderDark: '#cbd5e1', + + // Status Colors + success: '#10b981', + successLight: '#d5f4e6', + successBorder: '#27ae60', + error: '#e74c3c', + errorLight: '#fadbd8', + errorBorder: '#e74c3c', + warning: '#f39c12', + warningLight: '#fff8e1', + info: '#3498db', + + // Component Specific Colors + // Purple theme + purple: '#9b59b6', + purpleDark: '#8e44ad', + purpleLight: '#f8f3ff', + purpleLighter: '#f0e6ff', + purpleFilled: '#e8d5f5', + + // Gray theme + gray: '#95a5a6', + grayDark: '#7f8c8d', + grayLight: '#ecf0f1', + grayLighter: '#f5f5f5', + grayBorder: '#bdc3c7', + grayText: '#5d6d7e', + grayPlaceholder: '#d5d8dc', + + // Orange/Yellow theme + orange: '#f39c12', + orangeDark: '#e67e22', + orangeLight: '#fff8e1', + yellow: '#ffd54f', + yellowLight: '#ffe082', + + // Blue theme + blue: '#3498db', + blueDark: '#2980b9', + blueLight: '#ebf5fb', + blueLighter: '#d6eaf8', + blueFilled: '#5dade2', + blueActive: '#aed6f1', + blueText: '#21618c', + + // Teal/Green theme + teal: '#16a085', + tealLight: '#1abc9c', + tealLighter: '#e8f8f5', + green: '#27ae60', + greenDark: '#229954', + greenLight: '#eafaf1', + + // Red theme + red: '#c0392b', + redLight: '#fadbd8', + redFilled: '#f5b7b1', + + // Figma Light Style + figmaLight: '#FAFAF9', + figmaLightBorder: '#E5E5E5', + figmaLightFilled: '#E0E0E0', + figmaLightText: '#333333', + + // Shadow Colors + shadow: '#000', + shadowBlue: '#3498db', + shadowDark: '#040A01', + + // Icon/Badge Colors + iconBackground: '#ecf0f1', + badgeBackground: '#f1f5f9', + + // Transparent + transparent: 'transparent', +} + +// Color aliases for semantic usage +export const semanticColors = { + // Text + heading: demoColors.textPrimary, + body: demoColors.textSecondary, + caption: demoColors.textTertiary, + + // Backgrounds + screen: demoColors.background, + card: demoColors.surface, + + // Interactions + active: demoColors.primary, + hover: demoColors.surfaceHover, + disabled: demoColors.grayLight, + + // Status + success: demoColors.success, + error: demoColors.error, + warning: demoColors.warning, + info: demoColors.info, +} + +export type DemoColors = typeof demoColors +export type SemanticColors = typeof semanticColors diff --git a/example/theme/demoMetrics.ts b/example/theme/demoMetrics.ts new file mode 100644 index 0000000..eb2027c --- /dev/null +++ b/example/theme/demoMetrics.ts @@ -0,0 +1,210 @@ +/** + * Demo App Metrics System + * All spacing, sizing, and dimension values used in the component demo application + */ + +export const demoMetrics = { + // Spacing Scale + spacing: { + tiny: 4, + small: 8, + medium: 12, + normal: 16, + large: 20, + xlarge: 24, + xxlarge: 30, + xxxlarge: 40, + }, + + // Padding Scale + padding: { + tiny: 4, + small: 8, + medium: 12, + normal: 16, + large: 20, + xlarge: 24, + }, + + // Margin Scale + margin: { + tiny: 4, + small: 8, + medium: 10, + normal: 15, + large: 20, + xlarge: 30, + }, + + // Border Radius + borderRadius: { + tiny: 4, + small: 6, + medium: 8, + normal: 10, + large: 12, + xlarge: 14, + xxlarge: 16, + round: 25, + }, + + // Border Width + borderWidth: { + thin: 0.5, + normal: 1, + medium: 1.5, + thick: 2, + xthick: 3, + xxthick: 4, + xxxthick: 5, + }, + + // Font Sizes + fontSize: { + tiny: 11, + small: 12, + caption: 13, + body: 14, + subheading: 15, + normal: 16, + medium: 18, + large: 20, + xlarge: 22, + xxlarge: 24, + title: 26, + heading: 28, + hero: 32, + }, + + // Icon Sizes + iconSize: { + tiny: 16, + small: 20, + normal: 24, + medium: 28, + large: 32, + xlarge: 40, + xxlarge: 48, + huge: 56, + }, + + // Cell/Input Sizes (for CodeInput) + cellSize: { + tiny: 36, + small: 38, + compact: 40, + normal: 45, + medium: 48, + large: 50, + xlarge: 52, + xxlarge: 55, + huge: 60, + xhuge: 65, + }, + + // Button/Control Heights + controlHeight: { + small: 32, + normal: 40, + medium: 44, + large: 48, + xlarge: 52, + }, + + // Shadow + shadow: { + small: { + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.08, + shadowRadius: 4, + elevation: 2, + }, + normal: { + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.08, + shadowRadius: 8, + elevation: 3, + }, + medium: { + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 6, + elevation: 5, + }, + large: { + shadowOffset: { width: 0, height: 6 }, + shadowOpacity: 0.35, + shadowRadius: 8, + elevation: 8, + }, + glass: { + shadowOffset: { width: 0, height: 6 }, + shadowOpacity: 0.1, + shadowRadius: 32, + elevation: 8, + }, + }, + + // Opacity + opacity: { + disabled: 0.5, + dimmed: 0.6, + faded: 0.7, + translucent: 0.8, + }, + + // Line Height + lineHeight: { + tight: 20, + normal: 24, + relaxed: 28, + }, + + // Gap (for flex/grid) + gap: { + tiny: 4, + small: 8, + medium: 12, + normal: 16, + }, + + // Scale transforms + scale: { + small: 1.03, + normal: 1.05, + medium: 1.08, + }, +} + +// Metric aliases for specific use cases +export const componentMetrics = { + // Card + cardPadding: demoMetrics.padding.large, + cardBorderRadius: demoMetrics.borderRadius.xxlarge, + cardGap: demoMetrics.gap.medium, + + // Button + buttonMinWidth: 100, + buttonPaddingHorizontal: demoMetrics.padding.medium, + buttonPaddingVertical: demoMetrics.padding.small, + + // Icon Container + iconContainerSize: demoMetrics.iconSize.xlarge, + iconContainerPadding: demoMetrics.padding.tiny, + + // Section + sectionPadding: demoMetrics.padding.large, + sectionMargin: demoMetrics.margin.medium, + sectionBorderRadius: demoMetrics.borderRadius.large, + + // Container + containerPadding: demoMetrics.padding.large, + containerPaddingBottom: demoMetrics.padding.xlarge, + + // Stats + statDividerWidth: 1, + statDividerMargin: demoMetrics.spacing.normal, +} + +export type DemoMetrics = typeof demoMetrics +export type ComponentMetrics = typeof componentMetrics From 65ae822a969a27de0eca931cac0f673a446d6075 Mon Sep 17 00:00:00 2001 From: "hang.nguyen" Date: Wed, 1 Oct 2025 15:24:34 +0700 Subject: [PATCH 04/13] feat: figma code connect with base components --- code-connect/.env.example | 1 + code-connect/.gitignore | 41 + code-connect/components/Accordion.figma.tsx | 68 + code-connect/components/Button.figma.tsx | 65 + code-connect/components/Checkbox.figma.tsx | 71 + code-connect/components/CodeInput.figma.tsx | 28 + code-connect/components/Progress.figma.tsx | 48 + code-connect/components/RadioButton.figma.tsx | 95 ++ code-connect/components/Slider.figma.tsx | 48 + code-connect/components/TextInput.figma.tsx | 83 ++ code-connect/components/Typography.figma.tsx | 13 + code-connect/figma.config.json | 22 + code-connect/package.json | 34 + code-connect/yarn.lock | 1284 +++++++++++++++++ 14 files changed, 1901 insertions(+) create mode 100644 code-connect/.env.example create mode 100644 code-connect/.gitignore create mode 100644 code-connect/components/Accordion.figma.tsx create mode 100644 code-connect/components/Button.figma.tsx create mode 100644 code-connect/components/Checkbox.figma.tsx create mode 100644 code-connect/components/CodeInput.figma.tsx create mode 100644 code-connect/components/Progress.figma.tsx create mode 100644 code-connect/components/RadioButton.figma.tsx create mode 100644 code-connect/components/Slider.figma.tsx create mode 100644 code-connect/components/TextInput.figma.tsx create mode 100644 code-connect/components/Typography.figma.tsx create mode 100644 code-connect/figma.config.json create mode 100644 code-connect/package.json create mode 100644 code-connect/yarn.lock diff --git a/code-connect/.env.example b/code-connect/.env.example new file mode 100644 index 0000000..111a4ed --- /dev/null +++ b/code-connect/.env.example @@ -0,0 +1 @@ +FIGMA_ACCESS_TOKEN= \ No newline at end of file diff --git a/code-connect/.gitignore b/code-connect/.gitignore new file mode 100644 index 0000000..516c25f --- /dev/null +++ b/code-connect/.gitignore @@ -0,0 +1,41 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build output +dist/ +build/ +lib/ + +# TypeScript +*.tsbuildinfo + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# But keep example file +!.env.example + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +logs +*.log + +# Cache +.eslintcache +.cache/ diff --git a/code-connect/components/Accordion.figma.tsx b/code-connect/components/Accordion.figma.tsx new file mode 100644 index 0000000..67d773c --- /dev/null +++ b/code-connect/components/Accordion.figma.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import figma from '@figma/code-connect' +import {Accordion, Icon, Typography} from 'rn-base-component' +import {StyleSheet, View} from 'react-native' + +const ACCORDION_FIGMA_URL = '' + +const accordionProps = { + title: figma.string('label_text'), + expandMultiple: figma.enum('state', { + expand: true, + collapse: false, + disabled: false, + }), + leadingIcon: figma.boolean('leading_icon', { + true: , + false: undefined, + }), + trailingIcon: figma.boolean('trailing_icon', { + true: , + false: undefined, + }), + groupButtons: figma.boolean('group_buttons', { + true: figma.children('button'), + false: undefined, + }), + onPress: () => { + /* TODO: Handle onPress */ + }, +} + +const styles = StyleSheet.create({ + buttonContainer: { + flexDirection: 'row', + }, + headerContent: { + flexDirection: 'row', + }, +}) + +figma.connect(Accordion, ACCORDION_FIGMA_URL, { + props: accordionProps, + example: ({trailingIcon, leadingIcon, groupButtons, title, ...props}) => ( + ( + + {leadingIcon} + {item.title} + {trailingIcon} + + )} + renderContent={item => ( + + {item.content} + {groupButtons} + + )} + /> + ), +}) diff --git a/code-connect/components/Button.figma.tsx b/code-connect/components/Button.figma.tsx new file mode 100644 index 0000000..9f695f0 --- /dev/null +++ b/code-connect/components/Button.figma.tsx @@ -0,0 +1,65 @@ +import React from 'react' +import figma from '@figma/code-connect' +import { + Button, + ButtonPrimary, + ButtonSecondary, + ButtonOutline, + ButtonTransparent, + Icon, +} from 'rn-base-component' + +const BUTTON_FIGMA_URL = '' +const buttonProps = { + disabled: figma.enum('state', { + disabled: true, + }), + leftIcon: figma.boolean('leading_icon', { + true: figma.boolean('is_icon_only', { + true: undefined, + false: , + }), + false: undefined, + }), + rightIcon: figma.boolean('trailing_icon', { + true: figma.boolean('is_icon_only', { + true: undefined, + false: , + }), + false: undefined, + }), + children: figma.boolean('is_icon_only', { + true: , + false: figma.string('button_text'), + }), +} + +figma.connect(ButtonPrimary, BUTTON_FIGMA_URL, { + variant: {type: 'contained', style: 'primary'}, + props: buttonProps, + example: props => , +}) + +figma.connect(ButtonSecondary, BUTTON_FIGMA_URL, { + variant: {type: 'contained', style: 'secondary'}, + props: buttonProps, + example: props => , +}) + +figma.connect(ButtonOutline, BUTTON_FIGMA_URL, { + variant: {type: 'outlined'}, + props: buttonProps, + example: props => , +}) + +figma.connect(ButtonTransparent, BUTTON_FIGMA_URL, { + variant: {type: 'ghost'}, + props: buttonProps, + example: props => , +}) + +figma.connect(Button, BUTTON_FIGMA_URL, { + variant: {type: 'link'}, + props: buttonProps, + example: props => + ) +} +``` + +#### Step 3B: If Code Connect DOESN'T EXIST + +When NO Code Connect mapping: + +1. **Run `get_code` MCP** to get raw Figma code +2. **Apply theme token mapping** (colors, spacing, typography) +3. **Use StyleSheet.create()** for all styles +4. **Follow all styling constraints** (no inline styles, no hardcoded values) + +**Example:** + +```typescript +// No Code Connect mapping +// Generate custom component from Figma + +import React from 'react' +import {Pressable, Text, StyleSheet} from 'react-native' +import {colors} from '@/themes/colors' +import {metrics, fontSizes, fontWeights} from '@/themes/metrics' +import {fonts} from '@/themes/fonts' + +const CustomButton = ({onPress, children}) => { + return ( + + {children} + + ) +} + +const styles = StyleSheet.create({ + button: { + padding: metrics.spacing16, + backgroundColor: colors.background.brandPrimary, + borderRadius: metrics.radius8, + }, + text: { + fontSize: fontSizes.md, + fontWeight: fontWeights.medium, + fontFamily: fonts.medium, + color: colors.foreground.white, + }, +}) + +export default CustomButton +``` + +--- + +## 3. Mandatory Rules + +### 3.1 Code Connect Priority Rules + +**🚨 CRITICAL: Code Connect has HIGHEST PRIORITY** + +``` +Priority Order: +1. Check Code Connect FIRST (always) +2. Use linked components IF they exist (mandatory) +3. Generate new code ONLY when no Code Connect exists +4. Apply theme tokens ALWAYS (for all code) +``` + +**⚠️ STOP AND VERIFY CHECKLIST:** + +Before writing ANY component code, answer these questions: + +- [ ] Did I run `get_code_connect_map` for this node? +- [ ] Did the response contain a mapping for this node ID? +- [ ] If YES mapping exists → Am I importing and using that exact component? +- [ ] If NO mapping exists → Only then can I generate custom code + +**🛑 RED FLAG WARNINGS:** + +If you're about to write any of these, STOP and check Code Connect first: + +- `` → Could be `ButtonPrimary`, `ButtonSecondary`, etc. +- `` → Could be a mapped button component +- `` → Could be a mapped input component +- `` with complex styling → Could be a mapped container component +- Custom icon implementations → Could be mapped icon components + +**Remember: If Code Connect says a component exists, you MUST use it. No exceptions.** + +### 3.2 MUST DO Rules + +**✅ When Code Connect mapping EXISTS:** + +1. ✅ **MUST use the linked component** - Never generate duplicate code +2. ✅ **MUST import from the specified path** - Use exact `codeConnectSrc` path +3. ✅ **MUST use the exact component name** - Use `codeConnectName` exactly as provided +4. ✅ **MUST read the component file** - Understand props, API, and usage patterns +5. ✅ **MUST map Figma properties to component props** - Translate design properties correctly +6. ✅ **MUST check all nested components** - Children may also have Code Connect +7. ✅ **MUST apply theme tokens** - For container/layout styles around components + +**❌ NEVER DO Rules (VIOLATIONS):** + +```typescript +// ❌ VIOLATION 1: Generating new component when Code Connect exists +const Button = () => { + return ... // Code Connect says use src/components/Button.tsx! +} + +// ❌ VIOLATION 2: Using wrong import path +import {Button} from '@/ui/Button' // Code Connect says src/components/Button.tsx! + +// ❌ VIOLATION 3: Using wrong component name +import {CustomButton} from '@/components/Button' // Code Connect says name is "Button"! + +// ❌ VIOLATION 4: Not checking Code Connect before generating +// ALWAYS run get_code_connect_map FIRST! + +// ❌ VIOLATION 5: Recreating component logic +const MyButton = ({children}) => { + // Duplicating existing Button component logic + return +} + +// ❌ VIOLATION 6: Ignoring component props +; // Missing required props like variant, onPress +``` + +### 3.3 Code Generation Checklist + +**Before generating ANY code from Figma, complete this checklist:** + +- [ ] Extract node ID from Figma URL +- [ ] Run `get_code_connect_map` for the node +- [ ] **🚨 CRITICAL: Analyze the Code Connect response** + - [ ] List ALL node IDs that have mappings + - [ ] Note component name and source for EACH mapping + - [ ] Create a mapping reference table before coding +- [ ] Check if Code Connect mapping exists +- [ ] **IF Code Connect EXISTS:** + - [ ] ⚠️ MANDATORY: Read linked component file + - [ ] ⚠️ MANDATORY: Understand component props and API + - [ ] ⚠️ MANDATORY: Map Figma properties to component props + - [ ] ⚠️ MANDATORY: Import using exact path from `codeConnectSrc` + - [ ] ⚠️ MANDATORY: Use exact name from `codeConnectName` + - [ ] Check all nested/child components for Code Connect + - [ ] **NEVER generate custom code for this element** +- [ ] **IF Code Connect DOESN'T EXIST:** + - [ ] Run `get_code` MCP to get Figma code + - [ ] Identify Figma variables + - [ ] Map to theme tokens + - [ ] Use StyleSheet.create() + - [ ] No inline styles + - [ ] No hardcoded values +- [ ] **ALWAYS:** + - [ ] Apply theme token mapping + - [ ] Import necessary theme modules + - [ ] Verify no hardcoded values + - [ ] **Double-check: Did I use ALL Code Connect components?** + - [ ] Test code compiles + +**⚠️ VIOLATION CHECK:** + +After generating code, verify: + +- [ ] Every node with Code Connect uses the linked component +- [ ] No `TouchableOpacity`/`Pressable` where `ButtonPrimary` exists +- [ ] No custom implementations duplicating Code Connect components +- [ ] All imports match `codeConnectSrc` paths exactly + +--- + +## 3.4 Real-World Violation Example + +### Case Study: Floating Action Button Violation + +**Scenario:** Implementing a home screen with a floating action button from Figma. + +**Code Connect Response:** + +```json +{ + "85:439": { + "componentName": "ButtonPrimary", + "source": "https://github.com/.../rn-base-component/.../ButtonPrimary.d.ts", + "snippet": "}/>", + "snippetImports": ["import { ButtonPrimary } from 'rn-base-component'"] + } +} +``` + +**❌ VIOLATION - What was generated:** + +```typescript +// WRONG - Ignored Code Connect mapping +; + + + + +const styles = StyleSheet.create({ + fab: { + width: 56, + height: 56, + borderRadius: 1000, + backgroundColor: colors.foreground.brandPrimary, + // ... custom styling + }, +}) +``` + +**Why this is wrong:** + +1. ❌ Code Connect said to use `ButtonPrimary` from `rn-base-component` +2. ❌ Generated custom `TouchableOpacity` instead +3. ❌ Duplicated button functionality that already exists +4. ❌ Lost built-in theming, states, and accessibility features +5. ❌ Creates maintenance burden with duplicate code + +**✅ CORRECT - Using Code Connect:** + +```typescript +import {ButtonPrimary, Icon} from 'rn-base-component' + +// CORRECT - Uses mapped component +; + + + +const styles = StyleSheet.create({ + fab: { + position: 'absolute', + bottom: 124, + right: metrics.spacing16, + // Only container positioning, not button styling + }, +}) +``` + +**Lessons Learned:** + +1. **Always create the mapping table (Step 2.5)** before writing code +2. **Stop when you see `TouchableOpacity`** - check if it should be a mapped button +3. **Read the Code Connect response carefully** - it tells you exactly what to use +4. **Use the linked component's styling** - don't recreate its internal styles +5. **Container styles only** - only style the positioning, not the component itself + +**Prevention Checklist:** + +Before writing ``, ask: + +- [ ] Did I check Code Connect for this node? +- [ ] Is there a mapped button component? +- [ ] Did I read the component's props/API? +- [ ] Am I using the exact import path? +- [ ] Am I only styling the container, not the component? + +--- + +## 4. Usage Examples + +### 4.1 Example 1: Simple Button with Code Connect + +**Scenario:** Generate login button from Figma design + +**Figma Design:** + +- Node ID: `456:789` +- Component: Button +- Text: "Login" +- Variant: Primary +- Size: Medium + +**Step 1: Check Code Connect** + +```typescript +mcp_Figma_get_code_connect_map({ + nodeId: "456:789", + clientLanguages: "typescript", + clientFrameworks: "react" +}) + +// Response: +{ + "456:789": { + "codeConnectSrc": "src/components/Button.tsx", + "codeConnectName": "Button" + } +} +``` + +**Step 2: Read Component** + +```typescript +// Read src/components/Button.tsx +interface ButtonProps { + variant?: 'primary' | 'secondary' | 'outline' + size?: 'small' | 'medium' | 'large' + icon?: string + onPress: () => void + disabled?: boolean + children: React.ReactNode +} + +export const Button: React.FC = ({ + variant = 'primary', + size = 'medium', + icon, + onPress, + disabled, + children, +}) => { + // Component implementation... +} +``` + +**Step 3: Generate Code** + +```typescript +// ✅ CORRECT - Using Code Connect component +import React from 'react' +import {View, StyleSheet} from 'react-native' +import {Button} from '@/components/Button' +import {metrics} from '@/themes/metrics' + +const LoginScreen = () => { + const handleLogin = () => { + // Login logic + } + + return ( + + + + ) +} + +const styles = StyleSheet.create({ + container: { + padding: metrics.spacing16, + }, +}) + +export default LoginScreen +``` + +### 4.2 Example 2: Form with Multiple Code Connect Components + +**Scenario:** Generate registration form from Figma + +**Figma Design:** + +- Node ID: `100:200` +- Contains: Email TextInput (Node: `100:201`) +- Contains: Password TextInput (Node: `100:202`) +- Contains: Confirm Password TextInput (Node: `100:203`) +- Contains: Checkbox "I agree to terms" (Node: `100:204`) +- Contains: Submit Button (Node: `100:205`) + +**Step 1: Check Code Connect for All Components** + +```typescript +mcp_Figma_get_code_connect_map({ + nodeId: "100:200", // Parent form + clientLanguages: "typescript", + clientFrameworks: "react" +}) + +// Response: +{ + "100:200": null, // Parent container has no mapping + "100:201": { // Email TextInput + "codeConnectSrc": "src/components/TextInput.tsx", + "codeConnectName": "TextInput" + }, + "100:202": { // Password TextInput + "codeConnectSrc": "src/components/TextInput.tsx", + "codeConnectName": "TextInput" + }, + "100:203": { // Confirm Password TextInput + "codeConnectSrc": "src/components/TextInput.tsx", + "codeConnectName": "TextInput" + }, + "100:204": { // Checkbox + "codeConnectSrc": "src/components/Checkbox.tsx", + "codeConnectName": "Checkbox" + }, + "100:205": { // Button + "codeConnectSrc": "src/components/Button.tsx", + "codeConnectName": "Button" + } +} +``` + +**Step 2: Read All Component Files** + +```typescript +// src/components/TextInput.tsx +interface TextInputProps { + label: string + placeholder?: string + value: string + onChangeText: (text: string) => void + error?: string + secureTextEntry?: boolean + keyboardType?: KeyboardTypeOptions +} + +// src/components/Checkbox.tsx +interface CheckboxProps { + label: string + checked: boolean + onToggle: (checked: boolean) => void +} + +// src/components/Button.tsx +interface ButtonProps { + variant?: 'primary' | 'secondary' + size?: 'small' | 'medium' | 'large' + onPress: () => void + disabled?: boolean + children: React.ReactNode +} +``` + +**Step 3: Generate Complete Form** + +```typescript +// ✅ CORRECT - Using all Code Connect components +import React, {useState} from 'react' +import {View, StyleSheet} from 'react-native' +import {TextInput} from '@/components/TextInput' +import {Checkbox} from '@/components/Checkbox' +import {Button} from '@/components/Button' +import {metrics} from '@/themes/metrics' + +const RegistrationScreen = () => { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [confirmPassword, setConfirmPassword] = useState('') + const [agreedToTerms, setAgreedToTerms] = useState(false) + + const handleRegister = () => { + // Registration logic + } + + const isFormValid = + email.length > 0 && password.length >= 8 && password === confirmPassword && agreedToTerms + + return ( + + + + + + + + + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: metrics.spacing16, + gap: metrics.spacing16, + }, +}) + +export default RegistrationScreen +``` + +### 4.3 Example 3: Mixed - Code Connect + Custom Code + +**Scenario:** Screen with custom header but reusable button + +**Figma Design:** + +- Node ID: `300:400` +- Contains: Custom Header with logo and title (Node: `300:401`) - No Code Connect +- Contains: Custom welcome message (Node: `300:402`) - No Code Connect +- Contains: "Get Started" Button (Node: `300:403`) - Has Code Connect +- Contains: "Learn More" Button (Node: `300:404`) - Has Code Connect + +**Step 1: Check Code Connect** + +```typescript +mcp_Figma_get_code_connect_map({ + nodeId: "300:400", + clientLanguages: "typescript", + clientFrameworks: "react" +}) + +// Response: +{ + "300:400": null, // Parent has no mapping + "300:401": null, // Custom header - no mapping + "300:402": null, // Custom message - no mapping + "300:403": { // Get Started button + "codeConnectSrc": "src/components/Button.tsx", + "codeConnectName": "Button" + }, + "300:404": { // Learn More button + "codeConnectSrc": "src/components/Button.tsx", + "codeConnectName": "Button" + } +} +``` + +**Step 2: Generate Mixed Code** + +```typescript +// ✅ CORRECT - Mix of Code Connect and custom code +import React from 'react' +import {View, Text, Image, StyleSheet} from 'react-native' +import {Button} from '@/components/Button' // ← Code Connect +import {colors} from '@/themes/colors' +import {metrics, fontSizes, fontWeights} from '@/themes/metrics' +import {fonts} from '@/themes/fonts' +import {images} from '@/themes/images' + +const WelcomeScreen = () => { + const handleGetStarted = () => { + // Navigate to sign up + } + + const handleLearnMore = () => { + // Navigate to info screen + } + + return ( + + {/* Custom Header - Generated from Figma (no Code Connect) */} + + + Welcome to MyApp + Your journey to success starts here + + + {/* Custom Message - Generated from Figma (no Code Connect) */} + + + Join thousands of users who have already transformed their lives + + + + {/* Buttons - Using Code Connect */} + + + + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: metrics.spacing24, + backgroundColor: colors.background.primary, + }, + header: { + alignItems: 'center', + marginBottom: metrics.spacing32, + }, + logo: { + width: metrics.iconSize24 * 3, + height: metrics.iconSize24 * 3, + marginBottom: metrics.spacing16, + }, + title: { + fontSize: fontSizes.xl, + fontWeight: fontWeights.medium, + fontFamily: fonts.medium, + color: colors.text.primary, + marginBottom: metrics.spacing8, + textAlign: 'center', + }, + subtitle: { + fontSize: fontSizes.md, + fontWeight: fontWeights.regular, + fontFamily: fonts.regular, + color: colors.text.secondary, + textAlign: 'center', + }, + messageContainer: { + padding: metrics.spacing16, + backgroundColor: colors.background.brandPrimary, + borderRadius: metrics.radius8, + marginBottom: metrics.spacing32, + }, + messageText: { + fontSize: fontSizes.sm, + color: colors.text.primary, + textAlign: 'center', + lineHeight: 20, + }, + buttonContainer: { + gap: metrics.spacing12, + }, +}) + +export default WelcomeScreen +``` + +### 4.4 Example 4: Nested Components with Code Connect + +**Scenario:** Product card with multiple nested components + +**Figma Design:** + +- Product Card (Node: `500:100`) + - Product Image (Node: `500:101`) - Custom + - Product Title (Node: `500:102`) - Custom + - Product Price (Node: `500:103`) - Custom + - Add to Cart Button (Node: `500:104`) - Code Connect + - Favorite Icon Button (Node: `500:105`) - Code Connect + +**Step 1: Check All Components** + +```typescript +mcp_Figma_get_code_connect_map({ + nodeId: "500:100", + clientLanguages: "typescript", + clientFrameworks: "react" +}) + +// Response: +{ + "500:100": null, // Card container - no mapping + "500:101": null, // Image - no mapping + "500:102": null, // Title - no mapping + "500:103": null, // Price - no mapping + "500:104": { // Add to Cart Button + "codeConnectSrc": "src/components/Button.tsx", + "codeConnectName": "Button" + }, + "500:105": { // Favorite Icon Button + "codeConnectSrc": "src/components/IconButton.tsx", + "codeConnectName": "IconButton" + } +} +``` + +**Step 2: Generate Product Card Component** + +```typescript +// ✅ CORRECT - Nested mix of Code Connect and custom code +import React from 'react' +import {View, Text, Image, StyleSheet} from 'react-native' +import {Button} from '@/components/Button' // ← Code Connect +import {IconButton} from '@/components/IconButton' // ← Code Connect +import {colors} from '@/themes/colors' +import {metrics, fontSizes, fontWeights} from '@/themes/metrics' +import {fonts} from '@/themes/fonts' + +interface ProductCardProps { + product: { + id: string + title: string + price: number + imageUrl: string + } + onAddToCart: (id: string) => void + onToggleFavorite: (id: string) => void + isFavorite: boolean +} + +const ProductCard: React.FC = ({product, onAddToCart, onToggleFavorite, isFavorite}) => { + return ( + + {/* Custom Image Section */} + + + + {/* Icon Button - Code Connect */} + onToggleFavorite(product.id)} + style={styles.favoriteButton} + /> + + + {/* Custom Product Info */} + + + {product.title} + + ${product.price.toFixed(2)} + + + {/* Button - Code Connect */} + + + ) +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: colors.background.primary, + borderRadius: metrics.radius12, + padding: metrics.spacing16, + gap: metrics.spacing12, + shadowColor: colors.foreground.primary, + shadowOffset: {width: 0, height: 2}, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + }, + imageContainer: { + position: 'relative', + width: '100%', + height: 200, + borderRadius: metrics.radius8, + overflow: 'hidden', + }, + image: { + width: '100%', + height: '100%', + }, + favoriteButton: { + position: 'absolute', + top: metrics.spacing8, + right: metrics.spacing8, + }, + infoContainer: { + gap: metrics.spacing4, + }, + title: { + fontSize: fontSizes.md, + fontWeight: fontWeights.medium, + fontFamily: fonts.medium, + color: colors.text.primary, + }, + price: { + fontSize: fontSizes.lg, + fontWeight: fontWeights.medium, + fontFamily: fonts.medium, + color: colors.text.brandPrimary, + }, +}) + +export default ProductCard +``` + +--- + +## 5. Integration with Theme System + +### 5.1 Code Connect + Theme Tokens + +Code Connect components **MUST** work together with theme token mapping: + +**Rule:** When using Code Connect components: + +- ✅ Use Code Connect for **component imports and usage** +- ✅ Use theme tokens for **container/layout styles** +- ✅ Never override Code Connect component styles with inline styles + +**Example:** + +```typescript +// ✅ CORRECT - Code Connect + Theme Tokens +import { Button } from '@/components/Button' // Code Connect +import { metrics } from '@/themes/metrics' // Theme tokens + +const MyScreen = () => { + return ( + {/* Theme tokens for layout */} + + + ) +} + +const styles = StyleSheet.create({ + container: { + padding: metrics.spacing16, // ✅ Theme token + gap: metrics.spacing12, // ✅ Theme token + }, +}) + +// ❌ WRONG - Overriding Code Connect component + {/* ❌ Inline style + hardcoded */} + + +``` + +### 5.2 Priority Order + +When generating code, follow this priority: + +``` +1. Code Connect Check (Highest Priority) + ↓ +2. Use Code Connect components if they exist + ↓ +3. Apply theme tokens for layout/container styles + ↓ +4. Generate custom code only when no Code Connect + ↓ +5. Apply theme tokens to custom code (Lowest Priority, Always Apply) +``` + +### 5.3 Complete Integration Example + +```typescript +// Complete example showing Code Connect + Theme Token integration + +import React, {useState} from 'react' +import {View, Text, ScrollView, StyleSheet} from 'react-native' + +// Code Connect Components +import {TextInput} from '@/components/TextInput' +import {Button} from '@/components/Button' +import {Checkbox} from '@/components/Checkbox' + +// Theme System +import {colors} from '@/themes/colors' +import {metrics, fontSizes, fontWeights} from '@/themes/metrics' +import {fonts} from '@/themes/fonts' + +const CompleteFormScreen = () => { + const [formData, setFormData] = useState({ + name: '', + email: '', + agreedToTerms: false, + }) + + const handleSubmit = () => { + // Submit logic + } + + return ( + + {/* Custom Header - No Code Connect */} + + Sign Up + Create your account + + + {/* Form Fields - Code Connect Components */} + + setFormData({...formData, name})} + /> + + setFormData({...formData, email})} + keyboardType="email-address" + /> + + setFormData({...formData, agreedToTerms})} + /> + + + {/* Submit Button - Code Connect */} + + + {/* Custom Footer - No Code Connect */} + + Already have an account? + Sign In + + + ) +} + +const styles = StyleSheet.create({ + // All styles use theme tokens + container: { + flex: 1, + backgroundColor: colors.background.primary, + padding: metrics.spacing24, + }, + header: { + marginBottom: metrics.spacing32, + }, + title: { + fontSize: fontSizes.xl, + fontWeight: fontWeights.medium, + fontFamily: fonts.medium, + color: colors.text.primary, + marginBottom: metrics.spacing8, + }, + subtitle: { + fontSize: fontSizes.md, + fontWeight: fontWeights.regular, + fontFamily: fonts.regular, + color: colors.text.secondary, + }, + form: { + gap: metrics.spacing16, + marginBottom: metrics.spacing24, + }, + footer: { + flexDirection: 'row', + justifyContent: 'center', + gap: metrics.spacing4, + marginTop: metrics.spacing24, + }, + footerText: { + fontSize: fontSizes.sm, + color: colors.text.tertiary, + }, + footerLink: { + fontSize: fontSizes.sm, + color: colors.text.brandPrimary, + fontWeight: fontWeights.medium, + }, +}) + +export default CompleteFormScreen +``` + +--- + +## Summary + +### Key Takeaways + +1. **ALWAYS check Code Connect FIRST** before generating any code from Figma +2. **CREATE MAPPING TABLE (Step 2.5)** after getting Code Connect response +3. **MUST use linked components** when Code Connect mappings exist +4. **NEVER generate duplicate code** for components with Code Connect +5. **ALWAYS apply theme tokens** for layout and container styles +6. **Mix Code Connect and custom code** intelligently based on mappings +7. **Read component files** to understand props and API before using +8. **Map Figma properties** correctly to component props + +### Workflow Summary + +``` +Figma Design → Check Code Connect → 🚨 CREATE MAPPING TABLE 🚨 → Use Linked Components (if exist) → Apply Theme Tokens → Done + ↓ + Generate Custom Code (if no mapping) → Apply Theme Tokens → Done +``` + +### Rules Summary + +- ✅ Code Connect = Highest Priority +- ✅ Mapping Table = MANDATORY before coding +- ✅ Theme Tokens = Always Apply +- ✅ Reuse > Generate +- ❌ Never duplicate components with Code Connect +- ❌ Never ignore Code Connect mappings +- ❌ Never skip the mapping table analysis +- ❌ Never use inline styles or hardcoded values + +### Common Violations to Avoid + +| Violation | Instead Use | +| ---------------------------------------------- | ---------------------------------------- | +| `` when ButtonPrimary exists | `` from rn-base-component | +| `` when Button exists | The mapped Button component | +| Custom TextInput when mapped | The linked TextInput component | +| Recreating component logic | Import and use existing component | +| Skipping Code Connect check | Always run get_code_connect_map first | +| Ignoring mapping response | Create table, review ALL mappings | + +### Final Warning + +**🚨 THE MOST COMMON MISTAKE:** + +Checking Code Connect, seeing a mapping exists, then **generating custom code anyway**. + +**This happens when you:** + +- ❌ Skip creating the mapping table (Step 2.5) +- ❌ Don't carefully read the Code Connect response +- ❌ Rush to code generation without analysis +- ❌ Forget to verify mapped components were used + +**To prevent this:** + +- ✅ ALWAYS create mapping table before coding +- ✅ Review table: "Which nodes MUST use existing components?" +- ✅ After coding: "Did I use ALL mapped components?" +- ✅ Code review: No `TouchableOpacity`/`Pressable` where buttons map + +### Success Checklist + +After implementing from Figma, verify: + +- [ ] I ran `get_code_connect_map` for all nodes +- [ ] I created the mapping table (Step 2.5) +- [ ] Every mapped node uses the linked component +- [ ] No custom implementations for mapped components +- [ ] Imports match Code Connect sources exactly +- [ ] Props are correctly mapped from Figma to component +- [ ] Theme tokens applied for container/layout styles +- [ ] No inline styles or hardcoded values + +By following this guide strictly, especially **Step 2.5 (mapping table creation)**, you ensure that AI-generated code from Figma designs consistently reuses your existing components while maintaining theme consistency across your entire application. + +**Remember: Code Connect exists to prevent duplicate code. Honor it.** diff --git a/code-connect/docs/Mobile_Figma_Variables_Setup_Guide.md b/code-connect/docs/Mobile_Figma_Variables_Setup_Guide.md new file mode 100644 index 0000000..2947422 --- /dev/null +++ b/code-connect/docs/Mobile_Figma_Variables_Setup_Guide.md @@ -0,0 +1,1150 @@ +# Mobile Theme Setup Guideline with Figma MCP + +This document provides step-by-step instructions for setting up and synchronizing mobile theme variables from Figma designs using Figma MCP (Model Context Protocol). + +## Prerequisites + +- Figma MCP installed and configured in your development environment +- Access to the Figma design file with proper permissions +- Existing project structure with theme files: + - `src/themes/colors.ts` + - `src/themes/metrics.ts` + - `src/themes/fonts.ts` + - `src/themes/theme.ts` + +## Overview + +The theme setup process consists of three main steps: + +1. **Extract variables** from Figma using MCP +2. **Transform and organize** variables into appropriate theme files +3. **Create design system rules** for consistent usage + +--- + +## Step 1: Extract Variables from Figma + +### 1.1 Get the Figma Node ID + +Extract the node ID from your Figma URL: + +``` +URL Format: https://figma.com/design/:fileKey/:fileName?node-id=1-2 +Node ID Format: 1:2 (replace dash with colon) + +Example: +URL: https://www.figma.com/design/JfmuXTpcFBa0xj3JzwNtxx/Project?node-id=9-55163 +Node ID: 9:55163 +``` + +### 1.2 Run MCP Command + +Use the Figma MCP `get_variable_defs` tool to extract all design variables: + +```typescript +mcp_Figma_get_variable_defs({ + nodeId: '9:55163', + clientLanguages: 'typescript', + clientFrameworks: 'react', +}) +``` + +### 1.3 Review Extracted Variables + +The MCP will return variables in the following categories: + +- **Colors**: Text, background, foreground, border, feedback colors, and color palettes +- **Typography**: Font families, sizes, weights, line heights +- **Spacing**: Padding and margin values +- **Radius**: Border radius values +- **Icon Sizes**: Standard icon dimensions + +Example output format: + +```json +{ + "color/text/text-primary": "#hexValue", + "typography/font-size/text/md": "number", + "spacing/8": "number", + "radius/8": "number", + "icon-size/24": "number" +} +``` + +**Note:** The actual values will depend on your Figma design system. + +--- + +## Step 2: Transform and Organize Variables + +### 2.1 COLORS - Update `src/themes/colors.ts` + +#### 2.1.1 Naming Convention Rules + +**MANDATORY RULES:** + +- ✅ Use **camelCase** for all color names +- ✅ Remove special characters (slashes, hyphens) +- ✅ Convert hierarchical structure to meaningful names + +**Transformation Examples:** + +```typescript +// Figma Variable → Code Variable Name +"color/text/text-primary_on-brand" → textPrimaryOnBrand +"color/background/bg-primary" → bgPrimary +"color/border/border-secondary" → borderSecondary +"color/feedback/text/error" → feedbackTextError +``` + +#### 2.1.2 Color Structure + +**Transformation Pattern:** + +``` +color/category/name → colors.category.name +color/category/subcategory/name → colors.category.subcategory.name +``` + +**Template Structure:** + +```typescript +const colors = { + // Direct colors + primary: '#hexValue', + + // Nested categories + text: { + primary: '#hexValue', + secondary: '#hexValue', + }, + + background: { + primary: '#hexValue', + }, + + feedback: { + success: { + text: '#hexValue', + }, + error: { + text: '#hexValue', + }, + }, + + // Legacy colors (if existing) + light: { ... }, + dark: { ... }, +} as const +``` + +**Implementation Steps:** + +1. Run `get_variable_defs` to extract all Figma color variables +2. Identify hierarchy patterns in variable names +3. Create nested objects matching Figma structure +4. Convert names to camelCase (remove hyphens/underscores) +5. Replace `#hexValue` with actual Figma values + +**Example Transformation:** + +```typescript +// Figma Variables: +"color/text/text-primary": "#123456" +"color/background/bg-primary": "#ffffff" +"color/feedback/error/text": "#ff0000" + +// Code Implementation: +const colors = { + text: { + primary: '#123456', + }, + background: { + primary: '#ffffff', + }, + feedback: { + error: { + text: '#ff0000', + }, + }, +} as const +``` + +#### 2.1.3 Color Palette (Separate Constant) + +**Purpose:** For colors with numeric scales (e.g., red/100, blue/500) + +**Transformation Pattern:** + +``` +color/red/100 → palette.red._100 +color/blue/500 → palette.blue._500 +color/black/opacity/20 → palette.black._20 +``` + +**Template Structure:** + +```typescript +const palette = { + red: { + _50: '#hexValue', + _100: '#hexValue', + _500: '#hexValue', + }, + blue: { + _300: '#hexValue', + }, + black: { + _20: '#hexValue', + }, +} as const +``` + +**Implementation Steps:** + +1. Identify numeric patterns in Figma variables (`/50`, `/100`, `/500`) +2. Group by color name (all red shades together) +3. Use underscore prefix for numbers (`50` → `_50`) +4. Only include colors with multiple numeric variants + +**Example Transformation:** + +```typescript +// Figma Variables: +"color/red/100": "#ffcccc" +"color/red/500": "#ff0000" +"color/blue/300": "#6699ff" + +// Code Implementation: +const palette = { + red: { + _100: '#ffcccc', + _500: '#ff0000', + }, + blue: { + _300: '#6699ff', + }, +} as const +``` + +#### 2.1.4 Replace Existing Values + +**IMPORTANT:** When a color name or meaning matches an existing color: + +- ✅ **MUST replace** the existing value with the new value from Figma +- ✅ Maintain backward compatibility by keeping the property name + +Example: + +```typescript +// Before (existing in your project) +const colors = { + primary: '#oldHexValue', + error: '#oldHexValue', +} + +// After (updated from Figma) +const colors = { + primary: '#newFigmaValue', // ✅ Replaced with Figma value + error: '#newFigmaValue', // ✅ Replaced with Figma value +} +``` + +#### 2.1.5 Utility Functions + +Keep existing utility functions at the bottom: + +```typescript +const getColorOpacity = (color: string, opacity: number): string => { + if (opacity >= 0 && opacity <= 1 && color.includes('#')) { + const hexValue = Math.round(opacity * 255).toString(16) + return `${color.slice(0, 7)}${hexValue.padStart(2, '0').toUpperCase()}` + } + return color +} + +export {colors, palette, getColorOpacity} +``` + +--- + +### 2.2 SPACING, RADIUS, ICON SIZE - Update `src/themes/metrics.ts` + +#### 2.2.1 Naming Convention Rules + +**MANDATORY RULES:** + +- ✅ Use **camelCase** for all metric names +- ✅ Append the numeric value to the base name +- ✅ Use `responsiveHeight()` for spacing and radius +- ✅ Use `responsiveWidth()` or `responsiveHeight()` for icon sizes + +**Transformation Examples:** + +```typescript +// Figma Variable → Code Variable Name +"spacing/4" → spacing4: responsiveHeight(4) +"spacing/16" → spacing16: responsiveHeight(16) +"radius/8" → radius8: responsiveHeight(8) +"icon-size/24" → iconSize24: responsiveHeight(24) +``` + +#### 2.2.2 Metrics Structure + +**Template Structure:** + +```typescript +import {Dimensions, Platform} from 'react-native' + +const DESIGN_WIDTH = 375 +const DESIGN_HEIGHT = 812 +const {width, height} = Dimensions.get('window') + +function responsiveWidth(value: T) { + return ((width * value) / DESIGN_WIDTH) as T +} + +function responsiveHeight(value: T) { + return ((height * value) / DESIGN_HEIGHT) as T +} + +function responsiveFont(value: T) { + return ((width * value) / DESIGN_WIDTH) as T +} + +const metrics = { + // Spacing from Figma + spacing4: responsiveHeight(4), + spacing8: responsiveHeight(8), + spacing16: responsiveHeight(16), + + // Radius from Figma + radius4: responsiveHeight(4), + radius8: responsiveHeight(8), + radiusRound: responsiveHeight(1000), + + // Icon sizes from Figma + iconSize16: responsiveHeight(16), + iconSize24: responsiveHeight(24), + + // Legacy values (keep existing) + borderWidth: responsiveHeight(1), + textInputHeight: responsiveHeight(44), +} as const +``` + +**Implementation Steps:** + +1. Add Figma spacing values: `spacing/X` → `spacingX: responsiveHeight(X)` +2. Add Figma radius values: `radius/X` → `radiusX: responsiveHeight(X)` +3. Add Figma icon sizes: `icon-size/X` → `iconSizeX: responsiveHeight(X)` +4. Keep existing legacy values for backward compatibility + +#### 2.2.3 Update Strategy + +When updating metrics: + +1. ✅ **Add new Figma values** with their exact names +2. ✅ **Keep legacy values** for backward compatibility +3. ✅ **Document equivalents** with comments (e.g., `// Same as spacing16`) +4. ✅ **Gradually migrate** old usage to new names in future refactoring + +--- + +### 2.3 TYPOGRAPHY - Update `src/themes/metrics.ts` + +#### 2.3.1 Font Sizes + +**Template Structure:** + +```typescript +const fontSizes = { + xs: responsiveFont(12), + sm: responsiveFont(14), + md: responsiveFont(16), + lg: responsiveFont(18), + xl: responsiveFont(20), + + // Legacy values (keep existing) + body: responsiveFont(16), + title: responsiveFont(20), +} as const +``` + +#### 2.3.2 Font Weights + +**Template Structure:** + +```typescript +const fontWeights = { + regular: '400', + medium: '500', + + // Legacy values (keep existing) + semiBold: '600', + bold: '700', +} as const +``` + +#### 2.3.3 Font Families + +**Update `src/themes/fonts.ts`:** + +```typescript +const fonts = { + regular: 'YourFont-Regular', + medium: 'YourFont-Medium', + bold: 'YourFont-Bold', + family: 'YourFontFamily', +} as const + +export {fonts} +``` + +#### 2.3.4 Line Heights (Optional) + +**If needed:** + +```typescript +const lineHeights = { + xs: responsiveHeight(18), + sm: responsiveHeight(20), + md: responsiveHeight(22), +} as const +``` + +**Implementation Steps:** + +1. Map Figma font sizes: `typography/font-size/text/md` → `fontSizes.md` +2. Map Figma font weights: `typography/font-weight/medium` → `fontWeights.medium` +3. Update font family names to match your actual fonts +4. Add line heights if your design system uses them + +--- + +## Step 3: Create Design System Rules (Figma Theming and Styling) + +### 3.1 Purpose of Design System Rules + +The design system rules file acts as a **bridge** between Figma variables and your code implementation. When AI generates code from Figma designs using `get_code`, it needs to know how to translate Figma variable names to your actual theme tokens. + +**Example Mapping:** + +- Figma variable: `spacing/4` → Code: `metrics.spacing4` +- Figma variable: `color/text/text-primary` → Code: `colors.text.primary` +- Figma variable: `typography/font-size/text/md` → Code: `fontSizes.md` + +**This file also includes:** + +- ✅ Complete variable mapping tables (colors, spacing, radius, typography) +- ✅ Theming system rules (metrics reuse, platform detection) +- ✅ Styling constraints (no inline styles, no hardcoded values) +- ✅ Code generation rules and best practices +- ✅ Special patterns for feedback colors, opacity, rounded elements + +### 3.2 Create Theming and Styling Rules File + +Create a comprehensive rules file at `.cursor/rules/figma-theming-and-styling.mdc`: + +**Template Structure:** + +````markdown +# Figma Design System: Theming and Styling Rules + +This document defines the complete design system integration between Figma designs and React Native code implementation, including variable mapping, theming rules, and styling constraints. + +## Import Statements + +```typescript +// ALWAYS include these imports when using theme tokens +import {colors, palette} from '@/themes/colors' +import {metrics, fontSizes, fontWeights} from '@/themes/metrics' +import {fonts} from '@/themes/fonts' +``` +```` + +## 1. COLOR VARIABLE MAPPING + +Create tables for EACH color category found in your Figma design: + +### Example: Text Colors + +| Figma Variable | Code Implementation | Hex Value | +| --------------------------- | ----------------------- | --------- | +| `color/text/text-primary` | `colors.text.primary` | #hexValue | +| `color/text/text-secondary` | `colors.text.secondary` | #hexValue | +| ... | ... | ... | + +### Example: Background Colors + +| Figma Variable | Code Implementation | Hex Value | +| ------------------------------- | ----------------------------- | --------- | +| `color/background/bg-primary` | `colors.background.primary` | #hexValue | +| `color/background/bg-secondary` | `colors.background.secondary` | #hexValue | +| ... | ... | ... | + +### Example: Border Colors + +| Figma Variable | Code Implementation | Hex Value | +| ----------------------------- | ----------------------- | --------- | +| `color/border/border-primary` | `colors.border.primary` | #hexValue | +| ... | ... | ... | + +### Example: Feedback Colors + +| Figma Variable | Code Implementation | Hex Value | +| ----------------------------------- | ------------------------------------ | --------- | +| `color/feedback/success/text` | `colors.feedback.success.text` | #hexValue | +| `color/feedback/success/background` | `colors.feedback.success.background` | #hexValue | +| `color/feedback/error/text` | `colors.feedback.error.text` | #hexValue | +| ... | ... | ... | + +### Example: Color Palette (Base Colors) + +| Figma Variable | Code Implementation | Hex Value | +| ---------------- | ------------------- | --------- | +| `color/red/50` | `palette.red._50` | #hexValue | +| `color/red/100` | `palette.red._100` | #hexValue | +| `color/blue/500` | `palette.blue._500` | #hexValue | +| ... | ... | ... | + +**Instructions:** + +- List ALL color variables from your Figma `get_variable_defs` output +- Group by category (text, background, border, feedback, etc.) +- Include the hex value for reference +- Adapt categories based on your actual Figma structure + +## 2. SPACING VARIABLE MAPPING + +| Figma Variable | Code Implementation | Value | +| -------------- | ------------------- | ----- | +| `spacing/4` | `metrics.spacing4` | 4px | +| `spacing/8` | `metrics.spacing8` | 8px | +| `spacing/16` | `metrics.spacing16` | 16px | +| ... | ... | ... | + +**Instructions:** + +- List ALL spacing values from Figma +- Format: `spacing/X` → `metrics.spacingX` +- Include pixel value for reference + +**Usage Example:** + +```typescript +// ❌ WRONG - Hardcoded +padding: 16, + +// ✅ CORRECT - Using mapped token +import { metrics } from '@/themes/metrics' +padding: metrics.spacing16, // From Figma spacing/16 +``` + +## 3. RADIUS VARIABLE MAPPING + +| Figma Variable | Code Implementation | Value | +| -------------- | ------------------- | ----- | +| `radius/4` | `metrics.radius4` | 4px | +| `radius/8` | `metrics.radius8` | 8px | +| ... | ... | ... | + +**Instructions:** + +- List ALL radius values from Figma +- Format: `radius/X` → `metrics.radiusX` + +**Usage Example:** + +```typescript +// ❌ WRONG +borderRadius: 8, + +// ✅ CORRECT +borderRadius: metrics.radius8, +``` + +## 4. ICON SIZE VARIABLE MAPPING + +| Figma Variable | Code Implementation | Value | +| -------------- | -------------------- | ----- | +| `icon-size/16` | `metrics.iconSize16` | 16px | +| `icon-size/24` | `metrics.iconSize24` | 24px | +| ... | ... | ... | + +**Instructions:** + +- List ALL icon size values from Figma +- Format: `icon-size/X` → `metrics.iconSizeX` + +## 5. TYPOGRAPHY VARIABLE MAPPING + +### Font Sizes + +| Figma Variable | Code Implementation | Value | +| ------------------------------ | ------------------- | ----- | +| `typography/font-size/text/xs` | `fontSizes.xs` | 12px | +| `typography/font-size/text/sm` | `fontSizes.sm` | 14px | +| ... | ... | ... | + +### Font Weights + +| Figma Variable | Code Implementation | Value | +| -------------------------------- | --------------------- | ----- | +| `typography/font-weight/regular` | `fontWeights.regular` | '400' | +| `typography/font-weight/medium` | `fontWeights.medium` | '500' | +| ... | ... | ... | + +### Font Family + +| Figma Variable | Code Implementation | Value | +| ------------------------ | ------------------- | ------------------ | +| `typography/font-family` | `fonts.family` | 'YourFontName' | +| Font(Regular) | `fonts.regular` | 'YourFont-Regular' | +| Font(Medium) | `fonts.medium` | 'YourFont-Medium' | +| Font(Bold) | `fonts.bold` | 'YourFont-Bold' | + +### Line Heights (if applicable) + +| Figma Variable | Code Implementation | Value | +| -------------------------------- | ------------------- | ----- | +| `typography/line-height/text/xs` | `lineHeights.xs` | 18px | +| `typography/line-height/text/sm` | `lineHeights.sm` | 20px | +| ... | ... | ... | + +**Instructions:** + +- List ALL typography variables from Figma +- Adapt font family names to match your actual fonts +- Include line heights if your design system uses them + +## 6. COMPLETE USAGE EXAMPLE + +When AI generates code from Figma using `get_code`, it should transform: + +### Figma Design Properties (Example): + +``` +Container: + - padding: spacing/16 ← Figma variable + - borderRadius: radius/8 ← Figma variable + - backgroundColor: color/background/bg-primary ← Figma variable + +Text: + - color: color/text/text-primary ← Figma variable + - fontSize: typography/font-size/text/md ← Figma variable + - fontWeight: typography/font-weight/medium ← Figma variable +``` + +### Into React Native Code: + +```typescript +import React from 'react' +import {View, Text, StyleSheet} from 'react-native' +import {colors} from '@/themes/colors' +import {metrics, fontSizes, fontWeights, fonts} from '@/themes/metrics' + +const Component = () => { + return ( + + Hello World + + ) +} + +const styles = StyleSheet.create({ + container: { + padding: metrics.spacing16, // ← Mapped from spacing/16 + borderRadius: metrics.radius8, // ← Mapped from radius/8 + backgroundColor: colors.background.primary, // ← Mapped from color/background/bg-primary + }, + text: { + color: colors.text.primary, // ← Mapped from color/text/text-primary + fontSize: fontSizes.md, // ← Mapped from typography/font-size/text/md + fontWeight: fontWeights.medium, // ← Mapped from typography/font-weight/medium + fontFamily: fonts.medium, // ← Mapped from typography/font-family (Medium) + }, +}) + +export default Component +``` + +## 7. CODE GENERATION RULES + +When generating code from Figma designs: + +1. ✅ **ALWAYS** import theme tokens at the top of the file +2. ✅ **ALWAYS** use mapped code implementation instead of Figma variable names +3. ✅ **NEVER** use hardcoded values (numbers or hex colors) +4. ✅ **ALWAYS** use StyleSheet.create() for styles - inline styles are FORBIDDEN +5. ✅ **ALWAYS** use predefined metrics from metrics.ts - check before creating new ones +6. ✅ **ALWAYS** use `isIOS` from metrics instead of `Platform.OS` +7. ✅ **ALWAYS** check this mapping table before generating code +8. 🚨 **CRITICAL:** Icon size props MUST use `metrics.iconSize24` or `metrics.iconSize16` +9. 🚨 **CRITICAL:** NEVER use inline styles like `style={{ width: 24 }}` +10. 🚨 **CRITICAL:** NEVER use responsive functions directly in styles + +### Before Generating Code Checklist: + +- [ ] Identify all Figma variables used in the design +- [ ] Map each Figma variable to code implementation using this table +- [ ] Import necessary theme modules +- [ ] **Check metrics.ts for existing values before creating new ones** +- [ ] Use theme tokens in StyleSheet.create() +- [ ] Verify no hardcoded values remain +- [ ] **Replace ALL icon size props** (`size={24}` → `size={metrics.iconSize24}`) +- [ ] **Remove ALL inline styles** - use StyleSheet.create() instead +- [ ] **Remove ALL direct responsive function calls** - use predefined metrics +- [ ] **Replace `Platform.OS` with `isIOS` from metrics** + +## 8. STYLING CONSTRAINTS + +### 8.1 Inline Styles are FORBIDDEN + +**🚨 CRITICAL: NEVER use inline styles** + +```typescript +// ❌ WRONG - NEVER use inline styles + + Hello + + +// ❌ WRONG - Even with metrics tokens + + Hello + + +// ✅ CORRECT - Always use StyleSheet.create() + + Hello + + +const styles = StyleSheet.create({ + container: { + padding: metrics.spacing16, + backgroundColor: colors.background.primary, + }, + text: { + fontSize: fontSizes.sm, + }, +}) +``` + +### 8.2 Icon Size Props Must Use Metrics + +**🚨 CRITICAL: Icon sizes MUST use metrics tokens** + +```typescript +// ❌ WRONG + + + +// ✅ CORRECT + + + +const styles = StyleSheet.create({ + icon: { + width: metrics.iconSize24, + height: metrics.iconSize24, + }, +}) +``` + +### 8.3 Metrics Reuse Rules + +**🚨 CRITICAL: Always check existing metrics before creating new ones** + +```typescript +// ❌ WRONG - Direct responsive calls +const styles = StyleSheet.create({ + container: { + padding: responsiveHeight(16), // Use metrics.spacing16 + marginTop: responsiveHeight(24), // Use metrics.spacing24 + }, +}) + +// ✅ CORRECT - Reuse predefined metrics +const styles = StyleSheet.create({ + container: { + padding: metrics.spacing16, // Reuses existing value + marginTop: metrics.spacing24, // Reuses existing value + }, +}) + +// ✅ CORRECT - Only create new metrics in metrics.ts if value doesn't exist +// In src/themes/metrics.ts: +const metrics = { + // ... existing values + homeBackgroundHeight: responsiveHeight(188), // NEW: Unique value +} +``` + +### 8.4 Platform Detection + +**🚨 CRITICAL: Use `isIOS` from metrics instead of `Platform.OS`** + +```typescript +// ❌ WRONG +import { Platform } from 'react-native' +behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + +// ✅ CORRECT +import { isIOS } from '@/themes/metrics' +behavior={isIOS ? 'padding' : 'height'} +``` + +alwaysApply: true + +```` + +**Important Notes:** +- The above is a **TEMPLATE** - populate it with YOUR actual Figma variables +- Run `get_variable_defs` first to get your complete list of variables +- Create one mapping entry for EACH variable returned from Figma +- Group variables logically by category +- Include all theming rules and styling constraints +- The `alwaysApply: true` ensures AI always references this file + +### 3.3 Complete Rule File Contents + +The complete `.cursor/rules/figma-theming-and-styling.mdc` file should include: + +1. **Variable Mapping Tables** - All Figma variables mapped to code tokens +2. **Theming System Rules** - How to use metrics, platform detection, naming conventions +3. **Styling Constraints** - No inline styles, no hardcoded values, icon size rules +4. **Code Generation Rules** - Complete checklist for generating code from Figma +5. **Special Patterns** - Feedback colors, opacity, rounded elements, composite fonts + +See the template above for the complete structure. The key additions beyond just variable mapping are: + +- ✅ **Metrics Reuse Rules**: Check existing metrics before creating new ones +- ✅ **Platform Detection**: Use `isIOS` from metrics instead of `Platform.OS` +- ✅ **Inline Style Ban**: NEVER use inline styles, always use StyleSheet.create() +- ✅ **Icon Size Constraints**: MUST use `metrics.iconSize24`, never hardcoded `size={24}` +- ✅ **No Responsive Calls**: NEVER call `responsiveHeight()` directly in styles +- ✅ **Code Generation Checklist**: Complete checklist before submitting code + +### 3.4 How to Use This Mapping + +When implementing a design from Figma: + +1. **Get Figma code** using `get_code` MCP command +2. **Identify Figma variables** in the generated code (e.g., `spacing/16`, `color/text/text-primary`) +3. **Refer to mapping table** in `.cursor/rules/figma-theming-and-styling.mdc` +4. **Replace Figma variables** with the corresponding code tokens +5. **Check existing metrics** before creating new values +6. **Remove inline styles** and use StyleSheet.create() +7. **Replace icon size props** with metrics tokens +8. **Replace Platform.OS** with `isIOS` from metrics +9. **Add proper imports** from theme files + +**Example Transformation:** + +```typescript +// ❌ BEFORE (Raw code from Figma get_code - hardcoded values) +const styles = StyleSheet.create({ + container: { + padding: 16, // This was spacing/16 in Figma + backgroundColor: '#hexValue', // This was color/background/bg-primary in Figma + borderRadius: 8, // This was radius/8 in Figma + gap: 12, // This was spacing/12 in Figma + }, + title: { + fontSize: 16, // This was typography/font-size/text/md + fontWeight: '500', // This was typography/font-weight/medium + color: '#hexValue', // This was color/text/text-primary + } +}) + +// ✅ AFTER (Mapped to your code implementation using mapping table) +import { colors } from '@/themes/colors' +import { metrics, fontSizes, fontWeights } from '@/themes/metrics' + +const styles = StyleSheet.create({ + container: { + padding: metrics.spacing16, // spacing/16 → metrics.spacing16 + backgroundColor: colors.background.primary, // bg-primary → colors.background.primary + borderRadius: metrics.radius8, // radius/8 → metrics.radius8 + gap: metrics.spacing12, // spacing/12 → metrics.spacing12 + }, + title: { + fontSize: fontSizes.md, // font-size/md → fontSizes.md + fontWeight: fontWeights.medium, // font-weight/medium → fontWeights.medium + color: colors.text.primary, // text-primary → colors.text.primary + } +}) +```` + +**Process:** + +1. Identify which Figma variable each hardcoded value represents +2. Look up the mapping in your `.cursor/rules/figma-variable-mapping.mdc` file +3. Replace with the corresponding theme token +4. Add necessary imports + +--- + +## Step 4: Implementation Checklist + +After setting up the theme system, verify the following: + +### 4.1 Colors Verification + +- [ ] All Figma colors are defined in `src/themes/colors.ts` +- [ ] Color names use camelCase convention +- [ ] Color hierarchy matches Figma structure +- [ ] Palette colors are in separate `palette` constant +- [ ] Existing color values are updated with Figma values +- [ ] `getColorOpacity` utility function is preserved + +### 4.2 Metrics Verification + +- [ ] All spacing values are defined with `responsiveHeight()` +- [ ] All radius values are defined with `responsiveHeight()` +- [ ] All icon sizes are defined +- [ ] Legacy values are maintained for compatibility +- [ ] Naming follows `spacing4`, `radius8`, `iconSize24` pattern + +### 4.3 Typography Verification + +- [ ] Font sizes defined in `fontSizes` constant +- [ ] Font weights defined in `fontWeights` constant +- [ ] Font family updated in `src/themes/fonts.ts` +- [ ] Line heights defined (if needed) +- [ ] All values use responsive functions + +### 4.4 Theme Integration + +- [ ] `src/themes/theme.ts` updated with new colors +- [ ] Component theming uses new design tokens +- [ ] Dark mode colors mapped correctly +- [ ] Backward compatibility maintained + +### 4.5 Documentation + +- [ ] Design system rules created +- [ ] Rules saved to `.cursor/rules/figma-theming-and-styling.mdc` +- [ ] Variable mapping tables complete +- [ ] Theming system rules documented +- [ ] Styling constraints defined +- [ ] Usage examples provided +- [ ] Best practices documented + +--- + +## Step 5: Usage in Components + +### 5.1 Importing Theme Values + +```typescript +// Import colors +import {colors, palette} from '@/themes/colors' + +// Import metrics, typography +import {metrics, fontSizes, fontWeights} from '@/themes/metrics' + +// Import fonts +import {fonts} from '@/themes/fonts' + +// Import complete theme +import {theme} from '@/themes' +``` + +### 5.2 Component Example + +```typescript +import React from 'react' +import {View, Text, StyleSheet} from 'react-native' +import {colors} from '@/themes/colors' +import {metrics, fontSizes, fontWeights} from '@/themes/metrics' + +const ExampleComponent = () => { + return ( + + Hello World + This uses Figma design tokens + + ) +} + +const styles = StyleSheet.create({ + container: { + backgroundColor: colors.background.primary, // Use your actual color structure + padding: metrics.spacing16, // Use your actual spacing values + borderRadius: metrics.radius8, // Use your actual radius values + gap: metrics.spacing12, // Use your actual spacing values + }, + title: { + color: colors.text.primary, // Use your actual text colors + fontSize: fontSizes.lg, // Use your actual font sizes + fontWeight: fontWeights.medium, // Use your actual font weights + }, + body: { + color: colors.text.secondary, // Use your actual text colors + fontSize: fontSizes.md, // Use your actual font sizes + fontWeight: fontWeights.regular, // Use your actual font weights + }, +}) + +export default ExampleComponent +``` + +**Note:** Replace the theme token names with your actual tokens defined in Step 2. + +--- + +## Common Patterns + +### Pattern 1: Using Color Hierarchy + +```typescript +// ✅ Correct - Use hierarchical color names + + Brand Text + + +// ❌ Avoid - Flat or hardcoded colors + + Brand Text + +``` + +### Pattern 2: Using Feedback Colors + +```typescript +// ✅ Correct - Use semantic feedback colors +const getStatusStyle = (status: 'success' | 'error' | 'warning' | 'info') => ({ + backgroundColor: colors.feedback[status].background, + color: colors.feedback[status].text, +}) + +// Usage + + Success Message + +``` + +### Pattern 3: Using Responsive Spacing + +```typescript +// ✅ Correct - Use predefined spacing tokens +const styles = StyleSheet.create({ + container: { + padding: metrics.spacing16, + gap: metrics.spacing8, + marginBottom: metrics.spacing24, + }, +}) + +// ❌ Avoid - Direct responsive calls +const styles = StyleSheet.create({ + container: { + padding: responsiveHeight(16), // Use metrics.spacing16 instead + gap: responsiveHeight(8), // Use metrics.spacing8 instead + }, +}) +``` + +### Pattern 4: Typography Composition + +```typescript +// ✅ Correct - Compose typography tokens +const textStyles = { + heading: { + fontSize: fontSizes.xl, + fontWeight: fontWeights.medium, + lineHeight: lineHeights.xl, + color: colors.text.primary, + }, + body: { + fontSize: fontSizes.md, + fontWeight: fontWeights.regular, + lineHeight: lineHeights.md, + color: colors.text.secondary, + }, +} +``` + +--- + +## Troubleshooting + +### Issue 1: Colors Not Updating + +**Problem**: Colors in the app don't reflect Figma values + +**Solution**: + +1. Clear Metro bundler cache: `npx expo start --clear` +2. Verify imports use correct paths +3. Check if old color constants are being imported +4. Restart development server + +### Issue 2: Responsive Values Not Working + +**Problem**: Spacing or fonts don't scale properly + +**Solution**: + +1. Verify `DESIGN_WIDTH` and `DESIGN_HEIGHT` match your design +2. Check that responsive functions are applied correctly +3. Ensure `Dimensions.get('window')` is used, not `screen` + +### Issue 3: Type Errors with Theme + +**Problem**: TypeScript errors when accessing theme values + +**Solution**: + +1. Ensure all constants use `as const` assertion +2. Update theme type definitions if using custom types +3. Rebuild TypeScript: `npx tsc --noEmit` + +### Issue 4: Font Not Loading + +**Problem**: Custom fonts from Figma not displaying + +**Solution**: + +1. Verify font files are in `src/assets/fonts/` +2. Check font loading in `MainLayout.tsx` +3. Ensure font names match in `fonts.ts` and `useFonts()` call +4. Run `npx expo install expo-font` if needed + +--- + +## Maintenance + +### Regular Updates + +1. **Sync with Figma**: Run Step 1 periodically to get latest design tokens +2. **Update Rules**: Regenerate design system rules when major changes occur +3. **Deprecate Old Values**: Gradually remove unused legacy values +4. **Document Changes**: Keep changelog of theme updates + +### Version Control + +```bash +# Track theme changes +git add src/themes/ +git commit -m "chore: update theme tokens from Figma design system" + +# Tag major theme updates +git tag -a theme-v2.0.0 -m "Major theme update with Figma integration" +``` + +--- + +## Summary + +This guideline ensures: + +- ✅ Consistent design token naming across the codebase +- ✅ Direct synchronization between Figma and code +- ✅ Type-safe theme system with TypeScript +- ✅ Backward compatibility with existing code +- ✅ Scalable and maintainable theme architecture +- ✅ Clear documentation for team collaboration + +By following these steps, your mobile application will maintain perfect synchronization with your Figma design system, ensuring visual consistency and reducing design-to-code discrepancies. From 96222dabbb9d87b507b98e98b56f5a012d8619c7 Mon Sep 17 00:00:00 2001 From: "hang.nguyen" Date: Thu, 2 Oct 2025 13:39:25 +0700 Subject: [PATCH 06/13] feat: add countdown code connect --- code-connect/components/Countdown.figma.tsx | 96 +++++++++++++++++++++ code-connect/figma.config.json | 3 +- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 code-connect/components/Countdown.figma.tsx diff --git a/code-connect/components/Countdown.figma.tsx b/code-connect/components/Countdown.figma.tsx new file mode 100644 index 0000000..93c4927 --- /dev/null +++ b/code-connect/components/Countdown.figma.tsx @@ -0,0 +1,96 @@ +import React from 'react' +import figma from '@figma/code-connect' +import {CountDown, theme, Typography} from 'rn-base-component' +import {StyleSheet, View} from 'react-native' +import {metrics} from 'src/helpers' + +const COUNT_DOWN_FIGMA_URL = '' + +const countdownProps = { + value: 50000, + timeToShow: ['D', 'H', 'M', 'S'], + fontSize: theme.fontSizes.lg, + fontFamily: theme.fonts.regular, + textColor: theme.colors.black, + textStyle: { + fontFamily: theme.fonts.regular, + fontSize: theme.fontSizes.lg, + color: theme.colors.black, + }, + unitTextStyle: { + fontFamily: theme.fonts.regular, + fontSize: theme.fontSizes.sm, + color: theme.colors.gray, + }, + onFinish: () => { + /* TODO: handle onFinish */ + }, +} + +figma.connect(CountDown, COUNT_DOWN_FIGMA_URL, { + variant: {type: 'clock_countdown'}, + props: { + ...countdownProps, + showLabels: true, + timeLabels: { + days: 'd', + hours: 'h', + minutes: 'm', + seconds: 's', + }, + }, + example: props => , +}) + +figma.connect(CountDown, COUNT_DOWN_FIGMA_URL, { + variant: {type: 'colon_countdown'}, + props: { + ...countdownProps, + separator: ' : ', + }, + example: props => , +}) + +figma.connect(CountDown, COUNT_DOWN_FIGMA_URL, { + variant: {type: 'large_text_with_labels'}, + props: { + ...countdownProps, + textStyle: { + fontFamily: theme.fonts.bold, + fontSize: theme.fontSizes.lg, + color: theme.colors.black, + }, + }, + example: props => , +}) + +figma.connect(CountDown, COUNT_DOWN_FIGMA_URL, { + variant: {type: 'large_text_with_labels_under'}, + props: { + ...countdownProps, + }, + example: props => ( + + + + {['days', 'hours', 'minutes', 'seconds'].map(item => ( + + {item} + + ))} + + + ), +}) + +const styles = StyleSheet.create({ + container: { + flexDirection: 'column', + alignItems: 'center', + }, + labelsContainer: { + flexDirection: 'row', + alignItems: 'center', + gap: metrics.xxs, + }, +}) diff --git a/code-connect/figma.config.json b/code-connect/figma.config.json index 44603cc..58c8a5d 100644 --- a/code-connect/figma.config.json +++ b/code-connect/figma.config.json @@ -16,7 +16,8 @@ "": "https://www.figma.com/design/JfmuXTpcFBa0xj3JzwNtxx/Saigon-Technology_MobilePortal?node-id=701-13179", "": "https://www.figma.com/design/JfmuXTpcFBa0xj3JzwNtxx/Saigon-Technology_MobilePortal?node-id=709-2582", "": "https://www.figma.com/design/JfmuXTpcFBa0xj3JzwNtxx/Saigon-Technology_MobilePortal?node-id=3-63", - "": "https://www.figma.com/design/JfmuXTpcFBa0xj3JzwNtxx/Saigon-Technology_MobilePortal?node-id=773-10315" + "": "https://www.figma.com/design/JfmuXTpcFBa0xj3JzwNtxx/Saigon-Technology_MobilePortal?node-id=773-10315", + "": "https://www.figma.com/design/JfmuXTpcFBa0xj3JzwNtxx/Saigon-Technology_MobilePortal?node-id=804-15641" } } } From 22ac185b2568c7f25925b475ee68ea5eac75aacd Mon Sep 17 00:00:00 2001 From: "hang.nguyen" Date: Thu, 16 Oct 2025 11:27:53 +0700 Subject: [PATCH 07/13] feat: update button link code connect --- code-connect/components/Button.figma.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/code-connect/components/Button.figma.tsx b/code-connect/components/Button.figma.tsx index 9f695f0..0c34f43 100644 --- a/code-connect/components/Button.figma.tsx +++ b/code-connect/components/Button.figma.tsx @@ -7,7 +7,9 @@ import { ButtonOutline, ButtonTransparent, Icon, + Text, } from 'rn-base-component' +import {StyleSheet} from 'react-native' const BUTTON_FIGMA_URL = '' const buttonProps = { @@ -34,6 +36,12 @@ const buttonProps = { }), } +const styles = StyleSheet.create({ + underline: { + textDecorationLine: 'underline', + }, +}) + figma.connect(ButtonPrimary, BUTTON_FIGMA_URL, { variant: {type: 'contained', style: 'primary'}, props: buttonProps, @@ -60,6 +68,8 @@ figma.connect(ButtonTransparent, BUTTON_FIGMA_URL, { figma.connect(Button, BUTTON_FIGMA_URL, { variant: {type: 'link'}, - props: buttonProps, - example: props =>