diff --git a/package.json b/package.json index 4659ba9..b6d094b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sky-follower-bridge", - "displayName": "__MSG_extension_name__", + "displayName": "Sky Follower Bridge", "version": "2.1.1", "description": "__MSG_extension_description__", "author": "kawamataryou", diff --git a/src/lib/components/DetectedUserListItem.tsx b/src/lib/components/DetectedUserListItem.tsx index 54cda82..4523fd7 100644 --- a/src/lib/components/DetectedUserListItem.tsx +++ b/src/lib/components/DetectedUserListItem.tsx @@ -14,6 +14,7 @@ export type Props = { accountName: string; displayName: string; }) => Promise<void>; + deleteUser: (did: string) => Promise<void>; }; const DetectedUserListItem = ({ @@ -21,6 +22,7 @@ const DetectedUserListItem = ({ actionMode, clickAction, reSearch, + deleteUser, }: Props) => { const [isBtnHovered, setIsBtnHovered] = React.useState(false); const [isJustClicked, setIsJustClicked] = React.useState(false); @@ -100,6 +102,10 @@ const DetectedUserListItem = ({ }); }; + const handleDeleteClick = () => { + deleteUser(user.did); + }; + const matchTypeColor = MATCH_TYPE_LABEL_AND_COLOR[user.matchType].color; return ( @@ -123,6 +129,7 @@ const DetectedUserListItem = ({ setIsBtnHovered={setIsBtnHovered} setIsJustClicked={setIsJustClicked} handleReSearchClick={handleReSearchClick} + handleDeleteClick={handleDeleteClick} /> </div> </div> diff --git a/src/lib/components/UserCard.tsx b/src/lib/components/UserCard.tsx index 136399f..85b5905 100644 --- a/src/lib/components/UserCard.tsx +++ b/src/lib/components/UserCard.tsx @@ -4,14 +4,33 @@ import ActionButton from "./ActionButton"; import UserInfo from "./UserInfo"; import UserProfile from "./UserProfile"; -export type UserCardProps = { - user: Pick<BskyUser, "avatar" | "handle" | "displayName" | "description">; - loading: boolean; - actionBtnLabelAndClass: { label: string; class: string }; - handleActionButtonClick: () => void; - setIsBtnHovered: (value: boolean) => void; - setIsJustClicked: (value: boolean) => void; - handleReSearchClick: () => void; +const DeleteButton = ({ + onClick, +}: { + onClick: () => void; +}) => { + return ( + <button + type="button" + className="btn-outline w-7 h-7 border rounded-full flex items-center justify-center" + onClick={onClick} + > + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + strokeWidth={1.5} + stroke="currentColor" + className="h-4 w-4" + > + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6 18L18 6M6 6l12 12" + /> + </svg> + </button> + ); }; const ReSearchButton = ({ @@ -43,6 +62,17 @@ const ReSearchButton = ({ ); }; +export type UserCardProps = { + user: Pick<BskyUser, "avatar" | "handle" | "displayName" | "description">; + loading: boolean; + actionBtnLabelAndClass: { label: string; class: string }; + handleActionButtonClick: () => void; + setIsBtnHovered: (value: boolean) => void; + setIsJustClicked: (value: boolean) => void; + handleReSearchClick: () => void; + handleDeleteClick: () => void; +}; + const UserCard = ({ user, loading, @@ -51,6 +81,7 @@ const UserCard = ({ setIsBtnHovered, setIsJustClicked, handleReSearchClick, + handleDeleteClick = () => {}, }: UserCardProps) => { return ( <div className="relative py-3 pt-1 pl-0 pr-2 grid grid-cols-[50px_1fr]"> @@ -59,23 +90,28 @@ const UserCard = ({ url={`https://bsky.app/profile/${user.handle}`} /> <div className="flex flex-col gap-2"> - <div className="flex justify-between items-center gap-2"> + <div className="flex justify-between items-start gap-2"> <div className="flex items-start gap-4"> <UserInfo handle={user.handle} displayName={user.displayName} url={`https://bsky.app/profile/${user.handle}`} /> - <ReSearchButton onClick={handleReSearchClick} /> </div> - <div className="card-actions flex items-center gap-4"> - <ActionButton - loading={loading} - actionBtnLabelAndClass={actionBtnLabelAndClass} - handleActionButtonClick={handleActionButtonClick} - setIsBtnHovered={setIsBtnHovered} - setIsJustClicked={setIsJustClicked} - /> + <div className="card-actions flex items-center gap-6"> + <div className="flex items-center gap-2"> + <ReSearchButton onClick={handleReSearchClick} /> + <DeleteButton onClick={handleDeleteClick} /> + </div> + <div className="w-[170px]"> + <ActionButton + loading={loading} + actionBtnLabelAndClass={actionBtnLabelAndClass} + handleActionButtonClick={handleActionButtonClick} + setIsBtnHovered={setIsBtnHovered} + setIsJustClicked={setIsJustClicked} + /> + </div> </div> </div> <p className="text-sm break-all">{user.description}</p> diff --git a/src/lib/hooks/useBskyUserManager.ts b/src/lib/hooks/useBskyUserManager.ts index 47ee90b..b708f95 100644 --- a/src/lib/hooks/useBskyUserManager.ts +++ b/src/lib/hooks/useBskyUserManager.ts @@ -271,6 +271,13 @@ export const useBskyUserManager = () => { }); }, []); + const deleteUser = React.useCallback( + async (did: string) => { + await setUsers((prev) => prev.filter((user) => user.did !== did)); + }, + [setUsers], + ); + const changeDetectedUser = React.useCallback( (fromDid: string, toUser: ProfileView) => { setUsers((prev) => @@ -305,5 +312,6 @@ export const useBskyUserManager = () => { reSearchResults, changeDetectedUser, clearReSearchResults, + deleteUser, }; }; diff --git a/src/options.tsx b/src/options.tsx index cf07844..17a72ed 100644 --- a/src/options.tsx +++ b/src/options.tsx @@ -5,6 +5,7 @@ import useConfirm from "~lib/components/ConfirmDialog"; import Sidebar from "~lib/components/Sidebar"; import "react-toastify/dist/ReactToastify.css"; import type { ProfileView } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; +import { AnimatePresence, motion } from "framer-motion"; import React from "react"; import ReSearchModal from "~components/ReSearchModal"; import DetectedUserListItem from "~lib/components/DetectedUserListItem"; @@ -26,6 +27,7 @@ const Option = () => { reSearchResults, changeDetectedUser, clearReSearchResults, + deleteUser, } = useBskyUserManager(); const { @@ -175,15 +177,27 @@ const Option = () => { </h2> </div> <div className="flex flex-col border-b-[1px] border-gray-500"> - {filteredUsers.map((user) => ( - <DetectedUserListItem - key={user.handle} - user={user} - clickAction={handleClickAction} - actionMode={actionMode} - reSearch={handleReSearch} - /> - ))} + <AnimatePresence> + {filteredUsers.map((user) => ( + <motion.div + key={user.did} + layout + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + transition={{ duration: 0.3 }} + > + <DetectedUserListItem + key={user.handle} + user={user} + clickAction={handleClickAction} + actionMode={actionMode} + reSearch={handleReSearch} + deleteUser={deleteUser} + /> + </motion.div> + ))} + </AnimatePresence> </div> </div> <div className="fixed bottom-5 right-5">