From 3b33dc13f5e4860d48cb02c354f7f041b425dbc6 Mon Sep 17 00:00:00 2001 From: Osakpolor Omonbude Date: Sun, 27 Sep 2026 12:19:28 +0100 Subject: [PATCH 1/2] Add clip spinner type to dcc.Loading --- .../src/components/Loading.tsx | 2 + .../Loading/spinners/ClipSpinner.tsx | 73 +++++++++++++++++++ components/dash-core-components/src/types.ts | 4 +- .../loading/test_loading_component.py | 25 +++++++ 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 components/dash-core-components/src/fragments/Loading/spinners/ClipSpinner.tsx diff --git a/components/dash-core-components/src/components/Loading.tsx b/components/dash-core-components/src/components/Loading.tsx index 127b6dfacb..c9c3d31511 100644 --- a/components/dash-core-components/src/components/Loading.tsx +++ b/components/dash-core-components/src/components/Loading.tsx @@ -6,6 +6,7 @@ import DefaultSpinner from '../fragments/Loading/spinners/DefaultSpinner'; import CubeSpinner from '../fragments/Loading/spinners/CubeSpinner'; import CircleSpinner from '../fragments/Loading/spinners/CircleSpinner'; import DotSpinner from '../fragments/Loading/spinners/DotSpinner'; +import ClipSpinner from '../fragments/Loading/spinners/ClipSpinner'; import {LoadingProps} from 'src/types'; import {DebugTitleProps} from '../fragments/Loading/types'; import {DashLayoutPath} from '@dash-renderer/types/component'; @@ -15,6 +16,7 @@ const spinnerComponentOptions = { cube: CubeSpinner, circle: CircleSpinner, dot: DotSpinner, + clip: ClipSpinner, default: DefaultSpinner, } as const; diff --git a/components/dash-core-components/src/fragments/Loading/spinners/ClipSpinner.tsx b/components/dash-core-components/src/fragments/Loading/spinners/ClipSpinner.tsx new file mode 100644 index 0000000000..1f615a78d4 --- /dev/null +++ b/components/dash-core-components/src/fragments/Loading/spinners/ClipSpinner.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import DebugTitle from './DebugTitle'; +import {SpinnerProps} from '../types'; + +/** + * Spinner created by David Hu, https://github.com/davidhu2000/react-spinners + */ +const ClipSpinner = ({ + status, + color, + fullscreen, + debug, + className, + style, +}: SpinnerProps) => { + let debugTitle; + if (debug && status) { + debugTitle = status.map((s, i) => ); + } + let spinnerClass = fullscreen ? 'dash-spinner-container' : ''; + if (className) { + spinnerClass += ` ${className}`; + } + return ( +
+ {debugTitle} +
+ +
+ ); +}; + +export default ClipSpinner; diff --git a/components/dash-core-components/src/types.ts b/components/dash-core-components/src/types.ts index da6d6def97..ff0697096b 100644 --- a/components/dash-core-components/src/types.ts +++ b/components/dash-core-components/src/types.ts @@ -1077,9 +1077,9 @@ export interface LoadingProps extends BaseDccProps { /** * Property that determines which built-in spinner to show - * one of 'graph', 'cube', 'circle', 'dot', or 'default'. + * one of 'graph', 'cube', 'circle', 'dot', 'clip', or 'default'. */ - type?: 'graph' | 'cube' | 'circle' | 'dot' | 'default'; + type?: 'graph' | 'cube' | 'circle' | 'dot' | 'clip' | 'default'; /** * Boolean that makes the built-in spinner display full-screen diff --git a/components/dash-core-components/tests/integration/loading/test_loading_component.py b/components/dash-core-components/tests/integration/loading/test_loading_component.py index dd7946e7e8..6b7c7a097d 100644 --- a/components/dash-core-components/tests/integration/loading/test_loading_component.py +++ b/components/dash-core-components/tests/integration/loading/test_loading_component.py @@ -886,3 +886,28 @@ def updateDiv(n_clicks): dash_dcc.wait_for_text_to_equal(".div-1", "changed") assert dash_dcc.get_logs() == [] + + +def test_ldcp020_loading_component_clip_spinner(dash_dcc): + app = Dash(__name__) + + app.layout = html.Div( + dcc.Loading( + html.Div("content"), + type="clip", + color="rgb(255, 0, 0)", + display="show", + className="loading", + ) + ) + + dash_dcc.start_server(app) + + spinner = ".loading .dash-spinner.dash-clip-spinner" + dash_dcc.wait_for_style_to_equal(spinner, "border-top-color", "rgba(255, 0, 0, 1)") + dash_dcc.wait_for_style_to_equal(spinner, "border-bottom-color", "rgba(0, 0, 0, 0)") + dash_dcc.wait_for_style_to_equal( + spinner, "animation-name", "dash-clip-spinner-rotate" + ) + + assert dash_dcc.get_logs() == [] From 5929f79587e7135bb03961687522c12d5bb1f5a6 Mon Sep 17 00:00:00 2001 From: Osakpolor Omonbude Date: Tue, 20 Oct 2026 14:29:16 +0100 Subject: [PATCH 2/2] Replace circle spinner with rotating ring instead of adding clip type --- CHANGELOG.md | 1 + .../src/components/Loading.tsx | 2 - .../Loading/spinners/CircleSpinner.tsx | 144 ++---------------- .../Loading/spinners/ClipSpinner.tsx | 73 --------- components/dash-core-components/src/types.ts | 4 +- .../loading/test_loading_component.py | 8 +- .../renderer/test_loading_states.py | 2 +- 7 files changed, 24 insertions(+), 210 deletions(-) delete mode 100644 components/dash-core-components/src/fragments/Loading/spinners/ClipSpinner.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index f31991146e..975a02f1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#3646](https://github.com/plotly/dash/pull/3646) Remove React 16 support (`16.14.0` is no longer an accepted value for `REACT_VERSION` / `_set_react_version`). ### Changed +- [#4015](https://github.com/plotly/dash/pull/4015) Replace the `circle` spinner in `dcc.Loading` with a rotating ring, ported from react-spinners. Apps using `type="circle"` will see the new spinner. - [#3987](https://github.com/plotly/dash/pull/3987) Forward FastAPI reload scope options (`reload_dirs`, `reload_excludes`, and `reload_includes`) to Uvicorn when reloading. - [#3986](https://github.com/plotly/dash/pull/3986) Adjust `_run_before_hooks` in the `fastapi` backend to honor a response returned by a `before_request` function, matching the `flask` backend's behavior. diff --git a/components/dash-core-components/src/components/Loading.tsx b/components/dash-core-components/src/components/Loading.tsx index c9c3d31511..127b6dfacb 100644 --- a/components/dash-core-components/src/components/Loading.tsx +++ b/components/dash-core-components/src/components/Loading.tsx @@ -6,7 +6,6 @@ import DefaultSpinner from '../fragments/Loading/spinners/DefaultSpinner'; import CubeSpinner from '../fragments/Loading/spinners/CubeSpinner'; import CircleSpinner from '../fragments/Loading/spinners/CircleSpinner'; import DotSpinner from '../fragments/Loading/spinners/DotSpinner'; -import ClipSpinner from '../fragments/Loading/spinners/ClipSpinner'; import {LoadingProps} from 'src/types'; import {DebugTitleProps} from '../fragments/Loading/types'; import {DashLayoutPath} from '@dash-renderer/types/component'; @@ -16,7 +15,6 @@ const spinnerComponentOptions = { cube: CubeSpinner, circle: CircleSpinner, dot: DotSpinner, - clip: ClipSpinner, default: DefaultSpinner, } as const; diff --git a/components/dash-core-components/src/fragments/Loading/spinners/CircleSpinner.tsx b/components/dash-core-components/src/fragments/Loading/spinners/CircleSpinner.tsx index 36d3320910..5007df564c 100644 --- a/components/dash-core-components/src/fragments/Loading/spinners/CircleSpinner.tsx +++ b/components/dash-core-components/src/fragments/Loading/spinners/CircleSpinner.tsx @@ -3,7 +3,7 @@ import DebugTitle from './DebugTitle'; import {SpinnerProps} from '../types'; /** - * Spinner created by Tobias Ahlin, https://github.com/tobiasahlin/SpinKit + * Spinner created by David Hu, https://github.com/davidhu2000/react-spinners */ const CircleSpinner = ({ status, @@ -24,20 +24,7 @@ const CircleSpinner = ({ return (
{debugTitle} -
-
-
-
-
-
-
-
-
-
-
-
-
-
+
); diff --git a/components/dash-core-components/src/fragments/Loading/spinners/ClipSpinner.tsx b/components/dash-core-components/src/fragments/Loading/spinners/ClipSpinner.tsx deleted file mode 100644 index 1f615a78d4..0000000000 --- a/components/dash-core-components/src/fragments/Loading/spinners/ClipSpinner.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import DebugTitle from './DebugTitle'; -import {SpinnerProps} from '../types'; - -/** - * Spinner created by David Hu, https://github.com/davidhu2000/react-spinners - */ -const ClipSpinner = ({ - status, - color, - fullscreen, - debug, - className, - style, -}: SpinnerProps) => { - let debugTitle; - if (debug && status) { - debugTitle = status.map((s, i) => ); - } - let spinnerClass = fullscreen ? 'dash-spinner-container' : ''; - if (className) { - spinnerClass += ` ${className}`; - } - return ( -
- {debugTitle} -
- -
- ); -}; - -export default ClipSpinner; diff --git a/components/dash-core-components/src/types.ts b/components/dash-core-components/src/types.ts index ff0697096b..da6d6def97 100644 --- a/components/dash-core-components/src/types.ts +++ b/components/dash-core-components/src/types.ts @@ -1077,9 +1077,9 @@ export interface LoadingProps extends BaseDccProps { /** * Property that determines which built-in spinner to show - * one of 'graph', 'cube', 'circle', 'dot', 'clip', or 'default'. + * one of 'graph', 'cube', 'circle', 'dot', or 'default'. */ - type?: 'graph' | 'cube' | 'circle' | 'dot' | 'clip' | 'default'; + type?: 'graph' | 'cube' | 'circle' | 'dot' | 'default'; /** * Boolean that makes the built-in spinner display full-screen diff --git a/components/dash-core-components/tests/integration/loading/test_loading_component.py b/components/dash-core-components/tests/integration/loading/test_loading_component.py index 6b7c7a097d..7fe1f94e3a 100644 --- a/components/dash-core-components/tests/integration/loading/test_loading_component.py +++ b/components/dash-core-components/tests/integration/loading/test_loading_component.py @@ -888,13 +888,13 @@ def updateDiv(n_clicks): assert dash_dcc.get_logs() == [] -def test_ldcp020_loading_component_clip_spinner(dash_dcc): +def test_ldcp020_loading_component_circle_spinner(dash_dcc): app = Dash(__name__) app.layout = html.Div( dcc.Loading( html.Div("content"), - type="clip", + type="circle", color="rgb(255, 0, 0)", display="show", className="loading", @@ -903,11 +903,11 @@ def test_ldcp020_loading_component_clip_spinner(dash_dcc): dash_dcc.start_server(app) - spinner = ".loading .dash-spinner.dash-clip-spinner" + spinner = ".loading .dash-spinner.dash-circle-spinner" dash_dcc.wait_for_style_to_equal(spinner, "border-top-color", "rgba(255, 0, 0, 1)") dash_dcc.wait_for_style_to_equal(spinner, "border-bottom-color", "rgba(0, 0, 0, 0)") dash_dcc.wait_for_style_to_equal( - spinner, "animation-name", "dash-clip-spinner-rotate" + spinner, "animation-name", "dash-circle-spinner-rotate" ) assert dash_dcc.get_logs() == [] diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index 9818902f61..8b295ebf00 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -50,7 +50,7 @@ def input_triggers_nested(value): def wait_for_all_spinners(): dash_duo.find_element(".loading-1 .dash-spinner.dash-default-spinner") - dash_duo.find_element(".loading-2 .dash-spinner.dash-sk-circle") + dash_duo.find_element(".loading-2 .dash-spinner.dash-circle-spinner") dash_duo.find_element(".loading-3 .dash-spinner.dash-cube-container") def wait_for_no_spinners():