Skip to content

Commit 8d6e540

Browse files
Arc 2484 5ku orglist scroll fix (#2453)
* fix: 5ku organization list scroll ux * fix: 5ku organization list scroll ux * fix: 5ku organization list scroll ux * fix: 5ku organization list scroll ux * fix: 5ku organization list scroll ux * chore: rename vars --------- Co-authored-by: kAy <[email protected]>
1 parent bff749d commit 8d6e540

File tree

2 files changed

+135
-59
lines changed

2 files changed

+135
-59
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useState, useEffect, useRef } from "react";
2+
3+
const useOrgListScroll = () => {
4+
const [isScrolledToBottom, setIsScrolledToBottom] = useState(false);
5+
const [isListScrollable, setIsListScrollable] = useState(false);
6+
const containerRef = useRef<HTMLDivElement | null>(null);
7+
const hasUserScrolledRef = useRef(false);
8+
9+
const checkIsListScrollable = (): boolean => {
10+
let isListScrollable = false;
11+
const content = containerRef.current;
12+
if (content) {
13+
const max = parseInt(window.getComputedStyle(content).maxHeight);
14+
const size = content.scrollHeight;
15+
isListScrollable = (size - 15) > max;
16+
}
17+
return isListScrollable;
18+
};
19+
20+
const handleScroll = ()=> {
21+
const content = containerRef.current;
22+
hasUserScrolledRef.current = true;
23+
if (content) {
24+
const scrollTop = content.scrollTop;
25+
const scrollHeight = content.scrollHeight;
26+
const clientHeight = content.clientHeight;
27+
const scrolledToBottom = scrollTop + clientHeight === scrollHeight;
28+
setIsScrolledToBottom(scrolledToBottom);
29+
}
30+
};
31+
32+
useEffect(() => {
33+
const content = containerRef.current;
34+
hasUserScrolledRef.current = false;
35+
if (content) {
36+
setIsListScrollable(checkIsListScrollable());
37+
content.addEventListener("scroll", handleScroll);
38+
}
39+
return () => {
40+
if (content) {
41+
content.removeEventListener("scroll", handleScroll);
42+
}
43+
};
44+
}, []);
45+
return { isScrolledToBottom, isListScrollable, containerRef, hasUserScrolledRef };
46+
};
47+
48+
export default useOrgListScroll;

spa/src/pages/ConfigSteps/OrgsContainer/index.tsx

Lines changed: 87 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
/** @jsxImportSource @emotion/react */
2+
import { useState } from "react";
23
import Button, { LoadingButton } from "@atlaskit/button";
34
import { GitHubInstallationType } from "../../../../../src/rest-interfaces";
45
import { css } from "@emotion/react";
56
import { token } from "@atlaskit/tokens";
6-
import { useState } from "react";
77
import WarningIcon from "@atlaskit/icon/glyph/warning";
88
import OauthManager from "../../../services/oauth-manager";
9-
import { ErrorForIPBlocked, ErrorForNonAdmins, ErrorForSSO } from "../../../components/Error/KnownErrors";
9+
import {
10+
ErrorForIPBlocked,
11+
ErrorForNonAdmins,
12+
ErrorForSSO,
13+
} from "../../../components/Error/KnownErrors";
14+
import useOrgListScroll from "../../../helper/useOrgListScroll";
1015

1116
const orgsWrapperStyle = css`
1217
max-height: 250px;
1318
overflow-y: auto;
14-
padding-right: 80px;
15-
margin-right: -80px;
19+
width: 100%;
1620
`;
1721
const orgDivStyle = css`
1822
display: flex;
@@ -34,6 +38,14 @@ const iconWrapperStyle = css`
3438
padding-top: ${token("space.150")};
3539
`;
3640

41+
const gradientStyle = css`
42+
background: linear-gradient(rgba(255, 255, 255, 0), rgb(255, 255, 255));
43+
height: 70px;
44+
margin-top: -70px;
45+
position: relative;
46+
width: 100%;
47+
display: block;
48+
`;
3749

3850
const OrganizationsList = ({
3951
organizations,
@@ -49,8 +61,18 @@ const OrganizationsList = ({
4961
resetCallback: (args: boolean) => void;
5062
connectingOrg: (org: GitHubInstallationType) => void;
5163
}) => {
52-
const [clickedOrg, setClickedOrg] = useState<GitHubInstallationType | undefined>(undefined);
53-
const canConnect = (org: GitHubInstallationType) => !org.requiresSsoLogin && !org.isIPBlocked && org.isAdmin;
64+
const {
65+
isScrolledToBottom,
66+
isListScrollable,
67+
containerRef,
68+
hasUserScrolledRef,
69+
} = useOrgListScroll();
70+
const [clickedOrg, setClickedOrg] = useState<
71+
GitHubInstallationType | undefined
72+
>(undefined);
73+
74+
const canConnect = (org: GitHubInstallationType) =>
75+
!org.requiresSsoLogin && !org.isIPBlocked && org.isAdmin;
5476

5577
// This method clears the tokens and then re-authenticates
5678
const resetToken = async () => {
@@ -81,60 +103,66 @@ const OrganizationsList = ({
81103
}
82104
};
83105
return (
84-
<div css={orgsWrapperStyle}>
85-
{organizations.map((org) => {
86-
const hasError = !canConnect(org);
87-
const orgDivStyles = hasError
88-
? [orgDivStyle, orgDivWithErrorStyle]
89-
: [orgDivStyle];
90-
return (
91-
<div key={org.id} css={orgDivStyles}>
92-
{canConnect(org) ? (
93-
<>
94-
<span css={orgNameStyle}>{org.account.login}</span>
95-
{loaderForOrgClicked && clickedOrg?.id === org.id ? (
96-
<LoadingButton style={{ width: 80 }} isLoading>
97-
Loading button
98-
</LoadingButton>
99-
) : (
100-
<Button
101-
isDisabled={
102-
loaderForOrgClicked && clickedOrg?.id !== org.id
103-
}
104-
onClick={async () => {
105-
setLoaderForOrgClicked(true);
106-
setClickedOrg(org);
107-
try {
108-
// Calling the create connection function that is passed from the parent
109-
await connectingOrg(org);
110-
} finally {
111-
setLoaderForOrgClicked(false);
112-
}
113-
}}
114-
>
115-
Connect
116-
</Button>
117-
)}
118-
</>
119-
) : (
120-
<>
121-
<div>
106+
<>
107+
<div css={orgsWrapperStyle} ref={containerRef}>
108+
{organizations.map((org) => {
109+
const hasError = !canConnect(org);
110+
const orgDivStyles = hasError
111+
? [orgDivStyle, orgDivWithErrorStyle]
112+
: [orgDivStyle];
113+
return (
114+
<div key={org.id} css={orgDivStyles}>
115+
{canConnect(org) ? (
116+
<>
122117
<span css={orgNameStyle}>{org.account.login}</span>
123-
<div>{errorMessage(org)}</div>
124-
</div>
125-
<div css={iconWrapperStyle}>
126-
<WarningIcon
127-
label="warning"
128-
primaryColor={token("color.background.warning.bold")}
129-
size="medium"
130-
/>
131-
</div>
132-
</>
133-
)}
134-
</div>
135-
);
136-
})}
137-
</div>
118+
{loaderForOrgClicked && clickedOrg?.id === org.id ? (
119+
<LoadingButton style={{ width: 80 }} isLoading>
120+
Loading button
121+
</LoadingButton>
122+
) : (
123+
<Button
124+
isDisabled={
125+
loaderForOrgClicked && clickedOrg?.id !== org.id
126+
}
127+
onClick={async () => {
128+
setLoaderForOrgClicked(true);
129+
setClickedOrg(org);
130+
try {
131+
// Calling the create connection function that is passed from the parent
132+
await connectingOrg(org);
133+
} finally {
134+
setLoaderForOrgClicked(false);
135+
}
136+
}}
137+
>
138+
Connect
139+
</Button>
140+
)}
141+
</>
142+
) : (
143+
<>
144+
<div>
145+
<span css={orgNameStyle}>{org.account.login}</span>
146+
<div>{errorMessage(org)}</div>
147+
</div>
148+
<div css={iconWrapperStyle}>
149+
<WarningIcon
150+
label="warning"
151+
primaryColor={token("color.background.warning.bold")}
152+
size="medium"
153+
/>
154+
</div>
155+
</>
156+
)}
157+
</div>
158+
);
159+
})}
160+
</div>
161+
{isListScrollable &&
162+
(hasUserScrolledRef.current ? !isScrolledToBottom : true) && (
163+
<div css={gradientStyle} />
164+
)}
165+
</>
138166
);
139167
};
140168

0 commit comments

Comments
 (0)