/**
* 简历编辑页共用件(基本信息 / 教育背景 / 工作经历 / 项目经历四页同一套骨架)。
*
* 抽出来的理由:四页的头部、表单行、四态、页脚完全一致,逐页复制会让
* 「错误提示样式」「禁用态按钮」这类细节在四处各自漂移。
*/
import { Ionicons } from '@expo/vector-icons';
import type { ReactNode } from 'react';
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import { colors } from '../../src/theme';
/** 顶部导航:返回 + 居中标题(右侧留同宽占位,保证标题居中) */
export function EditHeader({ title, onBack }: { title: string; onBack: () => void }) {
return (
{title}
);
}
/**
* 表单行。`error` 只在**填了但填错**时传入 —— 空值是「未采集」不是「填错了」,
* 一进页面就飘红字会让人以为自己做错了什么。
*/
export function Field({
label,
value,
onChangeText,
placeholder,
keyboardType,
maxLength,
error,
hint,
multiline,
autoFocus,
required,
}: {
label: string;
value: string;
onChangeText: (v: string) => void;
placeholder: string;
keyboardType?: 'default' | 'phone-pad' | 'email-address' | 'number-pad';
maxLength?: number;
error?: string;
hint?: string;
multiline?: boolean;
autoFocus?: boolean;
/** 必填项在 label 后打一个红色星号(只做视觉标注,拦截规则在页面里) */
required?: boolean;
}) {
const hasError = error !== undefined && error !== '';
return (
{label}
{required === true && *}
{hasError && {error}}
{!hasError && hint !== undefined && hint !== '' && {hint}}
);
}
/** 只读说明行(放在卡片或页脚里,字号小于正文) */
export function FormNote({ text }: { text: string }) {
return {text};
}
/** 白色圆角卡片容器(与简历首页的卡片同款) */
export function SectionCard({ title, right, children }: { title: string; right?: ReactNode; children: ReactNode }) {
return (
{title}
{right}
{children}
);
}
/** 页脚容器:上方一条浅描边,主按钮靠它压底 */
export function PageFooter({ children }: { children: ReactNode }) {
return {children};
}
/** 居中状态块(加载中 / 加载失败 / 空列表都走这一个) */
export function CenterState({
icon,
text,
actionLabel,
onAction,
}: {
icon?: keyof typeof Ionicons.glyphMap;
text: string;
actionLabel?: string;
onAction?: () => void;
}) {
return (
{icon !== undefined && }
{text}
{actionLabel !== undefined && onAction !== undefined && (
{actionLabel}
)}
);
}
/** 列表行右侧的图标按钮(编辑 / 删除) */
export function IconAction({
icon,
onPress,
color,
label,
}: {
icon: keyof typeof Ionicons.glyphMap;
onPress: () => void;
color?: string;
/** 无障碍标签:图标按钮对读屏用户是没有名字的,必须补一个 */
label: string;
}) {
return (
);
}
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 12,
paddingVertical: 12,
backgroundColor: colors.white,
},
headerBtn: { width: 32, height: 32, alignItems: 'center', justifyContent: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
section: { marginBottom: 18 },
label: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginBottom: 10 },
requiredMark: { color: colors.danger },
input: {
height: 50,
borderRadius: 12,
borderWidth: 1,
borderColor: colors.border,
backgroundColor: colors.white,
paddingHorizontal: 14,
fontSize: 16,
color: colors.textPrimary,
},
inputMultiline: { height: 120, paddingTop: 12, textAlignVertical: 'top' },
inputError: { borderColor: colors.dangerBorder },
fieldError: { fontSize: 12, color: colors.dangerText, marginTop: 6 },
hint: { fontSize: 12, color: colors.textHint, marginTop: 6 },
note: { fontSize: 12, color: colors.textHint, lineHeight: 18 },
card: {
backgroundColor: colors.white,
borderRadius: 8,
padding: 16,
marginBottom: 12,
borderWidth: 1,
borderColor: colors.borderLight,
},
cardHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 10,
},
cardTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary },
footer: {
paddingHorizontal: 20,
paddingVertical: 14,
backgroundColor: colors.white,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
},
center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12, paddingVertical: 40 },
centerText: { fontSize: 14, color: colors.textSecondary },
centerBtn: {
paddingHorizontal: 18,
paddingVertical: 8,
borderRadius: 16,
backgroundColor: colors.primaryLight,
},
centerBtnText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
iconBtn: { width: 32, height: 32, alignItems: 'center', justifyContent: 'center' },
});