From cb1b56f5a0eddbf77446f415f2beda57c8305f85 Mon Sep 17 00:00:00 2001 From: polwex Date: Sun, 23 Nov 2025 01:12:53 +0700 Subject: wut --- .../tweetdeck/src/components/AddColumnModal.tsx | 234 +++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 packages/tweetdeck/src/components/AddColumnModal.tsx (limited to 'packages/tweetdeck/src/components/AddColumnModal.tsx') diff --git a/packages/tweetdeck/src/components/AddColumnModal.tsx b/packages/tweetdeck/src/components/AddColumnModal.tsx new file mode 100644 index 0000000..b7098ed --- /dev/null +++ b/packages/tweetdeck/src/components/AddColumnModal.tsx @@ -0,0 +1,234 @@ +import { useEffect, useMemo, useState } from "react"; +import type { DeckAccount, DeckColumn, DeckListsCache } from "../types/app"; +import type { TwitterList } from "../lib/fetching/types"; + +interface AddColumnModalProps { + accounts: DeckAccount[]; + isOpen: boolean; + onClose: () => void; + onAdd: (column: Omit) => void; + activeAccountId?: string; + fetchLists: (accountId: string) => Promise; + listsCache: DeckListsCache; +} + +const columnOptions = [ + { + id: "foryou", + label: "For You", + description: "Twitter's AI-ranked firehose", + }, + { + id: "following", + label: "Following", + description: "Chronological feed of people you follow", + }, + { id: "bookmarks", label: "Bookmarks", description: "Your saved deep dives" }, + { id: "list", label: "List", description: "Curate a topic-specific stream" }, + { id: "chat", label: "Chat", description: "Mentions & notifications" }, +] as const; + +export function AddColumnModal({ + accounts, + activeAccountId, + isOpen, + onClose, + onAdd, + fetchLists, + listsCache, +}: AddColumnModalProps) { + const [kind, setKind] = useState("foryou"); + const [accountId, setAccountId] = useState( + activeAccountId || accounts[0]?.id || "", + ); + const [title, setTitle] = useState("For You"); + const [listId, setListId] = useState(""); + const [listOptions, setListOptions] = useState([]); + const [listsLoading, setListsLoading] = useState(false); + const [listsError, setListsError] = useState(); + + useEffect(() => { + if (!isOpen) return; + setAccountId(activeAccountId || accounts[0]?.id || ""); + }, [isOpen, activeAccountId, accounts]); + + useEffect(() => { + setTitle(defaultTitle(kind)); + }, [kind]); + + useEffect(() => { + if (!isOpen || kind !== "list" || !accountId) return; + let mounted = true; + async function loadLists() { + try { + setListsLoading(true); + setListsError(undefined); + const cached = listsCache[accountId]; + if (cached) { + setListOptions(cached); + return; + } + const lists = await fetchLists(accountId); + if (!mounted) return; + setListOptions(lists); + } catch (error) { + setListsError( + error instanceof Error ? error.message : "Failed to load lists", + ); + } finally { + if (mounted) setListsLoading(false); + } + } + loadLists(); + return () => { + mounted = false; + }; + }, [accountId, fetchLists, isOpen, kind, listsCache]); + + useEffect(() => { + if (!isOpen) return; + setListId(""); + setListOptions([]); + }, [accountId, isOpen, kind]); + + const selectedAccount = useMemo( + () => accounts.find((account) => account.id === accountId), + [accountId, accounts], + ); + + const canSubmit = Boolean(selectedAccount && (kind !== "list" || listId)); + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (!canSubmit || !selectedAccount) return; + onAdd({ + accountId: selectedAccount.id, + account: selectedAccount.username || "", + kind, + title: title.trim() || defaultTitle(kind), + listId: listId || undefined, + listName: + kind === "list" + ? listOptions.find((list) => list.id === listId)?.name + : undefined, + }); + onClose(); + }; + + useEffect(() => { + if (!isOpen) return; + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") onClose(); + }; + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
{ + if (event.target === event.currentTarget) { + onClose(); + } + }} + > +
+
+
+

New column

+

Design your stream

+
+ +
+
+ +
+ {columnOptions.map((option) => ( + + ))} +
+ {kind === "list" && ( + + )} + + +
+
+
+ ); +} + +function defaultTitle(kind: DeckColumn["kind"]) { + switch (kind) { + case "following": + return "Following"; + case "bookmarks": + return "Bookmarks"; + case "list": + return "List"; + case "chat": + return "Chat"; + default: + return "For You"; + } +} -- cgit v1.2.3