@umituz/react-native-design-system 2.6.61 → 2.6.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/atoms/AtomicButton.tsx +6 -257
- package/src/atoms/AtomicChip.tsx +4 -224
- package/src/atoms/button/AtomicButton.tsx +108 -0
- package/src/atoms/button/configs/buttonSizeConfig.ts +37 -0
- package/src/atoms/button/index.ts +6 -0
- package/src/atoms/button/styles/buttonStyles.ts +36 -0
- package/src/atoms/button/styles/buttonVariantStyles.ts +88 -0
- package/src/atoms/button/types/index.ts +40 -0
- package/src/atoms/chip/AtomicChip.tsx +112 -0
- package/src/atoms/chip/configs/chipColorConfig.ts +47 -0
- package/src/atoms/chip/configs/chipSizeConfig.ts +34 -0
- package/src/atoms/chip/index.ts +6 -0
- package/src/atoms/chip/styles/chipStyles.ts +28 -0
- package/src/atoms/chip/types/index.ts +42 -0
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +17 -181
- package/src/layouts/ScreenLayout/components/ContentWrapper.tsx +31 -0
- package/src/layouts/ScreenLayout/components/index.ts +6 -0
- package/src/layouts/ScreenLayout/styles/screenLayoutStyles.ts +47 -0
- package/src/layouts/ScreenLayout/types/index.ts +27 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.62",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -1,258 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { AtomicSpinner } from './AtomicSpinner';
|
|
6
|
-
import { useAppDesignTokens } from '../theme';
|
|
7
|
-
import type { IconName } from './AtomicIcon';
|
|
1
|
+
/**
|
|
2
|
+
* AtomicButton - Re-export from button module
|
|
3
|
+
* Maintains backward compatibility
|
|
4
|
+
*/
|
|
8
5
|
|
|
9
|
-
export
|
|
10
|
-
export type
|
|
11
|
-
|
|
12
|
-
export interface AtomicButtonProps {
|
|
13
|
-
title?: string;
|
|
14
|
-
children?: React.ReactNode;
|
|
15
|
-
onPress: () => void;
|
|
16
|
-
variant?: ButtonVariant;
|
|
17
|
-
size?: ButtonSize;
|
|
18
|
-
disabled?: boolean;
|
|
19
|
-
loading?: boolean;
|
|
20
|
-
icon?: IconName;
|
|
21
|
-
iconPosition?: 'left' | 'right';
|
|
22
|
-
fullWidth?: boolean;
|
|
23
|
-
style?: StyleProp<ViewStyle>;
|
|
24
|
-
textStyle?: StyleProp<TextStyle>;
|
|
25
|
-
activeOpacity?: number;
|
|
26
|
-
testID?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const AtomicButton: React.FC<AtomicButtonProps> = React.memo(({
|
|
30
|
-
title,
|
|
31
|
-
children,
|
|
32
|
-
onPress,
|
|
33
|
-
variant = 'primary',
|
|
34
|
-
size = 'md',
|
|
35
|
-
disabled = false,
|
|
36
|
-
loading = false,
|
|
37
|
-
icon,
|
|
38
|
-
iconPosition = 'left',
|
|
39
|
-
fullWidth = false,
|
|
40
|
-
style,
|
|
41
|
-
textStyle,
|
|
42
|
-
activeOpacity = 0.8,
|
|
43
|
-
testID,
|
|
44
|
-
}) => {
|
|
45
|
-
const tokens = useAppDesignTokens();
|
|
46
|
-
|
|
47
|
-
const handlePress = () => {
|
|
48
|
-
if (!disabled && !loading) {
|
|
49
|
-
onPress();
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const isDisabled = disabled || loading;
|
|
54
|
-
|
|
55
|
-
// Size configurations
|
|
56
|
-
const sizeConfig = {
|
|
57
|
-
sm: {
|
|
58
|
-
paddingVertical: tokens.spacing.xs,
|
|
59
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
60
|
-
fontSize: tokens.typography.bodySmall.responsiveFontSize,
|
|
61
|
-
iconSize: 16 * tokens.spacingMultiplier,
|
|
62
|
-
minHeight: 32 * tokens.spacingMultiplier,
|
|
63
|
-
},
|
|
64
|
-
md: {
|
|
65
|
-
paddingVertical: tokens.spacing.sm,
|
|
66
|
-
paddingHorizontal: tokens.spacing.md,
|
|
67
|
-
fontSize: tokens.typography.bodyMedium.responsiveFontSize,
|
|
68
|
-
iconSize: 20 * tokens.spacingMultiplier,
|
|
69
|
-
minHeight: 44 * tokens.spacingMultiplier,
|
|
70
|
-
},
|
|
71
|
-
lg: {
|
|
72
|
-
paddingVertical: tokens.spacing.md,
|
|
73
|
-
paddingHorizontal: tokens.spacing.lg,
|
|
74
|
-
fontSize: tokens.typography.bodyLarge.responsiveFontSize,
|
|
75
|
-
iconSize: 24 * tokens.spacingMultiplier,
|
|
76
|
-
minHeight: 52 * tokens.spacingMultiplier,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const config = sizeConfig[size];
|
|
81
|
-
|
|
82
|
-
// Variant styles
|
|
83
|
-
const getVariantStyles = () => {
|
|
84
|
-
const baseStyle: ViewStyle = {
|
|
85
|
-
backgroundColor: tokens.colors.primary,
|
|
86
|
-
borderWidth: 0,
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const baseTextStyle: TextStyle = {
|
|
90
|
-
color: tokens.colors.textInverse,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
switch (variant) {
|
|
94
|
-
case 'primary':
|
|
95
|
-
return {
|
|
96
|
-
container: {
|
|
97
|
-
...baseStyle,
|
|
98
|
-
backgroundColor: tokens.colors.primary,
|
|
99
|
-
},
|
|
100
|
-
text: {
|
|
101
|
-
...baseTextStyle,
|
|
102
|
-
color: tokens.colors.textInverse,
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
case 'secondary':
|
|
107
|
-
return {
|
|
108
|
-
container: {
|
|
109
|
-
...baseStyle,
|
|
110
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
111
|
-
},
|
|
112
|
-
text: {
|
|
113
|
-
...baseTextStyle,
|
|
114
|
-
color: tokens.colors.textPrimary,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
case 'outline':
|
|
119
|
-
return {
|
|
120
|
-
container: {
|
|
121
|
-
...baseStyle,
|
|
122
|
-
backgroundColor: undefined,
|
|
123
|
-
borderWidth: 1,
|
|
124
|
-
borderColor: tokens.colors.border,
|
|
125
|
-
},
|
|
126
|
-
text: {
|
|
127
|
-
...baseTextStyle,
|
|
128
|
-
color: tokens.colors.textPrimary,
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
case 'text':
|
|
133
|
-
return {
|
|
134
|
-
container: {
|
|
135
|
-
...baseStyle,
|
|
136
|
-
backgroundColor: undefined,
|
|
137
|
-
},
|
|
138
|
-
text: {
|
|
139
|
-
...baseTextStyle,
|
|
140
|
-
color: tokens.colors.primary,
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
case 'danger':
|
|
145
|
-
return {
|
|
146
|
-
container: {
|
|
147
|
-
...baseStyle,
|
|
148
|
-
backgroundColor: tokens.colors.error,
|
|
149
|
-
},
|
|
150
|
-
text: {
|
|
151
|
-
...baseTextStyle,
|
|
152
|
-
color: tokens.colors.textInverse,
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
default:
|
|
157
|
-
return {
|
|
158
|
-
container: baseStyle,
|
|
159
|
-
text: baseTextStyle,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const variantStyles = getVariantStyles();
|
|
165
|
-
|
|
166
|
-
const containerStyle: StyleProp<ViewStyle> = [
|
|
167
|
-
styles.button,
|
|
168
|
-
{
|
|
169
|
-
paddingVertical: config.paddingVertical,
|
|
170
|
-
paddingHorizontal: config.paddingHorizontal,
|
|
171
|
-
minHeight: config.minHeight,
|
|
172
|
-
borderRadius: tokens.borders.radius.md,
|
|
173
|
-
},
|
|
174
|
-
variantStyles.container,
|
|
175
|
-
fullWidth ? styles.fullWidth : undefined,
|
|
176
|
-
isDisabled ? styles.disabled : undefined,
|
|
177
|
-
style,
|
|
178
|
-
];
|
|
179
|
-
|
|
180
|
-
const buttonTextStyle: StyleProp<TextStyle> = [
|
|
181
|
-
{
|
|
182
|
-
fontSize: config.fontSize,
|
|
183
|
-
fontWeight: '600',
|
|
184
|
-
},
|
|
185
|
-
variantStyles.text,
|
|
186
|
-
isDisabled ? styles.disabledText : undefined,
|
|
187
|
-
textStyle,
|
|
188
|
-
];
|
|
189
|
-
|
|
190
|
-
const buttonText = title || children;
|
|
191
|
-
const showIcon = icon;
|
|
192
|
-
const iconColor = variantStyles.text.color;
|
|
193
|
-
|
|
194
|
-
return (
|
|
195
|
-
<TouchableOpacity
|
|
196
|
-
style={containerStyle}
|
|
197
|
-
onPress={handlePress}
|
|
198
|
-
activeOpacity={activeOpacity}
|
|
199
|
-
disabled={isDisabled}
|
|
200
|
-
testID={testID}
|
|
201
|
-
>
|
|
202
|
-
<View style={[styles.content, iconPosition === 'right' && styles.rowReverse]}>
|
|
203
|
-
{loading ? (
|
|
204
|
-
<AtomicSpinner
|
|
205
|
-
size="sm"
|
|
206
|
-
color={iconColor as string}
|
|
207
|
-
style={iconPosition === 'right' ? styles.iconRight : styles.iconLeft}
|
|
208
|
-
/>
|
|
209
|
-
) : showIcon ? (
|
|
210
|
-
<AtomicIcon
|
|
211
|
-
name={icon}
|
|
212
|
-
customSize={config.iconSize}
|
|
213
|
-
customColor={iconColor as string | undefined}
|
|
214
|
-
style={iconPosition === 'right' ? styles.iconRight : styles.iconLeft}
|
|
215
|
-
/>
|
|
216
|
-
) : null}
|
|
217
|
-
|
|
218
|
-
<AtomicText style={buttonTextStyle}>
|
|
219
|
-
{buttonText}
|
|
220
|
-
</AtomicText>
|
|
221
|
-
</View>
|
|
222
|
-
</TouchableOpacity>
|
|
223
|
-
);
|
|
224
|
-
});
|
|
225
|
-
AtomicButton.displayName = 'AtomicButton';
|
|
226
|
-
|
|
227
|
-
const styles = StyleSheet.create({
|
|
228
|
-
button: {
|
|
229
|
-
alignItems: 'center',
|
|
230
|
-
justifyContent: 'center',
|
|
231
|
-
flexDirection: 'row',
|
|
232
|
-
},
|
|
233
|
-
content: {
|
|
234
|
-
flexDirection: 'row',
|
|
235
|
-
alignItems: 'center',
|
|
236
|
-
justifyContent: 'center',
|
|
237
|
-
},
|
|
238
|
-
rowReverse: {
|
|
239
|
-
flexDirection: 'row-reverse',
|
|
240
|
-
},
|
|
241
|
-
fullWidth: {
|
|
242
|
-
width: '100%',
|
|
243
|
-
},
|
|
244
|
-
disabled: {
|
|
245
|
-
opacity: 0.5,
|
|
246
|
-
},
|
|
247
|
-
disabledText: {
|
|
248
|
-
opacity: 0.7,
|
|
249
|
-
},
|
|
250
|
-
iconLeft: {
|
|
251
|
-
marginRight: 8,
|
|
252
|
-
},
|
|
253
|
-
iconRight: {
|
|
254
|
-
marginLeft: 8,
|
|
255
|
-
},
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
export type { AtomicButtonProps as ButtonProps };
|
|
6
|
+
export { AtomicButton } from './button';
|
|
7
|
+
export type { AtomicButtonProps, ButtonVariant, ButtonSize } from './button';
|
package/src/atoms/AtomicChip.tsx
CHANGED
|
@@ -1,227 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AtomicChip -
|
|
3
|
-
*
|
|
4
|
-
* Displays small tags, labels, or status indicators
|
|
5
|
-
* Theme: {{THEME_NAME}} ({{CATEGORY}} category)
|
|
6
|
-
*
|
|
7
|
-
* Atomic Design Level: ATOM
|
|
8
|
-
* Purpose: Tag and label display
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* - Category tags
|
|
12
|
-
* - Status indicators
|
|
13
|
-
* - Filter chips
|
|
14
|
-
* - Skill labels
|
|
15
|
-
* - Badge displays
|
|
2
|
+
* AtomicChip - Re-export from chip module
|
|
3
|
+
* Maintains backward compatibility
|
|
16
4
|
*/
|
|
17
5
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import { AtomicText } from './AtomicText';
|
|
21
|
-
import { AtomicIcon } from './AtomicIcon';
|
|
22
|
-
import { useAppDesignTokens } from '../theme';
|
|
23
|
-
|
|
24
|
-
// =============================================================================
|
|
25
|
-
// TYPE DEFINITIONS
|
|
26
|
-
// =============================================================================
|
|
27
|
-
|
|
28
|
-
export interface AtomicChipProps {
|
|
29
|
-
/** Text content of the chip */
|
|
30
|
-
children: React.ReactNode;
|
|
31
|
-
/** Chip variant */
|
|
32
|
-
variant?: 'filled' | 'outlined' | 'soft';
|
|
33
|
-
/** Chip size */
|
|
34
|
-
size?: 'sm' | 'md' | 'lg';
|
|
35
|
-
/** Chip color theme */
|
|
36
|
-
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
|
|
37
|
-
/** Custom background color */
|
|
38
|
-
backgroundColor?: string;
|
|
39
|
-
/** Custom text color */
|
|
40
|
-
textColor?: string;
|
|
41
|
-
/** Custom border color */
|
|
42
|
-
borderColor?: string;
|
|
43
|
-
/** Leading icon */
|
|
44
|
-
leadingIcon?: string;
|
|
45
|
-
/** Trailing icon */
|
|
46
|
-
trailingIcon?: string;
|
|
47
|
-
/** Whether the chip is clickable */
|
|
48
|
-
clickable?: boolean;
|
|
49
|
-
/** Click handler */
|
|
50
|
-
onPress?: () => void;
|
|
51
|
-
/** Whether the chip is selected */
|
|
52
|
-
selected?: boolean;
|
|
53
|
-
/** Whether the chip is disabled */
|
|
54
|
-
disabled?: boolean;
|
|
55
|
-
/** Style overrides */
|
|
56
|
-
style?: ViewStyle;
|
|
57
|
-
/** Test ID for testing */
|
|
58
|
-
testID?: string;
|
|
59
|
-
/** Active opacity for touch feedback */
|
|
60
|
-
activeOpacity?: number;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// =============================================================================
|
|
64
|
-
// COMPONENT IMPLEMENTATION
|
|
65
|
-
// =============================================================================
|
|
66
|
-
|
|
67
|
-
export const AtomicChip: React.FC<AtomicChipProps> = React.memo(({
|
|
68
|
-
children,
|
|
69
|
-
variant = 'filled',
|
|
70
|
-
size = 'md',
|
|
71
|
-
color = 'primary',
|
|
72
|
-
backgroundColor,
|
|
73
|
-
textColor,
|
|
74
|
-
borderColor,
|
|
75
|
-
leadingIcon,
|
|
76
|
-
trailingIcon,
|
|
77
|
-
clickable = false,
|
|
78
|
-
onPress,
|
|
79
|
-
selected = false,
|
|
80
|
-
disabled = false,
|
|
81
|
-
style,
|
|
82
|
-
testID,
|
|
83
|
-
activeOpacity = 0.7,
|
|
84
|
-
}) => {
|
|
85
|
-
const tokens = useAppDesignTokens();
|
|
86
|
-
|
|
87
|
-
// Size mapping
|
|
88
|
-
const sizeMap = {
|
|
89
|
-
sm: {
|
|
90
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
91
|
-
paddingVertical: tokens.spacing.xs,
|
|
92
|
-
fontSize: tokens.typography.bodySmall.responsiveFontSize,
|
|
93
|
-
iconSize: 'xs' as const
|
|
94
|
-
},
|
|
95
|
-
md: {
|
|
96
|
-
paddingHorizontal: tokens.spacing.md,
|
|
97
|
-
paddingVertical: tokens.spacing.sm,
|
|
98
|
-
fontSize: tokens.typography.bodyMedium.responsiveFontSize,
|
|
99
|
-
iconSize: 'sm' as const
|
|
100
|
-
},
|
|
101
|
-
lg: {
|
|
102
|
-
paddingHorizontal: tokens.spacing.md,
|
|
103
|
-
paddingVertical: tokens.spacing.sm,
|
|
104
|
-
fontSize: tokens.typography.bodyLarge.responsiveFontSize,
|
|
105
|
-
iconSize: 'sm' as const
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const sizeConfig = sizeMap[size];
|
|
110
|
-
|
|
111
|
-
// Color mapping - using undefined for transparent backgrounds
|
|
112
|
-
const colorMap = {
|
|
113
|
-
primary: {
|
|
114
|
-
filled: { bg: tokens.colors.primary, text: tokens.colors.onPrimary, border: tokens.colors.primary },
|
|
115
|
-
outlined: { bg: undefined, text: tokens.colors.primary, border: tokens.colors.primary },
|
|
116
|
-
soft: { bg: tokens.colors.primaryContainer, text: tokens.colors.onPrimaryContainer, border: undefined },
|
|
117
|
-
},
|
|
118
|
-
secondary: {
|
|
119
|
-
filled: { bg: tokens.colors.secondary, text: tokens.colors.onSecondary, border: tokens.colors.secondary },
|
|
120
|
-
outlined: { bg: undefined, text: tokens.colors.secondary, border: tokens.colors.secondary },
|
|
121
|
-
soft: { bg: tokens.colors.secondaryContainer, text: tokens.colors.onSecondaryContainer, border: undefined },
|
|
122
|
-
},
|
|
123
|
-
success: {
|
|
124
|
-
filled: { bg: tokens.colors.success, text: tokens.colors.onSuccess, border: tokens.colors.success },
|
|
125
|
-
outlined: { bg: undefined, text: tokens.colors.success, border: tokens.colors.success },
|
|
126
|
-
soft: { bg: tokens.colors.successContainer, text: tokens.colors.onSuccessContainer, border: undefined },
|
|
127
|
-
},
|
|
128
|
-
warning: {
|
|
129
|
-
filled: { bg: tokens.colors.warning, text: tokens.colors.onWarning, border: tokens.colors.warning },
|
|
130
|
-
outlined: { bg: undefined, text: tokens.colors.warning, border: tokens.colors.warning },
|
|
131
|
-
soft: { bg: tokens.colors.warningContainer, text: tokens.colors.onWarningContainer, border: undefined },
|
|
132
|
-
},
|
|
133
|
-
error: {
|
|
134
|
-
filled: { bg: tokens.colors.error, text: tokens.colors.onError, border: tokens.colors.error },
|
|
135
|
-
outlined: { bg: undefined, text: tokens.colors.error, border: tokens.colors.error },
|
|
136
|
-
soft: { bg: tokens.colors.errorContainer, text: tokens.colors.onErrorContainer, border: undefined },
|
|
137
|
-
},
|
|
138
|
-
info: {
|
|
139
|
-
filled: { bg: tokens.colors.info, text: tokens.colors.onInfo, border: tokens.colors.info },
|
|
140
|
-
outlined: { bg: undefined, text: tokens.colors.info, border: tokens.colors.info },
|
|
141
|
-
soft: { bg: tokens.colors.infoContainer, text: tokens.colors.onInfoContainer, border: undefined },
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const colorConfig = colorMap[color][variant];
|
|
146
|
-
|
|
147
|
-
// Apply custom colors if provided
|
|
148
|
-
const finalBackgroundColor = backgroundColor || colorConfig.bg;
|
|
149
|
-
const finalTextColor = textColor || colorConfig.text;
|
|
150
|
-
const finalBorderColor = borderColor || colorConfig.border;
|
|
151
|
-
|
|
152
|
-
// Handle disabled state
|
|
153
|
-
const isDisabled = disabled || (!clickable && !onPress);
|
|
154
|
-
const opacity = isDisabled ? 0.5 : 1;
|
|
155
|
-
|
|
156
|
-
// Handle selected state
|
|
157
|
-
const selectedStyle = selected ? {
|
|
158
|
-
borderWidth: tokens.borders.width.medium,
|
|
159
|
-
borderColor: tokens.colors.primary,
|
|
160
|
-
} : {};
|
|
161
|
-
|
|
162
|
-
const chipStyle: ViewStyle = {
|
|
163
|
-
flexDirection: 'row',
|
|
164
|
-
alignItems: 'center',
|
|
165
|
-
justifyContent: 'center',
|
|
166
|
-
paddingHorizontal: sizeConfig.paddingHorizontal,
|
|
167
|
-
paddingVertical: sizeConfig.paddingVertical,
|
|
168
|
-
backgroundColor: finalBackgroundColor,
|
|
169
|
-
borderRadius: tokens.borders.radius.xl,
|
|
170
|
-
borderWidth: variant === 'outlined' ? 1 : 0,
|
|
171
|
-
borderColor: finalBorderColor,
|
|
172
|
-
opacity,
|
|
173
|
-
...selectedStyle,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const textStyle = {
|
|
177
|
-
fontSize: sizeConfig.fontSize,
|
|
178
|
-
fontWeight: tokens.typography.medium,
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const iconColor = finalTextColor;
|
|
182
|
-
|
|
183
|
-
const content = (
|
|
184
|
-
<View style={[chipStyle, style]} testID={testID}>
|
|
185
|
-
{leadingIcon && (
|
|
186
|
-
<AtomicIcon
|
|
187
|
-
name={leadingIcon}
|
|
188
|
-
size={sizeConfig.iconSize}
|
|
189
|
-
customColor={iconColor}
|
|
190
|
-
style={{ marginRight: tokens.spacing.xs }}
|
|
191
|
-
/>
|
|
192
|
-
)}
|
|
193
|
-
<AtomicText
|
|
194
|
-
type="labelMedium"
|
|
195
|
-
color={finalTextColor}
|
|
196
|
-
style={textStyle}
|
|
197
|
-
>
|
|
198
|
-
{children}
|
|
199
|
-
</AtomicText>
|
|
200
|
-
{trailingIcon && (
|
|
201
|
-
<AtomicIcon
|
|
202
|
-
name={trailingIcon}
|
|
203
|
-
size={sizeConfig.iconSize}
|
|
204
|
-
customColor={iconColor}
|
|
205
|
-
style={{ marginLeft: tokens.spacing.xs }}
|
|
206
|
-
/>
|
|
207
|
-
)}
|
|
208
|
-
</View>
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
if (clickable && onPress && !disabled) {
|
|
212
|
-
return (
|
|
213
|
-
<TouchableOpacity onPress={onPress} activeOpacity={activeOpacity}>
|
|
214
|
-
{content}
|
|
215
|
-
</TouchableOpacity>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return content;
|
|
220
|
-
});
|
|
221
|
-
AtomicChip.displayName = 'AtomicChip';
|
|
222
|
-
|
|
223
|
-
// =============================================================================
|
|
224
|
-
// EXPORTS
|
|
225
|
-
// =============================================================================
|
|
226
|
-
|
|
227
|
-
export default AtomicChip;
|
|
6
|
+
export { AtomicChip } from './chip';
|
|
7
|
+
export type { AtomicChipProps } from './chip';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AtomicButton Component
|
|
3
|
+
* Refactored: Extracted configs, styles, and types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { StyleProp, ViewStyle, TextStyle, TouchableOpacity, View } from 'react-native';
|
|
8
|
+
import { AtomicText } from '../AtomicText';
|
|
9
|
+
import { AtomicIcon } from '../AtomicIcon';
|
|
10
|
+
import { AtomicSpinner } from '../AtomicSpinner';
|
|
11
|
+
import { useAppDesignTokens } from '../../theme';
|
|
12
|
+
import { getButtonSizeConfig } from './configs/buttonSizeConfig';
|
|
13
|
+
import { getVariantStyles } from './styles/buttonVariantStyles';
|
|
14
|
+
import { buttonStyles } from './styles/buttonStyles';
|
|
15
|
+
import type { AtomicButtonProps } from './types';
|
|
16
|
+
|
|
17
|
+
export const AtomicButton: React.FC<AtomicButtonProps> = React.memo(({
|
|
18
|
+
title,
|
|
19
|
+
children,
|
|
20
|
+
onPress,
|
|
21
|
+
variant = 'primary',
|
|
22
|
+
size = 'md',
|
|
23
|
+
disabled = false,
|
|
24
|
+
loading = false,
|
|
25
|
+
icon,
|
|
26
|
+
iconPosition = 'left',
|
|
27
|
+
fullWidth = false,
|
|
28
|
+
style,
|
|
29
|
+
textStyle,
|
|
30
|
+
activeOpacity = 0.8,
|
|
31
|
+
testID,
|
|
32
|
+
}) => {
|
|
33
|
+
const tokens = useAppDesignTokens();
|
|
34
|
+
|
|
35
|
+
const handlePress = () => {
|
|
36
|
+
if (!disabled && !loading) {
|
|
37
|
+
onPress();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const isDisabled = disabled || loading;
|
|
42
|
+
const config = getButtonSizeConfig(size, tokens);
|
|
43
|
+
const variantStyles = getVariantStyles(variant, tokens);
|
|
44
|
+
const iconColor = variantStyles.text.color;
|
|
45
|
+
|
|
46
|
+
const containerStyle: StyleProp<ViewStyle> = [
|
|
47
|
+
buttonStyles.button,
|
|
48
|
+
{
|
|
49
|
+
paddingVertical: config.paddingVertical,
|
|
50
|
+
paddingHorizontal: config.paddingHorizontal,
|
|
51
|
+
minHeight: config.minHeight,
|
|
52
|
+
borderRadius: tokens.borders.radius.md,
|
|
53
|
+
},
|
|
54
|
+
variantStyles.container,
|
|
55
|
+
fullWidth ? buttonStyles.fullWidth : undefined,
|
|
56
|
+
isDisabled ? buttonStyles.disabled : undefined,
|
|
57
|
+
style,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const buttonTextStyle: StyleProp<TextStyle> = [
|
|
61
|
+
{
|
|
62
|
+
fontSize: config.fontSize,
|
|
63
|
+
fontWeight: '600',
|
|
64
|
+
},
|
|
65
|
+
variantStyles.text,
|
|
66
|
+
isDisabled ? buttonStyles.disabledText : undefined,
|
|
67
|
+
textStyle,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const buttonText = title || children;
|
|
71
|
+
const showIcon = icon;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<TouchableOpacity
|
|
75
|
+
style={containerStyle}
|
|
76
|
+
onPress={handlePress}
|
|
77
|
+
activeOpacity={activeOpacity}
|
|
78
|
+
disabled={isDisabled}
|
|
79
|
+
testID={testID}
|
|
80
|
+
>
|
|
81
|
+
<View style={[buttonStyles.content, iconPosition === 'right' && buttonStyles.rowReverse]}>
|
|
82
|
+
{loading ? (
|
|
83
|
+
<AtomicSpinner
|
|
84
|
+
size="sm"
|
|
85
|
+
color={iconColor as string}
|
|
86
|
+
style={iconPosition === 'right' ? buttonStyles.iconRight : buttonStyles.iconLeft}
|
|
87
|
+
/>
|
|
88
|
+
) : showIcon ? (
|
|
89
|
+
<AtomicIcon
|
|
90
|
+
name={icon}
|
|
91
|
+
customSize={config.iconSize}
|
|
92
|
+
customColor={iconColor as string | undefined}
|
|
93
|
+
style={iconPosition === 'right' ? buttonStyles.iconRight : buttonStyles.iconLeft}
|
|
94
|
+
/>
|
|
95
|
+
) : null}
|
|
96
|
+
|
|
97
|
+
<AtomicText style={buttonTextStyle}>
|
|
98
|
+
{buttonText}
|
|
99
|
+
</AtomicText>
|
|
100
|
+
</View>
|
|
101
|
+
</TouchableOpacity>
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
AtomicButton.displayName = 'AtomicButton';
|
|
106
|
+
|
|
107
|
+
// Re-export types for convenience
|
|
108
|
+
export type { AtomicButtonProps, ButtonVariant, ButtonSize } from './types';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Size Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DesignTokens } from '../../../theme';
|
|
6
|
+
import type { ButtonSize, ButtonSizeConfig } from '../types';
|
|
7
|
+
|
|
8
|
+
export const getButtonSizeConfig = (
|
|
9
|
+
size: ButtonSize,
|
|
10
|
+
tokens: DesignTokens,
|
|
11
|
+
): ButtonSizeConfig => {
|
|
12
|
+
const sizeConfigs: Record<ButtonSize, ButtonSizeConfig> = {
|
|
13
|
+
sm: {
|
|
14
|
+
paddingVertical: tokens.spacing.xs,
|
|
15
|
+
paddingHorizontal: tokens.spacing.sm,
|
|
16
|
+
fontSize: tokens.typography.bodySmall.responsiveFontSize,
|
|
17
|
+
iconSize: 16 * tokens.spacingMultiplier,
|
|
18
|
+
minHeight: 32 * tokens.spacingMultiplier,
|
|
19
|
+
},
|
|
20
|
+
md: {
|
|
21
|
+
paddingVertical: tokens.spacing.sm,
|
|
22
|
+
paddingHorizontal: tokens.spacing.md,
|
|
23
|
+
fontSize: tokens.typography.bodyMedium.responsiveFontSize,
|
|
24
|
+
iconSize: 20 * tokens.spacingMultiplier,
|
|
25
|
+
minHeight: 44 * tokens.spacingMultiplier,
|
|
26
|
+
},
|
|
27
|
+
lg: {
|
|
28
|
+
paddingVertical: tokens.spacing.md,
|
|
29
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
30
|
+
fontSize: tokens.typography.bodyLarge.responsiveFontSize,
|
|
31
|
+
iconSize: 24 * tokens.spacingMultiplier,
|
|
32
|
+
minHeight: 52 * tokens.spacingMultiplier,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return sizeConfigs[size];
|
|
37
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Base Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { StyleSheet } from 'react-native';
|
|
6
|
+
|
|
7
|
+
export const buttonStyles = StyleSheet.create({
|
|
8
|
+
button: {
|
|
9
|
+
alignItems: 'center',
|
|
10
|
+
justifyContent: 'center',
|
|
11
|
+
flexDirection: 'row',
|
|
12
|
+
},
|
|
13
|
+
content: {
|
|
14
|
+
flexDirection: 'row',
|
|
15
|
+
alignItems: 'center',
|
|
16
|
+
justifyContent: 'center',
|
|
17
|
+
},
|
|
18
|
+
rowReverse: {
|
|
19
|
+
flexDirection: 'row-reverse',
|
|
20
|
+
},
|
|
21
|
+
fullWidth: {
|
|
22
|
+
width: '100%',
|
|
23
|
+
},
|
|
24
|
+
disabled: {
|
|
25
|
+
opacity: 0.5,
|
|
26
|
+
},
|
|
27
|
+
disabledText: {
|
|
28
|
+
opacity: 0.7,
|
|
29
|
+
},
|
|
30
|
+
iconLeft: {
|
|
31
|
+
marginRight: 8,
|
|
32
|
+
},
|
|
33
|
+
iconRight: {
|
|
34
|
+
marginLeft: 8,
|
|
35
|
+
},
|
|
36
|
+
});
|