diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index 634e355dd50..9e1172631ab 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -26,7 +26,7 @@
"prettier": "npm run format",
"check-translation": "prettier --check ../superset/translations/**/LC_MESSAGES/*.json",
"clean-translation": "prettier --write ../superset/translations/**/LC_MESSAGES/*.json",
- "storybook": "NODE_ENV=development BABEL_ENV=development start-storybook -p 6006",
+ "storybook": "NODE_ENV=development BABEL_ENV=development start-storybook -s ./images -p 6006",
"build-storybook": "build-storybook"
},
"repository": {
diff --git a/superset-frontend/src/components/Loading/Loading.stories.tsx b/superset-frontend/src/components/Loading/Loading.stories.tsx
new file mode 100644
index 00000000000..b0d8bf11f0e
--- /dev/null
+++ b/superset-frontend/src/components/Loading/Loading.stories.tsx
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import Loading, { Props, PositionOption } from './index';
+
+export default {
+ title: 'Loading',
+ component: Loading,
+ includeStories: ['LoadingGallery', 'InteractiveLoading'],
+};
+
+export const POSITIONS: PositionOption[] = ['normal', 'floating', 'inline'];
+
+export const LoadingGallery = () => (
+ <>
+ {POSITIONS.map(position => (
+
+
{position}
+
+
+ ))}
+ >
+);
+
+LoadingGallery.story = {
+ parameters: {
+ actions: {
+ disabled: true,
+ },
+ controls: {
+ disabled: true,
+ },
+ knobs: {
+ disabled: true,
+ },
+ },
+};
+
+export const InteractiveLoading = (args: Props) => ;
+
+InteractiveLoading.story = {
+ parameters: {
+ knobs: {
+ disabled: true,
+ },
+ },
+};
+
+InteractiveLoading.args = {
+ image: '/images/loading.gif',
+ className: '',
+};
+
+InteractiveLoading.argTypes = {
+ position: {
+ name: 'position',
+ control: { type: 'select', options: POSITIONS },
+ },
+};
diff --git a/superset-frontend/src/components/Loading/Loading.test.tsx b/superset-frontend/src/components/Loading/Loading.test.tsx
new file mode 100644
index 00000000000..243603fda6e
--- /dev/null
+++ b/superset-frontend/src/components/Loading/Loading.test.tsx
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/react';
+import Loading from './index';
+
+test('Rerendering correctly with default props', () => {
+ render();
+ const loading = screen.getByRole('status');
+ const classNames = loading.getAttribute('class')?.split(' ');
+ const imagePath = loading.getAttribute('src');
+ const ariaLive = loading.getAttribute('aria-live');
+ const ariaLabel = loading.getAttribute('aria-label');
+ expect(loading).toBeInTheDocument();
+ expect(imagePath).toBe('/static/assets/images/loading.gif');
+ expect(classNames).toContain('floating');
+ expect(classNames).toContain('loading');
+ expect(ariaLive).toContain('polite');
+ expect(ariaLabel).toContain('Loading');
+});
+
+test('Position must be a class', () => {
+ render();
+ const loading = screen.getByRole('status');
+ const classNames = loading.getAttribute('class')?.split(' ');
+ expect(loading).toBeInTheDocument();
+ expect(classNames).not.toContain('floating');
+ expect(classNames).toContain('normal');
+});
+
+test('support for extra classes', () => {
+ render();
+ const loading = screen.getByRole('status');
+ const classNames = loading.getAttribute('class')?.split(' ');
+ expect(loading).toBeInTheDocument();
+ expect(classNames).toContain('loading');
+ expect(classNames).toContain('floating');
+ expect(classNames).toContain('extra-class');
+});
+
+test('Diferent image path', () => {
+ render();
+ const loading = screen.getByRole('status');
+ const imagePath = loading.getAttribute('src');
+ expect(loading).toBeInTheDocument();
+ expect(imagePath).toBe('/images/loading.gif');
+});
diff --git a/superset-frontend/src/components/Loading.tsx b/superset-frontend/src/components/Loading/index.tsx
similarity index 72%
rename from superset-frontend/src/components/Loading.tsx
rename to superset-frontend/src/components/Loading/index.tsx
index 4eb92cb1f15..d6253560287 100644
--- a/superset-frontend/src/components/Loading.tsx
+++ b/superset-frontend/src/components/Loading/index.tsx
@@ -16,11 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
+
import React from 'react';
import { styled } from '@superset-ui/core';
+import cls from 'classnames';
-interface Props {
- position?: string;
+export type PositionOption = 'normal' | 'inline' | 'floating';
+export interface Props {
+ position?: PositionOption;
+ className?: string;
+ image?: string;
}
const LoaderImg = styled.img`
@@ -41,12 +46,19 @@ const LoaderImg = styled.img`
transform: translate(-50%, -50%);
}
`;
-export default function Loading({ position = 'floating' }: Props) {
+export default function Loading({
+ position = 'floating',
+ image = '/static/assets/images/loading.gif',
+ className,
+}: Props) {
return (
);
}