Segmented row of related actions with shared borders.
components/ui/button-group/button-group.tsx@lumioui/core@latest| attached? | boolean | Attach buttons together (shared borders, merged radius). Default true. |
| orientation? | "horizontal" | "vertical" | Orientation of the group. Default "horizontal". |
| disabled? | boolean | Disable every child in the group. |
| className? | string | |
| itemClassName? | string | Extra classes merged into each child element that accepts className. |
| children? | React.ReactNode |
Segmented row of related actions with shared borders.
import { ButtonGroupActions } from "@/components/ui/button-group/variants/actions";
export function Example() {
return (
<ButtonGroupActions />
);
} import * as React from "react";
import { View } from "react-native";
import { ButtonGroup } from "../button-group";
import { Button } from "../../button/button";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** ButtonGroupActions — detached cancel/confirm pair for order checkout. */
export function ButtonGroupActions() {
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">Order #8421 · Sakura Ramen</Text>
<ButtonGroup className="w-full">
<Button variant="glass" className="flex-1">Cancel Order</Button>
<Button variant="glass" className="flex-1">Place Order · $18.40</Button>
</ButtonGroup>
</Glass>
</View>
);
}
Segmented row of related actions with shared borders.
import { ButtonGroupSlots } from "@/components/ui/button-group/variants/slots";
export function Example() {
return (
<ButtonGroupSlots />
);
} import * as React from "react";
import { View } from "react-native";
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from "react-native-reanimated";
import { spring, useReducedMotion } from "@lumioui/core";
import { ButtonGroup } from "../button-group";
import { Button } from "../../button/button";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** ButtonGroupSlots — segment picker for delivery time slots. A glossy
* indicator glides across a soft muted track (modern segmented idiom). */
export function ButtonGroupSlots() {
const [slot, setSlot] = React.useState("12:15");
const slots = ["11:30", "12:15", "13:00"];
const idx = Math.max(0, slots.indexOf(slot));
const reduceMotion = useReducedMotion();
const [trackW, setTrackW] = React.useState(0);
// Track padding is p-1 (4px) — the glider insets to match.
const seg = (trackW - 8) / slots.length;
const glider = useSharedValue(idx);
React.useEffect(() => {
glider.value = reduceMotion ? idx : withSpring(idx, spring.indicator);
}, [idx, reduceMotion, glider]);
const indStyle = useAnimatedStyle(() => ({
transform: [{ translateX: glider.value * seg + 4 }],
}));
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">Delivery for Tue, Sep 15 · Sakura Ramen</Text>
<Glass interactive
curvature="edge"
className="relative w-full rounded-full p-1"
onLayout={(e) => setTrackW(e.nativeEvent.layout.width)}
>
{/* Gliding indicator — a real glossy Button face, decorative only,
riding behind the ghost segments. */}
{seg > 0 ? (
<Animated.View
pointerEvents="none"
accessibilityElementsHidden
importantForAccessibility="no-hide-descendants"
style={[
{ position: "absolute", top: 4, bottom: 4, left: 0, width: seg - 8 },
indStyle,
]}
>
<Button
variant="default"
className="h-full w-full"
pressedScale={1}
accessibilityElementsHidden
importantForAccessibility="no-hide-descendants"
/>
</Animated.View>
) : null}
<ButtonGroup className="w-full" itemClassName="border-0">
{slots.map((s) => (
<Button
key={s}
variant="ghost"
onPress={() => setSlot(s)}
className="flex-1"
textClassName={slot === s ? "text-primary-foreground" : undefined}
>
{s}
</Button>
))}
</ButtonGroup>
</Glass>
<Text variant="muted">Arrives in 25–35 min · $3.90 fee</Text>
</Glass>
</View>
);
}