Skip to content

Commit 645cd2b

Browse files
glsdowntcbegley
andauthored
Add new Stack component (#897)
* Add new Stack component and js test * Add Stack documentation Co-authored-by: Tom Begley <[email protected]>
1 parent 13d7134 commit 645cd2b

File tree

7 files changed

+237
-0
lines changed

7 files changed

+237
-0
lines changed

docs/components_page/components/layout.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,24 @@ Sometimes you may wish to use Bootstrap's grid system for specifying the layout
8888

8989
Alternatively download `bootstrap-grid.css` from the [Bootstrap website](https://getbootstrap.com/docs/5.1/getting-started/download/) and place it in your app's `assets/` directory. See the [Plotly Dash documentation](https://dash.plotly.com/external-resources) for details.
9090

91+
## `Stack`ing objects
92+
93+
You can make use of the `Stack` component to neatly arrange objects inside a vertical (default) stack. You can set the `gap` property between 0-5 to create an even gap between the objects.
94+
95+
{{example:components/layout/simple_stack.py:stack}}
96+
97+
You can also use `direction="horizontal"` for horizontal layouts.
98+
99+
{{example:components/layout/horizontal_stack.py:stack}}
100+
101+
You can combine these with the in-built [Bootstrap spacing](https://getbootstrap.com/docs/5.0/utilities/spacing/) options to improve your layouts even further.
102+
103+
{{example:components/layout/stack_spacers.py:stack}}
104+
91105
{{apidoc:src/components/layout/Container.js}}
92106

93107
{{apidoc:src/components/layout/Row.js}}
94108

95109
{{apidoc:src/components/layout/Col.js}}
110+
111+
{{apidoc:src/components/layout/Stack.js}}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import dash_bootstrap_components as dbc
2+
from dash import html
3+
4+
stack = html.Div(
5+
[
6+
dbc.Stack(
7+
[
8+
html.Div("Horizontal", className="bg-light border"),
9+
html.Div("Stack", className="bg-light border"),
10+
html.Div("Without", className="bg-light border"),
11+
html.Div("Gaps", className="bg-light border"),
12+
],
13+
direction="horizontal",
14+
),
15+
html.Hr(),
16+
dbc.Stack(
17+
[
18+
html.Div("Horizontal", className="bg-light border"),
19+
html.Div("Stack", className="bg-light border"),
20+
html.Div("With", className="bg-light border"),
21+
html.Div("Gaps", className="bg-light border"),
22+
],
23+
direction="horizontal",
24+
gap=3,
25+
),
26+
]
27+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import dash_bootstrap_components as dbc
2+
from dash import html
3+
4+
stack = html.Div(
5+
[
6+
dbc.Stack(
7+
[
8+
html.Div(
9+
"This stack has no gaps", className="bg-light border"
10+
),
11+
html.Div("Next item", className="bg-light border"),
12+
html.Div("Last item", className="bg-light border"),
13+
]
14+
),
15+
html.Hr(),
16+
dbc.Stack(
17+
[
18+
html.Div("This stack has gaps", className="bg-light border"),
19+
html.Div("Next item", className="bg-light border"),
20+
html.Div("Last item", className="bg-light border"),
21+
],
22+
gap=3,
23+
),
24+
]
25+
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import dash_bootstrap_components as dbc
2+
from dash import html
3+
4+
stack = html.Div(
5+
[
6+
dbc.Stack(
7+
[
8+
html.Div("Start", className="bg-light border"),
9+
html.Div(
10+
"Middle (ms-auto)", className="ms-auto bg-light border"
11+
),
12+
html.Div("End", className="bg-light border"),
13+
],
14+
direction="horizontal",
15+
gap=3,
16+
),
17+
html.Hr(),
18+
dbc.Stack(
19+
[
20+
html.Div("Start", className="bg-light border"),
21+
html.Div(
22+
"Middle (mx-auto)", className="mx-auto bg-light border"
23+
),
24+
html.Div("End", className="bg-light border"),
25+
],
26+
direction="horizontal",
27+
gap=3,
28+
),
29+
]
30+
)

src/components/layout/Stack.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import RBStack from 'react-bootstrap/Stack';
4+
import {omit} from 'ramda';
5+
6+
/**
7+
* Stacks are shorthand helpers that build on top of existing flexbox
8+
* utilities to make component layout faster and easier than ever.
9+
*/
10+
const Stack = props => {
11+
const {children, loading_state, className, class_name, ...otherProps} = props;
12+
return (
13+
<RBStack
14+
className={class_name || className}
15+
{...omit(['setProps'], otherProps)}
16+
data-dash-is-loading={
17+
(loading_state && loading_state.is_loading) || undefined
18+
}
19+
>
20+
{children}
21+
</RBStack>
22+
);
23+
};
24+
25+
Stack.defaultPropTypes = {
26+
direction: 'vertical'
27+
};
28+
29+
Stack.propTypes = {
30+
/**
31+
* The ID of this component, used to identify dash components
32+
* in callbacks. The ID needs to be unique across all of the
33+
* components in an app.
34+
*/
35+
id: PropTypes.string,
36+
37+
/**
38+
* The children of this component
39+
*/
40+
children: PropTypes.node,
41+
42+
/**
43+
* Defines CSS styles which will override styles previously set.
44+
*/
45+
style: PropTypes.object,
46+
47+
/**
48+
* Often used with CSS to style elements with common properties.
49+
*/
50+
class_name: PropTypes.string,
51+
52+
/**
53+
* **DEPRECATED** Use `class_name` instead.
54+
*
55+
* Often used with CSS to style elements with common properties.
56+
*/
57+
className: PropTypes.string,
58+
59+
/**
60+
* A unique identifier for the component, used to improve
61+
* performance by React.js while rendering components
62+
* See https://reactjs.org/docs/lists-and-keys.html for more info
63+
*/
64+
key: PropTypes.string,
65+
66+
/**
67+
* Object that holds the loading state object coming from dash-renderer
68+
*/
69+
loading_state: PropTypes.shape({
70+
/**
71+
* Determines if the component is loading or not
72+
*/
73+
is_loading: PropTypes.bool,
74+
/**
75+
* Holds which property is loading
76+
*/
77+
prop_name: PropTypes.string,
78+
/**
79+
* Holds the name of the component that is loading
80+
*/
81+
component_name: PropTypes.string
82+
}),
83+
84+
/**
85+
* Which direction to stack the objects in
86+
*/
87+
direction: PropTypes.oneOf(['vertical', 'horizontal']),
88+
89+
/**
90+
* Set the spacing between each item (0 - 5)
91+
*/
92+
gap: PropTypes.number
93+
};
94+
95+
export default Stack;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
5+
import React from 'react';
6+
import {render} from '@testing-library/react';
7+
import Stack from '../Stack';
8+
9+
describe('Stack', () => {
10+
test('renders a div with class "vstack"', () => {
11+
const stack = render(<Stack />);
12+
13+
expect(stack.container.querySelector('div.vstack')).not.toBe(null);
14+
});
15+
16+
test('renders its content', () => {
17+
const stack = render(
18+
<Stack>
19+
<div>First item</div>
20+
<div>Second item</div>
21+
<div>Third item</div>
22+
</Stack>
23+
);
24+
25+
expect(stack.container).toHaveTextContent('First item');
26+
expect(stack.container).toHaveTextContent('Second item');
27+
expect(stack.container).toHaveTextContent('Third item');
28+
});
29+
30+
test('renders a div with class hstack if direction is "horizontal"', () => {
31+
const {
32+
container: {firstChild: stack}
33+
} = render(<Stack direction="horizontal" />);
34+
35+
expect(stack).toHaveClass('hstack');
36+
});
37+
38+
test('gap changes the gap class of the object', () => {
39+
const stack = render(<Stack gap={3} />);
40+
41+
expect(stack.container.querySelector('div.gap-3')).not.toBe(null);
42+
});
43+
});

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export {default as RadioButton} from './components/input/RadioButton';
5555
export {default as Row} from './components/layout/Row';
5656
export {default as Select} from './components/input/Select';
5757
export {default as Spinner} from './components/spinner/Spinner';
58+
export {default as Stack} from './components/layout/Stack';
5859
export {default as Switch} from './components/input/Switch';
5960
export {default as Tab} from './components/tabs/Tab';
6061
export {default as Tabs} from './components/tabs/Tabs';

0 commit comments

Comments
 (0)