Button that morphs label into spinner, check and error shake.
components/ui/stateful-button/stateful-button.tsx@lumioui/core@latestreact-native-reanimated@*react-native-svg@*| children | string | Label text (idle state). |
| state? | ButtonState | Current state — drive it from your async handler, or let the button track it. |
| onPress? | (e: GestureResponderEvent) => void | Promise<unknown> | Called on press; return a promise and the button tracks idle → loading → success/error automatically. |
| loadingLabel? | string | Label shown while loading. Default: "Loading". |
| successLabel? | string | Label shown on success. Default: "Done". |
| errorLabel? | string | Label shown on error. Default: original label. |
| variant? | "default" | "destructive" | "outline" | "tinted" | Visual style. |
| size? | "sm" | "md" | "lg" | Size preset. |
| resetAfter? | number | Auto-reset to idle after success. Default 1400ms; 0 keeps success. |
| className? | string | Extra classes for the container. |
| textClassName? | string |
Button that morphs label into spinner, check and error shake.
import { StatefulAuto } from "@/components/ui/stateful-button/variants/auto";
export function Example() {
return (
<StatefulAuto />
);
} import * as React from "react";
import { View } from "react-native";
import { StatefulButton } from "../stateful-button";
import { Icon } from "../../icon/icon";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** StatefulAuto — async press auto-tracked idle → loading → success → idle,
* staged as a pickup order card. */
export function StatefulAuto() {
const placeOrder = async () => {
await new Promise((r) => setTimeout(r, 1200));
};
return (
<View className="w-full gap-3">
<Glass interactive curvature="edge" className="w-full gap-3 rounded-2xl p-4">
<View className="flex-row items-center gap-3">
<Glass interactive curvature="edge" className="h-10 w-10 items-center justify-center rounded-full">
<Icon name="coffee" size="md" color="mutedForeground" />
</Glass>
<View className="flex-1">
<Text variant="small" className="font-semibold">Sakura Ramen</Text>
<Text variant="muted" className="text-xs">2 items · pickup at 6:45 PM</Text>
</View>
<Text variant="small" className="tabular-nums">$90.90</Text>
</View>
<StatefulButton
onPress={placeOrder}
loadingLabel="Placing order…"
successLabel="Order placed"
className="w-full"
>
Place Order · $90.90
</StatefulButton>
<Text variant="muted" className="text-xs">Pickup at Sakura Ramen · ready in ~20 min</Text>
</Glass>
</View>
);
}
Button that morphs label into spinner, check and error shake.
import { StatefulControlled } from "@/components/ui/stateful-button/variants/controlled";
export function Example() {
return (
<StatefulControlled />
);
} import * as React from "react";
import { View } from "react-native";
import { StatefulButton } from "../stateful-button";
import { Button } from "../../button/button";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** StatefulControlled — drive the state machine externally via the state prop. */
export function StatefulControlled() {
const [state, setState] = React.useState<"idle" | "loading" | "success" | "error">("idle");
return (
<View className="w-full gap-3">
<Glass interactive curvature="edge" className="w-full gap-3 rounded-2xl p-4">
<Text variant="small" className="font-semibold">Transfer to Diego Ruiz</Text>
<View className="flex-row gap-2 flex-wrap">
<Button size="sm" variant="glass" onPress={() => setState("loading")}>Loading</Button>
<Button size="sm" variant="glass" onPress={() => setState("success")}>Success</Button>
<Button size="sm" variant="glass" onPress={() => setState("error")}>Error</Button>
<Button size="sm" variant="glass" onPress={() => setState("idle")}>Reset</Button>
</View>
<StatefulButton state={state} resetAfter={0} className="w-full">
Send $86.00 to Diego
</StatefulButton>
</Glass>
</View>
);
}
Button that morphs label into spinner, check and error shake.
import { StatefulError } from "@/components/ui/stateful-button/variants/error";
export function Example() {
return (
<StatefulError />
);
} import * as React from "react";
import { View } from "react-native";
import { StatefulButton } from "../stateful-button";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** StatefulError — failing async press: idle → loading → error → idle. */
export function StatefulError() {
const topUpFails = async () => {
await new Promise((r) => setTimeout(r, 1200));
throw new Error("card declined");
};
return (
<View className="w-full gap-3">
<Glass interactive curvature="edge" className="w-full gap-3 rounded-2xl p-4">
<View className="flex-row items-baseline justify-between">
<Text variant="small" className="font-semibold">Lumo Wallet</Text>
<Text variant="muted">Balance $4,280.50</Text>
</View>
<StatefulButton
onPress={topUpFails}
loadingLabel="Topping up…"
errorLabel="Top-up failed"
className="w-full"
>
Top Up $50
</StatefulButton>
<Text variant="muted">The simulated top-up declines — watch the error cycle</Text>
</Glass>
</View>
);
}
Button that morphs label into spinner, check and error shake.
import { StatefulVariants } from "@/components/ui/stateful-button/variants/variants";
export function Example() {
return (
<StatefulVariants />
);
} import * as React from "react";
import { View } from "react-native";
import { StatefulButton } from "../stateful-button";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** StatefulVariants — outline and tinted treatments of the stateful button. */
export function StatefulVariants() {
const placeOrder = async () => {
await new Promise((r) => setTimeout(r, 1200));
};
return (
<View className="w-full gap-3">
<Glass interactive curvature="edge" className="w-full gap-3 rounded-2xl p-4">
<Text variant="small" className="font-semibold">Variants</Text>
<StatefulButton
variant="outline"
onPress={placeOrder}
loadingLabel="Placing order…"
successLabel="Order placed"
className="w-full"
>
Place Order · $90.90
</StatefulButton>
<StatefulButton
variant="tinted"
onPress={placeOrder}
loadingLabel="Placing order…"
successLabel="Order placed"
className="w-full"
>
Place Order · $90.90
</StatefulButton>
</Glass>
</View>
);
}