import { X } from "lucide-react-native";
import { useCallback, useMemo, type ReactNode } from "react";
import { Pressable, Text, View, type PressableStateCallbackType } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
export type SidebarCalloutActionVariant = "primary" | "secondary";
export interface SidebarCalloutAction {
label: string;
onPress: () => void;
variant?: SidebarCalloutActionVariant;
disabled?: boolean;
testID?: string;
}
export type SidebarCalloutVariant = "default" | "success" | "error";
export interface SidebarCalloutProps {
title?: string;
description?: ReactNode;
icon?: ReactNode;
variant?: SidebarCalloutVariant;
actions?: readonly SidebarCalloutAction[];
onDismiss?: () => void;
testID?: string;
}
export function SidebarCalloutDescriptionText({ children }: { children: ReactNode }) {
return {children};
}
export function SidebarCallout({
title,
description,
icon,
variant = "default",
actions,
onDismiss,
testID,
}: SidebarCalloutProps) {
const { theme } = useUnistyles();
const visibleActions = (actions ?? []).slice(0, 2);
const hasHeader = title != null || icon != null;
const hasDescription = description != null && description !== "";
const containerStyle = useMemo(
() => [styles.container, variant === "error" ? styles.containerError : null],
[variant],
);
return (
{hasHeader || onDismiss ? (
{hasHeader ? (
{icon ? {icon} : null}
{title ? (
{title}
) : null}
) : null}
{onDismiss ? (
{({ hovered }) => (
)}
) : null}
) : null}
{hasDescription && typeof description === "string" ? (
{description}
) : null}
{hasDescription && typeof description !== "string" ? (
{description}
) : null}
{visibleActions.length > 0 ? (
{visibleActions.map((action, index) => (
))}
) : null}
);
}
function SidebarCalloutActionButton({
action,
testID,
}: {
action: SidebarCalloutAction;
testID?: string;
}) {
const isPrimary = action.variant === "primary";
const labelStyle = useMemo(
() => [styles.actionLabel, isPrimary ? styles.actionLabelPrimary : styles.actionLabelSecondary],
[isPrimary],
);
const pressableStyle = useCallback(
({ pressed }: PressableStateCallbackType) => [
styles.actionButton,
isPrimary ? styles.actionButtonPrimary : styles.actionButtonSecondary,
pressed ? styles.actionButtonPressed : null,
action.disabled ? styles.actionButtonDisabled : null,
],
[action.disabled, isPrimary],
);
return (
{action.label}
);
}
const styles = StyleSheet.create((theme) => ({
container: {
width: "100%",
paddingVertical: theme.spacing[3],
paddingHorizontal: theme.spacing[4],
borderTopWidth: theme.borderWidth[1],
borderTopColor: theme.colors.border,
gap: theme.spacing[2],
},
containerError: {
borderTopColor: theme.colors.destructive,
},
dismissButton: {
width: 14,
height: 14,
alignItems: "center",
justifyContent: "center",
},
body: {
gap: theme.spacing[2],
},
topRow: {
flexDirection: "row",
alignItems: "flex-start",
gap: theme.spacing[2],
},
header: {
flex: 1,
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
iconSlot: {
alignItems: "center",
justifyContent: "center",
},
title: {
flexShrink: 1,
color: theme.colors.foreground,
fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.normal,
},
description: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs,
},
descriptionSlot: {
flexShrink: 1,
minWidth: 0,
gap: theme.spacing[2],
},
actionRow: {
flexDirection: "row",
gap: theme.spacing[2],
marginTop: theme.spacing[2],
},
actionButton: {
flex: 1,
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[3],
borderRadius: theme.borderRadius.md,
borderWidth: theme.borderWidth[1],
alignItems: "center",
justifyContent: "center",
},
actionButtonPrimary: {
backgroundColor: theme.colors.foreground,
borderColor: theme.colors.foreground,
},
actionButtonSecondary: {
backgroundColor: "transparent",
borderColor: theme.colors.border,
},
actionButtonPressed: {
opacity: 0.8,
},
actionButtonDisabled: {
opacity: 0.5,
},
actionLabel: {
fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.medium,
},
actionLabelPrimary: {
color: theme.colors.surface0,
},
actionLabelSecondary: {
color: theme.colors.foreground,
},
}));