Blur/roll/cascade content-swap primitives — ActionSwapButton, ActionSwapText, ActionSwapIcon — plus a multi-state cycler.
components/ui/action-swap/action-swap.tsx@lumioui/core@latestreact-native-reanimated@*| ActionSwapText | ||
| value | string | Slot identity — when this changes, the text animates. |
| children | ReactNode | |
| mode? | ActionSwapMode | Swap choreography. "cascade" needs a string child. Default "roll". |
| animation? | LegacySwapAnimation | @deprecated Use `mode`. "fade" maps to "blur". |
| className? | string | Applied to the root View AND the swapped text (text classes are inert on the View, layout classes are inert on the Text). |
| ActionSwapIcon | ||
| value | string | Slot identity — when this changes, the icon animates. |
| children | ReactNode | |
| mode? | ActionSwapMode | Swap choreography ("cascade" falls back to roll — icons are single elements). Default "roll". |
| animation? | LegacySwapAnimation | @deprecated Use `mode`. |
| className? | string | |
| ActionSwapButton | ||
| items | ActionSwapItem[] | |
| value? | string | Controlled active item id. |
| defaultValue? | string | Initial active item id (uncontrolled). Defaults to the first item. |
| onValueChange? | (value: string, item: ActionSwapItem) => void | |
| variant? | ActionSwapButtonVariant | |
| size? | ActionSwapButtonSize | |
| mode? | ActionSwapMode | Swap choreography for icon + label slots. Default "roll". |
| animation? | LegacySwapAnimation | @deprecated Use `mode`. |
| iconOnly? | boolean | Hide the label — icon slot only. Defaults to size === "icon". |
| cycle? | boolean | Advance to the next item on press. Default true. |
| disabled? | boolean | |
| className? | string | |
| ActionSwap | ||
| states | ActionSwapState[] | States to cycle through. Must have at least 2. |
| value? | number | Controlled active index. |
| defaultValue? | number | Initial active index (uncontrolled). Default 0. |
| onStateChange? | (index: number) => void | |
| variant? | "pill" | "plain" | "outline" | Visual style. Default "pill". |
| mode? | ActionSwapMode | Swap choreography for icon + label. Default "roll". |
| accessibilityLabelPrefix? | string | Accessible label prefix. Default "Swap to". |
| disabled? | boolean | |
| className? | string | |
Blur/roll/cascade content-swap primitives — ActionSwapButton, ActionSwapText, ActionSwapIcon — plus a multi-state cycler.
import { ActionSwapModes } from "@/components/ui/action-swap/variants/modes";
export function Example() {
return (
<ActionSwapModes />
);
} import * as React from "react";
import { View } from "react-native";
import { ActionSwapButton, ActionSwapText, type ActionSwapMode, type ActionSwapItem } from "../action-swap";
import { Icon } from "../../icon/icon";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
function makeItems(color: "primaryForeground" | "secondaryForeground" | "foreground"): ActionSwapItem[] {
return [
{ id: "list", label: "List", icon: <Icon name="menu" size="sm" color={color} /> },
{ id: "grid", label: "Grid", icon: <Icon name="grid" size="sm" color={color} /> },
{ id: "gallery", label: "Gallery", icon: <Icon name="image" size="sm" color={color} /> },
];
}
/** ActionSwapModes — the three swap choreographies side by side. */
export function ActionSwapModes() {
const [mode, setMode] = React.useState(0);
const labels = ["rolled up", "blurred over", "cascaded in"];
const modes: ActionSwapMode[] = ["roll", "blur", "cascade"];
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">Roll, blur and cascade swaps</Text>
<View className="flex-row flex-wrap gap-2">
<ActionSwapButton items={makeItems("secondaryForeground")} mode="roll" variant="secondary" />
<ActionSwapButton items={makeItems("foreground")} mode="blur" variant="outline" />
<ActionSwapButton items={makeItems("primaryForeground")} mode="cascade" variant="primary" />
</View>
<View className="flex-row items-center gap-2 border-t border-border pt-3">
<Text variant="muted">Status:</Text>
<ActionSwapText
value={String(mode)}
mode={modes[mode] ?? "roll"}
className="text-sm text-muted-foreground"
children={labels[mode] ?? ""}
/>
</View>
<ActionSwapButton
items={modes.map((m, i) => ({ id: String(i), label: m }))}
value={String(mode)}
onValueChange={(v) => setMode(Number(v))}
variant="ghost"
size="sm"
/>
</Glass>
</View>
);
}
Blur/roll/cascade content-swap primitives — ActionSwapButton, ActionSwapText, ActionSwapIcon — plus a multi-state cycler.
import { ActionSwapSort } from "@/components/ui/action-swap/variants/sort";
export function Example() {
return (
<ActionSwapSort />
);
} import * as React from "react";
import { View } from "react-native";
import { ActionSwap } from "../action-swap";
import { Icon } from "../../icon/icon";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
const TXNS = [
{ icon: "coffee", title: "Sakura Ramen", sub: "Today · Dining", amt: "−$29.00" },
{ icon: "tram-front", title: "Tram 28 pass", sub: "Yesterday · Transit", amt: "−$6.40" },
{ icon: "shopping-cart", title: "Mercado de Arroios", sub: "Sep 12 · Groceries", amt: "−$41.20" },
];
/** ActionSwapSort — sort-direction cycler staged above the list it sorts. */
export function ActionSwapSort() {
const [sortMode, setSortMode] = React.useState(0);
const rows = sortMode === 1 ? [...TXNS].reverse() : TXNS;
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 justify-between">
<View className="gap-0.5">
<Text variant="small" className="font-semibold">Transactions</Text>
<Text variant="muted">3 recent · Lumo Wallet</Text>
</View>
<ActionSwap
states={[
{ label: "Newest", icon: "arrow-up", accent: "primary" },
{ label: "Oldest", icon: "arrow-down", accent: "warning" },
{ label: "Reset", icon: "refresh", accent: "destructive" },
]}
value={sortMode}
onStateChange={setSortMode}
/>
</View>
<View className="gap-0.5">
{rows.map((t) => (
<View key={t.title} className="flex-row items-center gap-3 rounded-xl px-1 py-2">
<Glass interactive curvature="edge" className="h-8 w-8 items-center justify-center rounded-full">
<Icon name={t.icon as never} size="sm" color="mutedForeground" />
</Glass>
<View className="flex-1 gap-0.5">
<Text variant="small">{t.title}</Text>
<Text variant="muted" className="text-xs">{t.sub}</Text>
</View>
<Text variant="small" className="tabular-nums">{t.amt}</Text>
</View>
))}
</View>
</Glass>
</View>
);
}
Blur/roll/cascade content-swap primitives — ActionSwapButton, ActionSwapText, ActionSwapIcon — plus a multi-state cycler.
import { ActionSwapStyles } from "@/components/ui/action-swap/variants/styles";
export function Example() {
return (
<ActionSwapStyles />
);
} import * as React from "react";
import { View } from "react-native";
import { ActionSwap } from "../action-swap";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** ActionSwapStyles — pill, outline and plain treatments. */
export function ActionSwapStyles() {
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">Pill, outline and plain</Text>
<View className="gap-2 flex-row flex-wrap">
<ActionSwap variant="pill" states={[{ label: "On" }, { label: "Off" }]} />
<ActionSwap variant="outline" states={[{ label: "Active" }, { label: "Paused" }]} />
<ActionSwap variant="plain" states={[{ label: "Run" }, { label: "Stop" }]} />
</View>
</Glass>
</View>
);
}
Blur/roll/cascade content-swap primitives — ActionSwapButton, ActionSwapText, ActionSwapIcon — plus a multi-state cycler.
import { ActionSwapTextSwap } from "@/components/ui/action-swap/variants/text-swap";
export function Example() {
return (
<ActionSwapTextSwap />
);
} import * as React from "react";
import { View } from "react-native";
import { ActionSwapText } from "../action-swap";
import { Button } from "../../button/button";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** ActionSwapTextSwap — inline animated label that rolls on change. */
export function ActionSwapTextSwap() {
const [filter, setFilter] = React.useState(0);
const labels = ["newest first", "oldest first", "highest amount"];
return (
<View className="w-full gap-3">
<Glass interactive curvature="edge" className="w-full gap-3 rounded-2xl p-4">
<Text variant="small">ActionSwapText — animated label swap</Text>
<View className="flex-row items-center gap-2">
<Text>Showing:</Text>
<ActionSwapText value={String(filter)} children={labels[filter] ?? ""} />
</View>
<Button size="sm" variant="glass" onPress={() => setFilter((f) => (f + 1) % labels.length)}>
Next sort order
</Button>
</Glass>
</View>
);
}
Blur/roll/cascade content-swap primitives — ActionSwapButton, ActionSwapText, ActionSwapIcon — plus a multi-state cycler.
import { ActionSwapViewModes } from "@/components/ui/action-swap/variants/view-modes";
export function Example() {
return (
<ActionSwapViewModes />
);
} import * as React from "react";
import { View } from "react-native";
import { ActionSwap } from "../action-swap";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** ActionSwapViewModes — cycles List → Grid → Gallery on each tap. */
export function ActionSwapViewModes() {
const [viewMode, setViewMode] = React.useState(0);
const viewNames = ["List", "Grid", "Gallery"];
return (
<View className="w-full gap-3">
<Glass interactive curvature="edge" className="w-full gap-3 rounded-2xl p-4">
<Text variant="small">Porto album — 248 photos</Text>
<ActionSwap
states={[
{ label: "List", icon: "menu" },
{ label: "Grid", icon: "grid" },
{ label: "Gallery", icon: "image" },
]}
value={viewMode}
onStateChange={setViewMode}
className="w-full"
/>
<Text variant="small">{viewNames[viewMode]} view · 248 photos</Text>
</Glass>
</View>
);
}