forked from github/plane
fix: existing and parent issue modal empty state flicker (#4281)
This commit is contained in:
parent
e60ef36bfe
commit
15c7deb2db
@ -36,6 +36,7 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
|||||||
workspaceLevelToggle = false,
|
workspaceLevelToggle = false,
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
|
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
|
||||||
const [selectedIssues, setSelectedIssues] = useState<ISearchIssueResponse[]>([]);
|
const [selectedIssues, setSelectedIssues] = useState<ISearchIssueResponse[]>([]);
|
||||||
@ -72,7 +73,7 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen || !workspaceSlug || !projectId) return;
|
if (!isOpen || !workspaceSlug || !projectId) return;
|
||||||
|
setIsLoading(true);
|
||||||
projectService
|
projectService
|
||||||
.projectIssuesSearch(workspaceSlug as string, projectId as string, {
|
.projectIssuesSearch(workspaceSlug as string, projectId as string, {
|
||||||
search: debouncedSearchTerm,
|
search: debouncedSearchTerm,
|
||||||
@ -80,7 +81,10 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
|||||||
workspace_search: isWorkspaceLevel,
|
workspace_search: isWorkspaceLevel,
|
||||||
})
|
})
|
||||||
.then((res) => setIssues(res))
|
.then((res) => setIssues(res))
|
||||||
.finally(() => setIsSearching(false));
|
.finally(() => {
|
||||||
|
setIsSearching(false);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
}, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, searchParams, workspaceSlug]);
|
}, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, searchParams, workspaceSlug]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -194,14 +198,7 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
|||||||
</h5>
|
</h5>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<IssueSearchModalEmptyState
|
{isSearching || isLoading ? (
|
||||||
debouncedSearchTerm={debouncedSearchTerm}
|
|
||||||
isSearching={isSearching}
|
|
||||||
issues={issues}
|
|
||||||
searchTerm={searchTerm}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isSearching ? (
|
|
||||||
<Loader className="space-y-3 p-3">
|
<Loader className="space-y-3 p-3">
|
||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
@ -209,48 +206,59 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
|||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
) : (
|
) : (
|
||||||
<ul className={`text-sm text-custom-text-100 ${issues.length > 0 ? "p-2" : ""}`}>
|
<>
|
||||||
{issues.map((issue) => {
|
{issues.length === 0 ? (
|
||||||
const selected = selectedIssues.some((i) => i.id === issue.id);
|
<IssueSearchModalEmptyState
|
||||||
|
debouncedSearchTerm={debouncedSearchTerm}
|
||||||
|
isSearching={isSearching}
|
||||||
|
issues={issues}
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ul className={`text-sm text-custom-text-100 ${issues.length > 0 ? "p-2" : ""}`}>
|
||||||
|
{issues.map((issue) => {
|
||||||
|
const selected = selectedIssues.some((i) => i.id === issue.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={issue.id}
|
key={issue.id}
|
||||||
as="label"
|
as="label"
|
||||||
htmlFor={`issue-${issue.id}`}
|
htmlFor={`issue-${issue.id}`}
|
||||||
value={issue}
|
value={issue}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
`group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||||
active ? "bg-custom-background-80 text-custom-text-100" : ""
|
active ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||||
} ${selected ? "text-custom-text-100" : ""}`
|
} ${selected ? "text-custom-text-100" : ""}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input type="checkbox" checked={selected} readOnly />
|
<input type="checkbox" checked={selected} readOnly />
|
||||||
<span
|
<span
|
||||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: issue.state__color,
|
backgroundColor: issue.state__color,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="flex-shrink-0 text-xs">
|
<span className="flex-shrink-0 text-xs">
|
||||||
{issue.project__identifier}-{issue.sequence_id}
|
{issue.project__identifier}-{issue.sequence_id}
|
||||||
</span>
|
</span>
|
||||||
{issue.name}
|
{issue.name}
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="z-1 relative hidden text-custom-text-200 hover:text-custom-text-100 group-hover:block"
|
className="z-1 relative hidden text-custom-text-200 hover:text-custom-text-100 group-hover:block"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Rocket className="h-4 w-4" />
|
<Rocket className="h-4 w-4" />
|
||||||
</a>
|
</a>
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Combobox.Options>
|
</Combobox.Options>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
|
@ -36,6 +36,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||||||
projectId,
|
projectId,
|
||||||
issueId,
|
issueId,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
|
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
@ -56,6 +57,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||||||
if (!isOpen || !workspaceSlug || !projectId) return;
|
if (!isOpen || !workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
setIsSearching(true);
|
setIsSearching(true);
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
projectService
|
projectService
|
||||||
.projectIssuesSearch(workspaceSlug as string, projectId as string, {
|
.projectIssuesSearch(workspaceSlug as string, projectId as string, {
|
||||||
@ -65,7 +67,10 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||||||
workspace_search: isWorkspaceLevel,
|
workspace_search: isWorkspaceLevel,
|
||||||
})
|
})
|
||||||
.then((res) => setIssues(res))
|
.then((res) => setIssues(res))
|
||||||
.finally(() => setIsSearching(false));
|
.finally(() => {
|
||||||
|
setIsSearching(false);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
}, [debouncedSearchTerm, isOpen, issueId, isWorkspaceLevel, projectId, workspaceSlug]);
|
}, [debouncedSearchTerm, isOpen, issueId, isWorkspaceLevel, projectId, workspaceSlug]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -153,14 +158,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||||||
</h5>
|
</h5>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<IssueSearchModalEmptyState
|
{isSearching || isLoading ? (
|
||||||
debouncedSearchTerm={debouncedSearchTerm}
|
|
||||||
isSearching={isSearching}
|
|
||||||
issues={issues}
|
|
||||||
searchTerm={searchTerm}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isSearching ? (
|
|
||||||
<Loader className="space-y-3 p-3">
|
<Loader className="space-y-3 p-3">
|
||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
@ -168,41 +166,52 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
) : (
|
) : (
|
||||||
<ul className={`text-sm ${issues.length > 0 ? "p-2" : ""}`}>
|
<>
|
||||||
{issues.map((issue) => (
|
{issues.length === 0 ? (
|
||||||
<Combobox.Option
|
<IssueSearchModalEmptyState
|
||||||
key={issue.id}
|
debouncedSearchTerm={debouncedSearchTerm}
|
||||||
value={issue}
|
isSearching={isSearching}
|
||||||
className={({ active, selected }) =>
|
issues={issues}
|
||||||
`group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
searchTerm={searchTerm}
|
||||||
active ? "bg-custom-background-80 text-custom-text-100" : ""
|
/>
|
||||||
} ${selected ? "text-custom-text-100" : ""}`
|
) : (
|
||||||
}
|
<ul className={`text-sm ${issues.length > 0 ? "p-2" : ""}`}>
|
||||||
>
|
{issues.map((issue) => (
|
||||||
<div className="flex flex-grow items-center gap-2 truncate">
|
<Combobox.Option
|
||||||
<span
|
key={issue.id}
|
||||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
value={issue}
|
||||||
style={{
|
className={({ active, selected }) =>
|
||||||
backgroundColor: issue.state__color,
|
`group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||||
}}
|
active ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||||
/>
|
} ${selected ? "text-custom-text-100" : ""}`
|
||||||
<span className="flex-shrink-0 text-xs">
|
}
|
||||||
{issue.project__identifier}-{issue.sequence_id}
|
>
|
||||||
</span>{" "}
|
<div className="flex flex-grow items-center gap-2 truncate">
|
||||||
<span className="truncate">{issue.name}</span>
|
<span
|
||||||
</div>
|
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||||
<a
|
style={{
|
||||||
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
backgroundColor: issue.state__color,
|
||||||
target="_blank"
|
}}
|
||||||
className="z-1 relative hidden flex-shrink-0 text-custom-text-200 hover:text-custom-text-100 group-hover:block"
|
/>
|
||||||
rel="noopener noreferrer"
|
<span className="flex-shrink-0 text-xs">
|
||||||
onClick={(e) => e.stopPropagation()}
|
{issue.project__identifier}-{issue.sequence_id}
|
||||||
>
|
</span>{" "}
|
||||||
<Rocket className="h-4 w-4" />
|
<span className="truncate">{issue.name}</span>
|
||||||
</a>
|
</div>
|
||||||
</Combobox.Option>
|
<a
|
||||||
))}
|
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
||||||
</ul>
|
target="_blank"
|
||||||
|
className="z-1 relative hidden flex-shrink-0 text-custom-text-200 hover:text-custom-text-100 group-hover:block"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Rocket className="h-4 w-4" />
|
||||||
|
</a>
|
||||||
|
</Combobox.Option>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Combobox.Options>
|
</Combobox.Options>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
|
Loading…
Reference in New Issue
Block a user