Skip to content

Commit d3cefac

Browse files
authored
🪟 🎨 Add loading backdrop for sync catalog table (#15671)
1 parent 722d751 commit d3cefac

File tree

13 files changed

+169
-47
lines changed

13 files changed

+169
-47
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@use "../../scss/colors";
2+
3+
%cover {
4+
width: 100%;
5+
height: 100%;
6+
}
7+
8+
.loadingBackdropContainer {
9+
@extend %cover;
10+
position: relative;
11+
12+
.backdrop {
13+
@extend %cover;
14+
position: absolute;
15+
background-color: colors.$white;
16+
opacity: 0.6;
17+
z-index: 1;
18+
}
19+
20+
.spinnerContainer {
21+
@extend %cover;
22+
position: absolute;
23+
display: flex;
24+
align-items: center;
25+
justify-content: center;
26+
z-index: 2;
27+
}
28+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { render } from "@testing-library/react";
2+
3+
import { LoadingBackdrop } from "./LoadingBackdrop";
4+
5+
describe("<LoadingBackdrop />", () => {
6+
it("loading backdrop is active", () => {
7+
const { getByTestId } = render(<LoadingBackdrop loading />);
8+
9+
expect(getByTestId("loading-backdrop")).toBeInTheDocument();
10+
});
11+
12+
it("loading backdrop is not active", () => {
13+
const { queryByTestId } = render(<LoadingBackdrop loading={false} />);
14+
15+
expect(queryByTestId("loading-backdrop")).toBeNull();
16+
});
17+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from "react";
2+
3+
import Spinner from "../Spinner/Spinner";
4+
import styles from "./LoadingBackdrop.module.scss";
5+
6+
interface LoadingBackdropProps {
7+
loading: boolean;
8+
small?: boolean;
9+
}
10+
export const LoadingBackdrop: React.FC<LoadingBackdropProps> = ({ loading, small, children }) => {
11+
return (
12+
<div className={styles.loadingBackdropContainer}>
13+
{loading && (
14+
<>
15+
<div className={styles.backdrop} data-testid="loading-backdrop" />
16+
<div className={styles.spinnerContainer}>
17+
<Spinner small={small} />
18+
</div>
19+
</>
20+
)}
21+
{children}
22+
</div>
23+
);
24+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentStory, ComponentMeta } from "@storybook/react";
2+
3+
import { LoadingBackdrop } from "./LoadingBackdrop";
4+
5+
export default {
6+
title: "Ui/LoadingBackdrop",
7+
component: LoadingBackdrop,
8+
argTypes: {
9+
loading: { type: "boolean", required: true },
10+
small: { type: "boolean", required: false },
11+
},
12+
} as ComponentMeta<typeof LoadingBackdrop>;
13+
14+
const Template: ComponentStory<typeof LoadingBackdrop> = (args) => (
15+
<div style={{ height: 200, width: 200, border: "1px solid blue" }}>
16+
<LoadingBackdrop {...args}>
17+
<div style={{ padding: 15 }}>
18+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
19+
</div>
20+
</LoadingBackdrop>
21+
</div>
22+
);
23+
export const Primary = Template.bind({});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { LoadingBackdrop } from "./LoadingBackdrop";
Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from "react";
2-
import styled, { useTheme } from "styled-components";
3-
import { Theme } from "theme";
2+
import styled from "styled-components";
43

54
import Spinner from "components/Spinner";
65

@@ -17,13 +16,10 @@ const Container = styled.div<IProps>`
1716
align-items: center;
1817
`;
1918

20-
const LoadingPage: React.FC<IProps> = ({ full }) => {
21-
const theme = useTheme() as Theme;
22-
return (
23-
<Container full={full}>
24-
<Spinner backgroundColor={theme.backgroundColor} />
25-
</Container>
26-
);
27-
};
19+
const LoadingPage: React.FC<IProps> = ({ full }) => (
20+
<Container full={full}>
21+
<Spinner />
22+
</Container>
23+
);
2824

2925
export default LoadingPage;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@use "../../scss/colors";
2+
3+
@keyframes spin {
4+
from {
5+
transform: rotate(0);
6+
}
7+
to {
8+
transform: rotate(359deg);
9+
}
10+
}
11+
.spinner {
12+
width: 42px;
13+
height: 42px;
14+
border: 4px solid colors.$blue-100;
15+
border-top: 4px solid transparent;
16+
border-radius: 50%;
17+
animation: spin 1.5s linear 0s infinite;
18+
}
19+
20+
.small {
21+
width: 30px;
22+
height: 30px;
23+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { render } from "@testing-library/react";
2+
3+
import Spinner from "./Spinner";
4+
5+
describe("<Spinner />", () => {
6+
it("should render without crash", () => {
7+
const { asFragment } = render(<Spinner />);
8+
9+
expect(asFragment()).toMatchSnapshot();
10+
});
11+
});
Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,14 @@
1+
import classnames from "classnames";
12
import React from "react";
2-
import styled, { keyframes } from "styled-components";
33

4-
interface IProps {
5-
backgroundColor?: string;
4+
import styles from "./Spinner.module.scss";
5+
6+
interface SpinnerProps {
67
small?: boolean;
78
}
89

9-
export const SpinAnimation = keyframes`
10-
0% {
11-
transform: rotate(0deg);
12-
}
13-
100% {
14-
transform: rotate(360deg);
15-
}
16-
`;
17-
18-
const SpinnerWheel = styled.div<{ small?: boolean }>`
19-
display: inline-block;
20-
height: ${({ small }) => (small ? 30 : 42)}px;
21-
width: ${({ small }) => (small ? 30 : 42)}px;
22-
border-radius: 50%;
23-
border: 4px solid ${({ theme }) => theme.primaryColor12};
24-
position: relative;
25-
animation: ${SpinAnimation} 1.5s linear 0s infinite;
26-
`;
27-
28-
const BreakRec = styled.div<IProps>`
29-
width: 13px;
30-
height: 7px;
31-
background: ${({ theme, backgroundColor }) => (backgroundColor ? backgroundColor : theme.whiteColor)};
32-
top: -4px;
33-
position: relative;
34-
margin: 0 auto;
35-
`;
36-
37-
const Spinner: React.FC<IProps> = ({ backgroundColor, small }) => (
38-
<SpinnerWheel small={small}>
39-
<BreakRec backgroundColor={backgroundColor} />
40-
</SpinnerWheel>
10+
const Spinner: React.FC<SpinnerProps> = ({ small }) => (
11+
<div className={classnames(styles.spinner, { [styles.small]: small })} />
4112
);
4213

4314
export default Spinner;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<Spinner /> should render without crash 1`] = `
4+
<DocumentFragment>
5+
<div
6+
class="spinner"
7+
/>
8+
</DocumentFragment>
9+
`;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ComponentStory, ComponentMeta } from "@storybook/react";
2+
3+
import SpinnerComponent from "./Spinner";
4+
5+
export default {
6+
title: "Ui/Spinner",
7+
component: SpinnerComponent,
8+
argTypes: {
9+
small: { type: "boolean", required: false },
10+
},
11+
} as ComponentMeta<typeof SpinnerComponent>;
12+
13+
const Template: ComponentStory<typeof SpinnerComponent> = (args) => <SpinnerComponent {...args} />;
14+
15+
export const Primary = Template.bind({});

airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ const ConnectionForm: React.FC<ConnectionFormProps> = ({
332332
destinationSupportedSyncModes={destDefinition.supportedDestinationSyncModes}
333333
additionalControl={additionalSchemaControl}
334334
component={SchemaField}
335+
isSubmitting={isSubmitting}
335336
mode={mode}
336337
/>
337338
</StyledSection>

airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { FormattedMessage } from "react-intl";
55
import styled from "styled-components";
66

77
import { CheckBox, H5 } from "components";
8+
import { LoadingBackdrop } from "components/LoadingBackdrop";
89
import { Cell, Header } from "components/SimpleTableComponents";
910

1011
import { useConfig } from "config";
@@ -70,6 +71,7 @@ const LearnMoreLink = styled.a`
7071
interface SchemaViewProps extends FieldProps<SyncSchemaStream[]> {
7172
additionalControl?: React.ReactNode;
7273
destinationSupportedSyncModes: DestinationSyncMode[];
74+
isSubmitting: boolean;
7375
mode?: ConnectionFormMode;
7476
}
7577

@@ -172,6 +174,7 @@ const SyncCatalogField: React.FC<SchemaViewProps> = ({
172174
additionalControl,
173175
field,
174176
form,
177+
isSubmitting,
175178
mode,
176179
}) => {
177180
const { value: streams, name: fieldName } = field;
@@ -209,7 +212,7 @@ const SyncCatalogField: React.FC<SchemaViewProps> = ({
209212

210213
return (
211214
<BatchEditProvider nodes={streams} update={onChangeSchema}>
212-
<div>
215+
<LoadingBackdrop loading={isSubmitting}>
213216
<HeaderBlock>
214217
{mode !== "readonly" ? (
215218
<>
@@ -236,7 +239,7 @@ const SyncCatalogField: React.FC<SchemaViewProps> = ({
236239
mode={mode}
237240
/>
238241
</TreeViewContainer>
239-
</div>
242+
</LoadingBackdrop>
240243
</BatchEditProvider>
241244
);
242245
};

0 commit comments

Comments
 (0)