Files
llink/js/mobile/src/features/stream-view/StreamActionsSheet.tsx
T
Arjun PatelandGitHub a8a0b7db1b infra: add linting and formatting for js projects (#230)
* wip

* wip

* wip

* format

* wip(mobile): lint and format

* cleanup

* nits

* nits

* idiomatic react

* nit

* format all root files
2026-06-02 07:44:24 -07:00

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>
);
}