import { useState } from "react";
import { Text, View } from "react-native";
import { Button } from "@/component-lib/button";
import { Card, CardBody, CardFooter, CardHeader, Stat, trendDirection } from "@/component-lib/card";
import { useKitTheme } from "@/component-lib/shared/native";
import { Row, Section } from "./layout"; // Section and Row: page layout for the demo
export function CardDemo() {
const { tokens } = useKitTheme();
const [presses, setPresses] = useState(0);
return (
<>
<Section title="Card · variants">
{(["outlined", "elevated", "plain"] as const).map((variant) => (
<Card key={variant} variant={variant}>
<CardHeader
title={`${variant[0].toUpperCase()}${variant.slice(1)} card`}
description="Header, body and footer share the card's padding."
actions={<Button size="sm" variant="ghost">Edit</Button>}
/>
<CardBody>The body is where the content goes.</CardBody>
<CardFooter>
<Button size="sm" variant="outline">Cancel</Button>
<Button size="sm">Save</Button>
</CardFooter>
</Card>
))}
</Section>
<Section title="Card · padding and press">
<Row>
{(["none", "sm", "md", "lg"] as const).map((padding) => (
<Card key={padding} padding={padding}>
<CardBody>{padding}</CardBody>
</Card>
))}
</Row>
<Card onPress={() => setPresses((n) => n + 1)} aria-label="Open invoice">
<CardHeader title="Pressable card" description={`Pressed ${presses}×`} />
<CardBody>The whole card is one target.</CardBody>
</Card>
</Section>
<Section title="Stat">
<View style={{ flexDirection: "row", gap: 10 }}>
<Card style={{ flex: 1 }}>
<CardBody>
<Stat
label="Revenue"
value="₹12.4L"
trend={{ direction: trendDirection(12.4), label: "12.4%", description: "vs last month" }}
/>
</CardBody>
</Card>
<Card style={{ flex: 1 }}>
<CardBody>
<Stat
label="Churn"
value="3.1%"
icon={<Text style={{ color: tokens.muted }}>◔</Text>}
trend={{ direction: "up", label: "0.4%", invert: true }}
help="Lower is better"
/>
</CardBody>
</Card>
</View>
<View style={{ flexDirection: "row", gap: 10 }}>
<Card style={{ flex: 1 }}>
<CardBody>
<Stat label="Orders" value="1,204" trend={{ direction: "flat", label: "0%" }} />
</CardBody>
</Card>
<Card style={{ flex: 1 }}>
<CardBody>
<Stat label="Loading" value="—" loading />
</CardBody>
</Card>
</View>
</Section>
</>
);
}Install
npx @grampro/kit@native add CardUsage
import { Card, CardHeader, CardBody, CardFooter, Stat } from "@/component-lib/card";
<Card variant="elevated" onPress={openInvoice}>
<CardHeader title="INV-0042" description="Due 12 Nov" />
<CardBody>₹12,400</CardBody>
</Card>Props
CardProps
| Prop | Type | Description |
|---|---|---|
children | ReactNode | |
variant | CardVariant | Default outlined. |
padding | CardPadding | Padding applied to the header, body and footer. Default md. |
onPress | (event: GestureResponderEvent) => void | Makes the whole card pressable (the web's href). Keep other pressable content out of it: a button inside a pressable card is two targets in one. |
interactive | boolean | Darkens the card while pressed. On by default when onPress is set. |
style | StyleProp<ViewStyle> | ((state: CardStyleState) => StyleProp<ViewStyle>) | |
styles | Partial<{ root: StyleProp<ViewStyle> }> |
Also accepts the props of Omit<ViewProps, "children" | "style">.
CardHeaderProps
| Prop | Type | Description |
|---|---|---|
title | ReactNode | The heading. Announced as a heading by screen readers. |
description | ReactNode | A line under the heading, for context the title cannot carry. |
actions | ReactNode | Buttons or a menu, aligned to the end of the header. |
children | ReactNode | |
style | StyleProp<ViewStyle> | |
styles | Partial<{ header: StyleProp<ViewStyle>; title: StyleProp<TextStyle>; description: Style… |
StatProps
| Prop | Type | Description |
|---|---|---|
label (required) | ReactNode | What the figure is. Always visible — a number alone means nothing. |
value (required) | ReactNode | |
trend | StatTrend | The change beside the figure. Left out, no arrow is shown. |
help | ReactNode | A note under the figure. |
icon | ReactNode | An icon beside the label, to identify the metric at a glance. |
loading | boolean | Shows placeholder bars instead of the figure. |
style | StyleProp<ViewStyle> | |
styles | Partial<{ root: StyleProp<ViewStyle>; label: StyleProp<TextStyle>; value: StyleProp<Tex… |
Native and web
onPressreplaceshref; the whole card becomes one button. Elevated cards use theraisedtoken, lighter in dark mode.- Web version: Card & Stat on the web.