* wip * wip * wip * format * wip(mobile): lint and format * cleanup * nits * nits * idiomatic react * nit * format all root files
144 lines
3.8 KiB
TypeScript
144 lines
3.8 KiB
TypeScript
import { useState } from 'react';
|
|
import { Pressable, Text, View } from 'react-native';
|
|
import {
|
|
CircleCheckBig,
|
|
CircleDot,
|
|
Pencil,
|
|
Trash2,
|
|
Users,
|
|
} from 'lucide-react-native';
|
|
import { cn } from '@/lib/utils';
|
|
import { BottomSheet } from '@/components/BottomSheet';
|
|
|
|
export type StreamActionId =
|
|
| 'toggle-status'
|
|
| 'rename'
|
|
| 'members'
|
|
| 'edit-particle'
|
|
| 'delete-particle';
|
|
|
|
interface StreamActionsSheetProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
onSelect: (action: StreamActionId) => void;
|
|
streamStatus: 'open' | 'closed';
|
|
isCreator: boolean;
|
|
/** True when the *current* particle is a text particle this user authored. */
|
|
canEditParticle: boolean;
|
|
/** True when the *current* particle is one this user can soft-delete. */
|
|
canDeleteParticle: boolean;
|
|
}
|
|
|
|
export function StreamActionsSheet({
|
|
open,
|
|
onClose,
|
|
onSelect,
|
|
streamStatus,
|
|
isCreator,
|
|
canEditParticle,
|
|
canDeleteParticle,
|
|
}: StreamActionsSheetProps) {
|
|
// Hold the picked action until the sheet's Modal has fully unmounted, then
|
|
// dispatch. Follow-ups like rename/members open another Modal and "delete
|
|
// particle" shows an Alert — both are iOS Modals, and iOS will not present
|
|
// a second Modal while another is still on screen. Without this defer, the
|
|
// tap appears to do nothing and the app feels frozen behind a phantom
|
|
// overlay until the close animation finishes.
|
|
const [pending, setPending] = useState<StreamActionId | null>(null);
|
|
|
|
const choose = (id: StreamActionId) => {
|
|
setPending(id);
|
|
onClose();
|
|
};
|
|
|
|
const handleClosed = () => {
|
|
if (pending) {
|
|
onSelect(pending);
|
|
setPending(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<BottomSheet open={open} onClose={onClose} onClosed={handleClosed}>
|
|
<View className="py-2">
|
|
<ActionRow
|
|
icon={
|
|
streamStatus === 'open' ? (
|
|
<CircleCheckBig color="white" size={20} />
|
|
) : (
|
|
<CircleDot color="#22c55e" size={20} />
|
|
)
|
|
}
|
|
label={streamStatus === 'open' ? 'Close stream' : 'Reopen stream'}
|
|
onPress={() => choose('toggle-status')}
|
|
/>
|
|
<ActionRow
|
|
icon={<Users color="white" size={20} />}
|
|
label="Members"
|
|
onPress={() => choose('members')}
|
|
/>
|
|
{isCreator ? (
|
|
<ActionRow
|
|
icon={<Pencil color="white" size={20} />}
|
|
label="Rename stream"
|
|
onPress={() => choose('rename')}
|
|
/>
|
|
) : null}
|
|
{canEditParticle ? (
|
|
<ActionRow
|
|
icon={<Pencil color="white" size={20} />}
|
|
label="Edit particle"
|
|
onPress={() => choose('edit-particle')}
|
|
/>
|
|
) : null}
|
|
{canDeleteParticle ? (
|
|
<ActionRow
|
|
icon={<Trash2 color="#ef4444" size={20} />}
|
|
label="Delete particle"
|
|
tone="destructive"
|
|
onPress={() => choose('delete-particle')}
|
|
/>
|
|
) : null}
|
|
</View>
|
|
|
|
<View className="px-5 pt-2 pb-2">
|
|
<Pressable
|
|
onPress={onClose}
|
|
className="bg-white/10 active:bg-white/15 rounded-xl py-3 items-center"
|
|
>
|
|
<Text className="text-white text-base font-semibold">Cancel</Text>
|
|
</Pressable>
|
|
</View>
|
|
</BottomSheet>
|
|
);
|
|
}
|
|
|
|
function ActionRow({
|
|
icon,
|
|
label,
|
|
onPress,
|
|
tone = 'default',
|
|
}: {
|
|
icon: React.ReactNode;
|
|
label: string;
|
|
onPress: () => void;
|
|
tone?: 'default' | 'destructive';
|
|
}) {
|
|
return (
|
|
<Pressable
|
|
onPress={onPress}
|
|
className="px-5 py-3.5 flex-row items-center gap-3 active:bg-white/5"
|
|
>
|
|
<View className="w-6 items-center">{icon}</View>
|
|
<Text
|
|
className={cn(
|
|
'text-base',
|
|
tone === 'destructive' ? 'text-red-400' : 'text-white',
|
|
)}
|
|
>
|
|
{label}
|
|
</Text>
|
|
</Pressable>
|
|
);
|
|
}
|