Files
llink/js/desktop/src/features/compose/configure-container-step.tsx
T
Arjun PatelandGitHub 095b9876f9 feat: stream list view and tasks (#279)
* first attempt at stream sidebar, tasks, and events

* fix folder from root

* cleanup folders and events, and condense changes

* cleanup and add toggle for sidebar

* cleanup

* fix nits
2026-06-12 12:26:49 -07:00

180 lines
6.1 KiB
TypeScript

import { useState, useCallback } from 'react';
import { useNetworks } from '@/hooks/use-networks';
import { cn, removeDuplicates } from '@/lib/utils';
import { metaKey } from '@/lib/platform';
import { useAuthStore } from '@/stores/auth-store';
import { generateRandomName } from '@/lib/random-name';
import { Input } from '@/components/ui/input';
import { KeyHint } from '@/components/key-hint';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { ScrollArea } from '@/components/ui/scroll-area';
interface ConfigureContainerStepProps {
/** Drives labels and hints; the form is identical for both kinds. */
kind?: 'stream' | 'folder';
networkId: string | null;
onCancel: () => void;
onSubmit: (name: string, visibleTo: string[]) => void;
}
export function ConfigureContainerStep({
kind = 'stream',
networkId,
onCancel,
onSubmit,
}: ConfigureContainerStepProps) {
const { data: networks } = useNetworks();
const network = networks?.find((n) => n.id === networkId);
const [name, setName] = useState(() => generateRandomName());
const [everyone, setEveryone] = useState(true);
const userId = useAuthStore((s) => s.user?.id);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
const members = (network?.humans ?? []).filter((h) => h.id !== userId);
const toggleMember = useCallback((id: string) => {
setSelectedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
}, []);
const buildVisibleTo = useCallback((): string[] => {
if (everyone && networkId) return [`network:${networkId}`];
return Array.from(
removeDuplicates([...selectedIds, userId].filter(Boolean) as string[]),
).map((id) => `human:${id}`);
}, [everyone, networkId, selectedIds, userId]);
const handleSubmit = useCallback(() => {
if (!name.trim() || !networkId) return;
onSubmit(name.trim(), buildVisibleTo());
}, [name, networkId, onSubmit, buildVisibleTo]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
switch (e.key) {
case 'Escape':
e.preventDefault();
onCancel();
return;
case 'Enter':
if (e.metaKey || e.ctrlKey) {
e.preventDefault();
handleSubmit();
}
return;
}
},
[onCancel, handleSubmit],
);
return (
<div
className="absolute inset-0 z-50 flex flex-col bg-black/90 pt-16"
onKeyDown={handleKeyDown}
tabIndex={-1}
>
<div className="mx-auto w-full max-w-sm space-y-4 px-6">
<div>
<Label className="mb-1 text-xs text-white/50">
{kind === 'folder' ? 'Folder name' : 'Stream name'}
</Label>
<Input
type="text"
autoFocus
value={name}
onChange={(e) => {
setName(e.target.value);
}}
placeholder="Give it a name..."
className="border-white/10 bg-white/5 text-white placeholder-white/30 focus-visible:border-white/30 focus-visible:ring-0"
/>
</div>
{/* Visibility */}
<div>
<Label className="mb-1 text-xs text-white/50">Visible to</Label>
<div className="rounded-md border border-white/10">
{/* Everyone in network */}
<div
role="button"
onClick={() => setEveryone((prev) => !prev)}
className={cn(
'flex w-full cursor-pointer items-center gap-2.5 px-3 py-2 text-left text-sm transition-colors',
'text-white/70 hover:bg-white/5',
)}
>
<Checkbox
checked={everyone}
onCheckedChange={(checked) => setEveryone(checked === true)}
tabIndex={-1}
className="pointer-events-none"
/>
<span className="font-medium">Everyone in network</span>
</div>
{/* Per-member selection */}
{!everyone && members.length > 0 && (
<ScrollArea className="max-h-48">
<div className="space-y-0.5 p-1">
{members.map((member) => {
const isSelected = selectedIds.has(member.id);
const initials = member.email_prefix
.slice(0, 2)
.toUpperCase();
return (
<div
key={member.id}
role="button"
onClick={() => toggleMember(member.id)}
className={cn(
'flex w-full cursor-pointer items-center gap-2.5 rounded px-3 py-1.5 text-left text-sm transition-colors',
'text-white/70 hover:bg-white/5',
)}
>
<Checkbox
checked={isSelected}
tabIndex={-1}
className="pointer-events-none"
/>
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-white/10 text-[10px] font-medium">
{initials}
</span>
<span className="flex-1 truncate">
{member.email_prefix}
</span>
</div>
);
})}
</div>
</ScrollArea>
)}
</div>
</div>
</div>
{/* Keyboard hints */}
<div className="absolute bottom-8 left-0 right-0 flex items-center justify-center gap-4 text-sm text-white/50">
<KeyHint keys="Esc" onClick={onCancel} title="Cancel (or press Esc)">
cancel
</KeyHint>
<KeyHint
keys={`${metaKey}+Enter`}
onClick={handleSubmit}
title={`Create ${kind} (or press ${metaKey}+Enter)`}
>
create
</KeyHint>
</div>
</div>
);
}