kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf(web): incrementally render assignee popover members

Tin 45265487 1448bfe4

+78 -6
+39 -3
apps/web/src/components/task/subtask-assignee-popover.tsx
··· 15 15 import { toast } from "@/lib/toast"; 16 16 import type Task from "@/types/task"; 17 17 18 + const INITIAL_VISIBLE_USERS = 40; 19 + const VISIBLE_USERS_STEP = 40; 20 + 18 21 type SubtaskAssigneePopoverProps = { 19 22 tasks: Task[]; 20 23 workspaceId: string; ··· 28 31 }: SubtaskAssigneePopoverProps) { 29 32 const { t } = useTranslation(); 30 33 const [open, setOpen] = useState(false); 34 + const [visibleUsersCount, setVisibleUsersCount] = useState( 35 + INITIAL_VISIBLE_USERS, 36 + ); 31 37 const { mutateAsync: updateTaskAssignee } = useUpdateTaskAssignee(); 32 38 const { data: workspaceUsers } = useGetActiveWorkspaceUsers(workspaceId); 33 39 ··· 75 81 return [unassignedOption, ...userOptions]; 76 82 }, [usersOptions, handleAssigneeChange]); 77 83 84 + const visibleUsersOptions = useMemo(() => { 85 + return usersOptions?.slice(0, visibleUsersCount) ?? []; 86 + }, [usersOptions, visibleUsersCount]); 87 + 88 + const handleOpenChange = useCallback((nextOpen: boolean) => { 89 + setOpen(nextOpen); 90 + if (nextOpen) { 91 + setVisibleUsersCount(INITIAL_VISIBLE_USERS); 92 + } 93 + }, []); 94 + 95 + const handleListScroll = useCallback( 96 + (event: React.UIEvent<HTMLDivElement>) => { 97 + const target = event.currentTarget; 98 + const nearBottom = 99 + target.scrollHeight - target.scrollTop - target.clientHeight < 48; 100 + 101 + if (!nearBottom) return; 102 + 103 + setVisibleUsersCount((current) => { 104 + const totalUsers = usersOptions?.length ?? current; 105 + return Math.min(current + VISIBLE_USERS_STEP, totalUsers); 106 + }); 107 + }, 108 + [usersOptions?.length], 109 + ); 110 + 78 111 useNumberedShortcuts(open, shortcutOptions); 79 112 80 113 return ( 81 - <Popover open={open} onOpenChange={setOpen} modal={false}> 114 + <Popover open={open} onOpenChange={handleOpenChange} modal={false}> 82 115 <PopoverTrigger asChild>{children}</PopoverTrigger> 83 116 <PopoverContent className="w-56 p-0" align="start"> 84 - <div className="max-h-80 space-y-1 overflow-y-auto p-1"> 117 + <div 118 + className="max-h-80 space-y-1 overflow-y-auto p-1" 119 + onScroll={handleListScroll} 120 + > 85 121 <Button 86 122 variant="ghost" 87 123 size="sm" ··· 105 141 <ShortcutNumber number={1} /> 106 142 )} 107 143 </Button> 108 - {usersOptions?.map((user, index) => ( 144 + {visibleUsersOptions.map((user, index) => ( 109 145 <Button 110 146 key={user.value} 111 147 variant="ghost"
+39 -3
apps/web/src/components/task/task-assignee-popover.tsx
··· 15 15 import { toast } from "@/lib/toast"; 16 16 import type Task from "@/types/task"; 17 17 18 + const INITIAL_VISIBLE_USERS = 40; 19 + const VISIBLE_USERS_STEP = 40; 20 + 18 21 type TaskAssigneePopoverProps = { 19 22 task: Task; 20 23 workspaceId: string; ··· 28 31 }: TaskAssigneePopoverProps) { 29 32 const { t } = useTranslation(); 30 33 const [open, setOpen] = useState(false); 34 + const [visibleUsersCount, setVisibleUsersCount] = useState( 35 + INITIAL_VISIBLE_USERS, 36 + ); 31 37 const { mutateAsync: updateTaskAssignee } = useUpdateTaskAssignee(); 32 38 const { data: workspaceUsers } = useGetActiveWorkspaceUsers(workspaceId); 33 39 ··· 67 73 return [unassignedOption, ...userOptions]; 68 74 }, [usersOptions, handleAssigneeChange]); 69 75 76 + const visibleUsersOptions = useMemo(() => { 77 + return usersOptions?.slice(0, visibleUsersCount) ?? []; 78 + }, [usersOptions, visibleUsersCount]); 79 + 80 + const handleOpenChange = useCallback((nextOpen: boolean) => { 81 + setOpen(nextOpen); 82 + if (nextOpen) { 83 + setVisibleUsersCount(INITIAL_VISIBLE_USERS); 84 + } 85 + }, []); 86 + 87 + const handleListScroll = useCallback( 88 + (event: React.UIEvent<HTMLDivElement>) => { 89 + const target = event.currentTarget; 90 + const nearBottom = 91 + target.scrollHeight - target.scrollTop - target.clientHeight < 48; 92 + 93 + if (!nearBottom) return; 94 + 95 + setVisibleUsersCount((current) => { 96 + const totalUsers = usersOptions?.length ?? current; 97 + return Math.min(current + VISIBLE_USERS_STEP, totalUsers); 98 + }); 99 + }, 100 + [usersOptions?.length], 101 + ); 102 + 70 103 useNumberedShortcuts(open, shortcutOptions); 71 104 72 105 return ( 73 - <Popover open={open} onOpenChange={setOpen}> 106 + <Popover open={open} onOpenChange={handleOpenChange}> 74 107 <PopoverTrigger asChild>{children}</PopoverTrigger> 75 108 <PopoverContent className="w-56 p-0" align="start"> 76 - <div className="max-h-80 space-y-1 overflow-y-auto p-1"> 109 + <div 110 + className="max-h-80 space-y-1 overflow-y-auto p-1" 111 + onScroll={handleListScroll} 112 + > 77 113 <Button 78 114 variant="ghost" 79 115 size="sm" ··· 97 133 <ShortcutNumber number={1} /> 98 134 )} 99 135 </Button> 100 - {usersOptions?.map((user, index) => ( 136 + {visibleUsersOptions.map((user, index) => ( 101 137 <Button 102 138 key={user.value} 103 139 variant="ghost"