diff --git a/docs/components_page/components/spinner/__init__.py b/docs/components_page/components/spinner/__init__.py index 464f7d362..253551bbd 100644 --- a/docs/components_page/components/spinner/__init__.py +++ b/docs/components_page/components/spinner/__init__.py @@ -4,7 +4,11 @@ import dash_html_components as html from ...api_doc import ApiDoc -from ...helpers import ExampleContainer, HighlightedSource +from ...helpers import ( + ExampleContainer, + HighlightedSource, + load_source_with_environment, +) from ...metadata import get_component_metadata from .button import spinners as spinners_button from .grow import spinners as spinners_grow @@ -14,59 +18,96 @@ HERE = Path(__file__).parent spinners_simple_source = (HERE / "simple.py").read_text() +spinners_loading_source = (HERE / "loading.py").read_text() spinners_grow_source = (HERE / "grow.py").read_text() spinners_size_source = (HERE / "size.py").read_text() spinners_button_source = (HERE / "button.py").read_text() -content = [ - html.H2("Spinners", className="display-4"), - html.P( - dcc.Markdown( - "Indicate the loading state of a component or page with the " - "`Spinner` component." + +def get_content(app): + return [ + html.H2("Spinners", className="display-4"), + html.P( + dcc.Markdown( + "Indicate the loading state of a component or page with the " + "`Spinner` component." + ), + className="lead", + ), + html.P( + dcc.Markdown( + "The `Spinner` component can be used either to create a " + "standalone spinner, or used in the same way as [dcc.Loading]" + "(https://dash.plot.ly/dash-core-components/loading) by " + "passing children." + ) + ), + html.H4("Basic usage"), + html.P( + dcc.Markdown( + "To create a simple spinner, just add `dbc.Spinner()` to your " + "layout. By default, `Spinner` uses the current text color " + "for its border color. Override the color of the `Spinner` " + "using the `color` argument and one of the eight supported " + "contextual color names." + ) + ), + ExampleContainer(spinners_simple), + HighlightedSource(spinners_simple_source), + html.H4("Loading component"), + html.P( + dcc.Markdown( + "If you pass children to `dbc.Spinner`, it will behave like " + "`dcc.Loading`, which is to say it will render a spinner " + "until the children component have loaded." + ), + ), + html.P( + dcc.Markdown( + "The spinner is rendered inside a `html.Div`. The `html.Div` " + "that the spinner is rendered in will expand to fill the " + "available width, and add a top and bottom margin. This can " + "be overridden using `spinner_style` or `spinnerClassName`. " + "Alter" + ) + ), + ExampleContainer( + load_source_with_environment( + spinners_loading_source, "loading_spinner", {"app": app} + ) + ), + HighlightedSource(spinners_loading_source), + html.H4("Growing spinners"), + html.P( + dcc.Markdown( + "There are two types of spinner, border and grow. Border " + "spinners are the default and can be seen above. To use grow " + 'spinners set `type="grow"`.' + ) + ), + ExampleContainer(spinners_grow), + HighlightedSource(spinners_grow_source), + html.H4("Size"), + html.P( + dcc.Markdown( + 'Create a small spinner with `size="sm"` or use inline style ' + "arguments for full control of the size of the spinner." + ) + ), + ExampleContainer(spinners_size), + HighlightedSource(spinners_size_source), + html.H4("Buttons"), + html.P( + dcc.Markdown( + "The `Spinner` component can be used inside buttons to " + "indicate that an action is currently processing or taking " + "place." + ) + ), + ExampleContainer(spinners_button), + HighlightedSource(spinners_button_source), + ApiDoc( + get_component_metadata("src/components/Spinner.js"), + component_name="Spinner", ), - className="lead", - ), - html.H4("Basic usage"), - html.P( - dcc.Markdown( - "By default, `Spinner` uses the current text color for its border " - "color. Override the color of the `Spinner` using the `color` " - "argument and one of the eight supported contextual color names." - ) - ), - ExampleContainer(spinners_simple), - HighlightedSource(spinners_simple_source), - html.H4("Growing spinners"), - html.P( - dcc.Markdown( - "There are two types of spinner, border and grow. Border spinners " - "are the default and can be seen above. To use grow spinners set " - '`type="grow"`.' - ) - ), - ExampleContainer(spinners_grow), - HighlightedSource(spinners_grow_source), - html.H4("Size"), - html.P( - dcc.Markdown( - 'Create a small spinner with `size="sm"` or use inline style ' - "arguments for full control of the size of the spinner." - ) - ), - ExampleContainer(spinners_size), - HighlightedSource(spinners_size_source), - html.H4("Buttons"), - html.P( - dcc.Markdown( - "The `Spinner` component can be used inside buttons to indicate " - "that an action is currently processing or taking place." - ) - ), - ExampleContainer(spinners_button), - HighlightedSource(spinners_button_source), - ApiDoc( - get_component_metadata("src/components/Spinner.js"), - component_name="Spinner", - ), -] + ] diff --git a/docs/components_page/components/spinner/loading.py b/docs/components_page/components/spinner/loading.py new file mode 100644 index 000000000..d3eeea8e6 --- /dev/null +++ b/docs/components_page/components/spinner/loading.py @@ -0,0 +1,22 @@ +import time + +import dash_bootstrap_components as dbc +import dash_html_components as html +from dash.dependencies import Input, Output + +loading_spinner = html.Div( + [ + dbc.Button("Load", id="loading-button"), + dbc.Spinner(html.Div(id="loading-output")), + ] +) + + +@app.callback( + Output("loading-output", "children"), [Input("loading-button", "n_clicks")] +) +def load_output(n): + if n: + time.sleep(1) + return f"Output loaded {n} times" + return "Output not reloaded yet" diff --git a/docs/components_page/page.py b/docs/components_page/page.py index 0e88b6578..09d4a0244 100644 --- a/docs/components_page/page.py +++ b/docs/components_page/page.py @@ -21,7 +21,7 @@ from .components.navbar import get_content as get_navbar_content from .components.popover import get_content as get_popover_content from .components.progress import get_content as get_progress_content -from .components.spinner import content as spinner_content +from .components.spinner import get_content as get_spinner_content from .components.table import content as table_content from .components.tabs import get_content as get_tabs_content from .components.toast import get_content as get_toast_content @@ -103,7 +103,7 @@ def __init__(self, app): "navbar": get_navbar_content(self._app), "popover": get_popover_content(self._app), "progress": get_progress_content(self._app), - "spinner": spinner_content, + "spinner": get_spinner_content(self._app), "table": table_content, "tabs": get_tabs_content(self._app), "toast": get_toast_content(self._app), diff --git a/setup.cfg b/setup.cfg index d80d992e1..03bcd36bb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ exclude = docs/components_page/components/popover/popover.py, docs/components_page/components/progress/animated.py, docs/components_page/components/progress/interval.py, + docs/components_page/components/spinner/loading.py, docs/components_page/components/table/kwargs.py, docs/components_page/components/tabs/active_tab.py, docs/components_page/components/tabs/card.py, diff --git a/src/components/Spinner.js b/src/components/Spinner.js index ca8f64f23..755a049a3 100644 --- a/src/components/Spinner.js +++ b/src/components/Spinner.js @@ -1,13 +1,64 @@ -import React from 'react'; +import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; -import {omit} from 'ramda'; +import {omit, type} from 'ramda'; import {Spinner as RSSpinner} from 'reactstrap'; const Spinner = props => { - const {children, ...otherProps} = props; - return {children}; + const { + children, + loading_state, + spinner_style, + fullscreen, + ...otherProps + } = props; + // this spacing is consistent with the behaviour of dcc.Loading + // it can be overridden with spinnerStyle + let defaultSpinnerStyle = children + ? {display: 'block', margin: '1rem auto'} + : {}; + + const spinnerStyle = children + ? {...defaultSpinnerStyle, ...spinner_style} + : spinner_style; + if (!children || (loading_state && loading_state.is_loading)) { + if (fullscreen) { + return ( +
+ +
+ ); + } + return ( + + ); + } + if (type(children) !== 'Object' || type(children) !== 'Function') { + return {children}; + } + return children; }; +Spinner._dashprivate_isLoadingComponent = true; + Spinner.propTypes = { /** * The ID of this component, used to identify dash components @@ -26,11 +77,21 @@ Spinner.propTypes = { */ style: PropTypes.object, + /** + * Inline CSS styles to apply to the spinner. + */ + spinner_style: PropTypes.object, + /** * Often used with CSS to style elements with common properties. */ className: PropTypes.string, + /** + * CSS class names to apply to the spinner. + */ + spinnerClassName: PropTypes.string, + /** * Spinner color, options: primary, secondary, success, info, warning, danger, * link. If not specified will default to text colour. @@ -45,7 +106,13 @@ Spinner.propTypes = { /** * The spinner size. Options are 'sm', 'md' and 'lg'. */ - size: PropTypes.string + size: PropTypes.string, + + /** + * Boolean that determines if the loading spinner will be displayed + * full-screen or not. + */ + fullscreen: PropTypes.bool }; export default Spinner;