This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-Mob/components/ui/Field.tsx
kirukib 7310a349c5 Add Gishen mobile app with complete demo shopping and care flows.
Ship Expo Router screens, seeded stores, and polished UX so auth, shop, checkout, prescriptions, loyalty, family, and account processes work end-to-end offline.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 01:21:32 +03:00

233 lines
5.6 KiB
TypeScript

import { useState, type ReactNode } from "react";
import { Ionicons } from "@expo/vector-icons";
import {
Pressable,
Switch,
TextInput,
View,
type TextInputProps,
} from "react-native";
import { Text } from "@/components/ui/Text";
import { colors, fonts, radii } from "@/lib/theme";
type FieldProps = TextInputProps & {
label?: string;
icon?: keyof typeof Ionicons.glyphMap;
error?: string;
hint?: string;
secure?: boolean;
};
export function Field({
label,
icon,
error,
hint,
secure,
style,
...rest
}: FieldProps) {
const [focused, setFocused] = useState(false);
const [hidden, setHidden] = useState(!!secure);
const borderColor = error ? colors.danger : focused ? colors.brand : colors.line;
return (
<View>
{label ? (
<Text variant="label" color={colors.muted} style={{ marginBottom: 8 }}>
{label}
</Text>
) : null}
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 10,
backgroundColor: colors.surface,
borderRadius: radii.md,
borderWidth: 1.5,
borderColor,
paddingHorizontal: 14,
height: 52,
}}
>
{icon ? (
<Ionicons name={icon} size={18} color={focused ? colors.brandText : colors.subtle} />
) : null}
<TextInput
placeholderTextColor={colors.subtle}
secureTextEntry={hidden}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
style={[
{
flex: 1,
fontFamily: fonts.regular,
fontSize: 14,
color: colors.ink,
paddingVertical: 0,
},
style,
]}
{...rest}
/>
{secure ? (
<Pressable onPress={() => setHidden(!hidden)} hitSlop={8}>
<Ionicons
name={hidden ? "eye-off-outline" : "eye-outline"}
size={18}
color={colors.subtle}
/>
</Pressable>
) : null}
</View>
{error ? (
<View style={{ flexDirection: "row", alignItems: "center", gap: 5, marginTop: 6 }}>
<Ionicons name="alert-circle" size={13} color={colors.danger} />
<Text variant="caption" color={colors.danger}>
{error}
</Text>
</View>
) : hint ? (
<Text variant="caption" color={colors.subtle} style={{ marginTop: 6 }}>
{hint}
</Text>
) : null}
</View>
);
}
export function SwitchRow({
title,
subtitle,
value,
onValueChange,
icon,
last,
}: {
title: string;
subtitle?: string;
value: boolean;
onValueChange: (v: boolean) => void;
icon?: keyof typeof Ionicons.glyphMap;
last?: boolean;
}) {
return (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 14,
paddingVertical: 14,
paddingHorizontal: 16,
borderBottomWidth: last ? 0 : 1,
borderBottomColor: colors.line,
}}
>
{icon ? (
<View
style={{
width: 38,
height: 38,
borderRadius: 12,
backgroundColor: colors.brandSoft,
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name={icon} size={19} color={colors.brandText} />
</View>
) : null}
<View style={{ flex: 1 }}>
<Text variant="h3">{title}</Text>
{subtitle ? (
<Text variant="caption" color={colors.subtle} style={{ marginTop: 2 }}>
{subtitle}
</Text>
) : null}
</View>
<Switch
value={value}
onValueChange={onValueChange}
trackColor={{ false: colors.line, true: colors.brand }}
thumbColor={colors.surface}
ios_backgroundColor={colors.line}
/>
</View>
);
}
/** Radio-style selectable row used for payment methods and addresses. */
export function OptionRow({
title,
subtitle,
selected,
onPress,
icon,
trailing,
last,
}: {
title: string;
subtitle?: string;
selected: boolean;
onPress: () => void;
icon?: keyof typeof Ionicons.glyphMap;
trailing?: ReactNode;
last?: boolean;
}) {
return (
<Pressable
onPress={onPress}
style={{
flexDirection: "row",
alignItems: "center",
gap: 14,
paddingVertical: 14,
paddingHorizontal: 16,
borderBottomWidth: last ? 0 : 1,
borderBottomColor: colors.line,
backgroundColor: selected ? colors.brandWash : "transparent",
}}
>
{icon ? (
<View
style={{
width: 38,
height: 38,
borderRadius: 12,
backgroundColor: selected ? colors.brandSoft : colors.surfaceAlt,
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons
name={icon}
size={19}
color={selected ? colors.brandText : colors.muted}
/>
</View>
) : null}
<View style={{ flex: 1 }}>
<Text variant="h3">{title}</Text>
{subtitle ? (
<Text variant="caption" color={colors.subtle} style={{ marginTop: 2 }}>
{subtitle}
</Text>
) : null}
</View>
{trailing}
<View
style={{
width: 22,
height: 22,
borderRadius: 11,
borderWidth: selected ? 7 : 1.5,
borderColor: selected ? colors.brand : colors.line,
backgroundColor: colors.surface,
}}
/>
</Pressable>
);
}