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/Card.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

222 lines
4.9 KiB
TypeScript

import type { ReactNode } from "react";
import {
Pressable,
View,
type StyleProp,
type ViewStyle,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { Text } from "@/components/ui/Text";
import { colors, radii, shadows, space } from "@/lib/theme";
export function Card({
children,
onPress,
padded = true,
tone = "surface",
elevated = true,
style,
}: {
children: ReactNode;
onPress?: () => void;
padded?: boolean;
tone?: "surface" | "wash" | "brand";
elevated?: boolean;
style?: StyleProp<ViewStyle>;
}) {
const bg =
tone === "wash" ? colors.surfaceAlt : tone === "brand" ? colors.brandWash : colors.surface;
const base: StyleProp<ViewStyle> = [
{
backgroundColor: bg,
borderRadius: radii.card,
borderWidth: 1,
borderColor: tone === "brand" ? colors.brandBorder : colors.line,
padding: padded ? space.lg : 0,
overflow: "hidden",
},
elevated && tone === "surface" ? shadows.xs : null,
style,
];
if (!onPress) return <View style={base}>{children}</View>;
return (
<Pressable
onPress={onPress}
style={({ pressed }) => [base, pressed && { opacity: 0.92 }]}
>
{children}
</Pressable>
);
}
export function SectionHeader({
title,
action,
onAction,
style,
}: {
title: string;
action?: string;
onAction?: () => void;
style?: StyleProp<ViewStyle>;
}) {
return (
<View
style={[
{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: space.md,
},
style,
]}
>
<Text variant="h2">{title}</Text>
{action ? (
<Pressable onPress={onAction} hitSlop={8}>
<Text variant="label" color={colors.brandText}>
{action}
</Text>
</Pressable>
) : null}
</View>
);
}
export function Section({
title,
action,
onAction,
children,
style,
}: {
title?: string;
action?: string;
onAction?: () => void;
children: ReactNode;
style?: StyleProp<ViewStyle>;
}) {
return (
<View style={[{ marginTop: space["2xl"] }, style]}>
{title ? <SectionHeader title={title} action={action} onAction={onAction} /> : null}
{children}
</View>
);
}
export function Divider({ style }: { style?: StyleProp<ViewStyle> }) {
return <View style={[{ height: 1, backgroundColor: colors.line }, style]} />;
}
/** Key/value row used for drug facts and order summaries. */
export function InfoRow({
label,
value,
valueColor,
emphasis,
last,
}: {
label: string;
value: string;
valueColor?: string;
emphasis?: boolean;
last?: boolean;
}) {
return (
<View
style={{
flexDirection: "row",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 16,
paddingVertical: 12,
borderBottomWidth: last ? 0 : 1,
borderBottomColor: colors.line,
}}
>
<Text variant="bodySm" color={colors.muted}>
{label}
</Text>
<Text
variant={emphasis ? "h3" : "label"}
color={valueColor ?? colors.ink}
style={{ flexShrink: 1, textAlign: "right" }}
>
{value}
</Text>
</View>
);
}
/** Tappable settings/menu row with icon tile, title and chevron. */
export function MenuRow({
icon,
title,
subtitle,
onPress,
right,
tone = "brand",
last,
}: {
icon: keyof typeof Ionicons.glyphMap;
title: string;
subtitle?: string;
onPress?: () => void;
right?: ReactNode;
tone?: "brand" | "danger" | "neutral";
last?: boolean;
}) {
const iconBg =
tone === "danger"
? colors.dangerSoft
: tone === "neutral"
? colors.surfaceAlt
: colors.brandSoft;
const iconFg =
tone === "danger" ? colors.danger : tone === "neutral" ? colors.muted : colors.brandText;
return (
<Pressable
onPress={onPress}
style={({ pressed }) => ({
flexDirection: "row",
alignItems: "center",
gap: 14,
paddingVertical: 14,
paddingHorizontal: space.lg,
borderBottomWidth: last ? 0 : 1,
borderBottomColor: colors.line,
backgroundColor: pressed ? colors.surfaceAlt : "transparent",
})}
>
<View
style={{
width: 38,
height: 38,
borderRadius: 12,
backgroundColor: iconBg,
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name={icon} size={19} color={iconFg} />
</View>
<View style={{ flex: 1 }}>
<Text variant="h3" color={tone === "danger" ? colors.danger : colors.ink}>
{title}
</Text>
{subtitle ? (
<Text variant="caption" color={colors.subtle} style={{ marginTop: 2 }}>
{subtitle}
</Text>
) : null}
</View>
{right ?? <Ionicons name="chevron-forward" size={18} color={colors.subtle} />}
</Pressable>
);
}