Skip to content

Commit 2f648c1

Browse files
TheSharpieOneeddywashere
authored andcommitted
feat(Uncontrolled): add uncontrolled components (#307)
- UncontrolledAlert - UncontrolledButtonDropdown - UncontrolledDropdown - UncontrolledNavDropdown - UncontrolledTooltip
1 parent 8487598 commit 2f648c1

9 files changed

Lines changed: 293 additions & 0 deletions

File tree

docs/lib/Components/AlertsPage.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const AlertExampleSource = require('!!raw!../examples/Alert');
1010
import AlertDismissExample from '../examples/AlertDismiss';
1111
const AlertDismissExampleSource = require('!!raw!../examples/AlertDismiss');
1212

13+
import AlertUncontrolledDismissExample from '../examples/AlertUncontrolledDismiss';
14+
const AlertUncontrolledDismissExampleSource = require('!!raw!../examples/AlertUncontrolledDismiss');
15+
1316
export default class AlertsPage extends React.Component {
1417
render() {
1518
return (
@@ -53,6 +56,19 @@ export default class AlertsPage extends React.Component {
5356
{AlertDismissExampleSource}
5457
</PrismCode>
5558
</pre>
59+
60+
<h3>Uncontrolled [disable] Alerts</h3>
61+
<p>
62+
For the most basic use-case an uncontrolled component can provide the functionality wanted without the need to manage/control the state of the component. <code>UncontrolledAlert</code> does not require <code>isOpen</code> nor <code>toggle</code> props to work.
63+
</p>
64+
<div className="docs-example">
65+
<AlertUncontrolledDismissExample />
66+
</div>
67+
<pre>
68+
<PrismCode className="language-jsx">
69+
{AlertUncontrolledDismissExampleSource}
70+
</PrismCode>
71+
</pre>
5672
</div>
5773
);
5874
}

docs/lib/Components/DropdownsPage.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import {
1111
import DropdownExample from '../examples/Dropdown';
1212
import DropdownSizingExample from '../examples/DropdownSizing';
1313
import CustomDropdownExample from '../examples/CustomDropdown';
14+
import DropdownUncontrolledExample from '../examples/DropdownUncontrolled';
1415

1516
const DropdownExampleSource = require('!!raw!../examples/Dropdown');
1617
const CustomDropdownExampleSource = require('!!raw!../examples/CustomDropdown');
18+
const DropdownUncontrolledExampleSource = require('!!raw!../examples/DropdownUncontrolled');
1719

1820
export default class DropdownPage extends React.Component {
1921
constructor(props) {
@@ -184,6 +186,19 @@ DropdownToggle.propTypes = {
184186
{CustomDropdownExampleSource}
185187
</PrismCode>
186188
</pre>
189+
190+
<h3>Uncontrolled Dropdown</h3>
191+
<p>
192+
For the most basic use-case an uncontrolled component can provide the functionality wanted without the need to manage/control the state of the component. <code>UncontrolledDropdown</code> does not require <code>isOpen</code> nor <code>toggle</code> props to work. For the other Dropdown flavors, <code>ButtonDropdown</code>, <code>NavDropdown</code>, uncontrolled components have been made as well; <code>UncontrolledButtonDropdown</code>, <code>UncontrolledNavDropdown</code> respectfully.
193+
</p>
194+
<div className="docs-example">
195+
<DropdownUncontrolledExample />
196+
</div>
197+
<pre>
198+
<PrismCode className="language-jsx">
199+
{DropdownUncontrolledExampleSource}
200+
</PrismCode>
201+
</pre>
187202
</div>
188203
);
189204
}

docs/lib/Components/TooltipsPage.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import TooltipAutoHideExample from '../examples/TooltipAutoHide';
88
const TooltipExampleAutoHideSource = require('!!raw!../examples/TooltipAutoHide');
99
import TooltipExampleMulti from '../examples/TooltipMulti';
1010
const TooltipExampleMultiSource = require('!!raw!../examples/TooltipMulti');
11+
import TooltipExampleUncontrolled from '../examples/TooltipUncontrolled';
12+
const TooltipExampleUncontrolledSource = require('!!raw!../examples/TooltipUncontrolled');
1113

1214
export default class TooltipsPage extends React.Component {
1315
render() {
@@ -86,6 +88,18 @@ export default class TooltipsPage extends React.Component {
8688
{TooltipExampleMultiSource}
8789
</PrismCode>
8890
</pre>
91+
<h3>Uncontrolled Tooltip</h3>
92+
<p>
93+
For the most basic use-case an uncontrolled component can provide the functionality wanted without the need to manage/control the state of the component. <code>UncontrolledTooltip</code> does not require <code>isOpen</code> nor <code>toggle</code> props to work.
94+
</p>
95+
<div className="docs-example">
96+
<TooltipExampleUncontrolled />
97+
</div>
98+
<pre>
99+
<PrismCode className="language-jsx">
100+
{TooltipExampleUncontrolledSource}
101+
</PrismCode>
102+
</pre>
89103
</div>
90104
);
91105
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import { UncontrolledAlert } from 'reactstrap';
3+
4+
function AlertExample() {
5+
return (
6+
<UncontrolledAlert color="info">
7+
I am an alert and I can be dismissed!
8+
</UncontrolledAlert>
9+
);
10+
}
11+
12+
export default AlertExample;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import { UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
3+
4+
export default function Example () {
5+
return (
6+
<UncontrolledDropdown>
7+
<DropdownToggle caret>
8+
Dropdown
9+
</DropdownToggle>
10+
<DropdownMenu>
11+
<DropdownItem header>Header</DropdownItem>
12+
<DropdownItem disabled>Action</DropdownItem>
13+
<DropdownItem>Another Action</DropdownItem>
14+
<DropdownItem divider />
15+
<DropdownItem>Another Action</DropdownItem>
16+
</DropdownMenu>
17+
</UncontrolledDropdown>
18+
);
19+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* eslint react/no-multi-comp: 0, react/prop-types: 0 */
2+
import React from 'react';
3+
import { UncontrolledTooltip } from 'reactstrap';
4+
5+
export default function Example() {
6+
return (
7+
<div>
8+
<p>Somewhere in here is a <a href="#" id="UncontrolledTooltipExample">tooltip</a>.</p>
9+
<UncontrolledTooltip placement="right" target="UncontrolledTooltipExample">
10+
Hello world!
11+
</UncontrolledTooltip>
12+
</div>
13+
);
14+
}

src/Uncontrolled.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React, { Component } from 'react';
2+
import Alert from './Alert';
3+
import ButtonDropdown from './ButtonDropdown';
4+
import Dropdown from './Dropdown';
5+
import NavDropdown from './NavDropdown';
6+
import Tooltip from './Tooltip';
7+
8+
const components = {
9+
UncontrolledAlert: Alert,
10+
UncontrolledButtonDropdown: ButtonDropdown,
11+
UncontrolledDropdown: Dropdown,
12+
UncontrolledNavDropdown: NavDropdown,
13+
UncontrolledTooltip: Tooltip,
14+
};
15+
16+
Object.keys(components).forEach(key => {
17+
const Tag = components[key];
18+
const defaultValue = Tag === Alert;
19+
20+
class Uncontrolled extends Component {
21+
constructor(props) {
22+
super(props);
23+
24+
this.state = { isOpen: defaultValue };
25+
26+
this.toggle = this.toggle.bind(this);
27+
}
28+
29+
toggle() {
30+
this.setState({ isOpen: !this.state.isOpen });
31+
}
32+
33+
render() {
34+
return <Tag isOpen={this.state.isOpen} toggle={this.toggle} {...this.props} />;
35+
}
36+
}
37+
38+
Uncontrolled.displayName = key;
39+
40+
components[key] = Uncontrolled;
41+
});
42+
43+
const UncontrolledAlert = components.UncontrolledAlert;
44+
const UncontrolledButtonDropdown = components.UncontrolledButtonDropdown;
45+
const UncontrolledDropdown = components.UncontrolledDropdown;
46+
const UncontrolledNavDropdown = components.UncontrolledNavDropdown;
47+
const UncontrolledTooltip = components.UncontrolledTooltip;
48+
49+
export {
50+
UncontrolledAlert,
51+
UncontrolledButtonDropdown,
52+
UncontrolledDropdown,
53+
UncontrolledNavDropdown,
54+
UncontrolledTooltip,
55+
};

src/__tests__/Uncontrolled.spec.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import Alert from '../Alert';
4+
import ButtonDropdown from '../ButtonDropdown';
5+
import Dropdown from '../Dropdown';
6+
import NavDropdown from '../NavDropdown';
7+
import Tooltip from '../Tooltip';
8+
import {
9+
UncontrolledAlert,
10+
UncontrolledButtonDropdown,
11+
UncontrolledDropdown,
12+
UncontrolledNavDropdown,
13+
UncontrolledTooltip,
14+
} from '../Uncontrolled';
15+
16+
describe('UncontrolledAlert', () => {
17+
it('should be an Alert', () => {
18+
const alert = shallow(<UncontrolledAlert>Yo!</UncontrolledAlert>);
19+
expect(alert.type()).toBe(Alert);
20+
});
21+
22+
it('should have isOpen default to true', () => {
23+
const alert = shallow(<UncontrolledAlert>Yo!</UncontrolledAlert>);
24+
expect(alert.prop('isOpen')).toBe(true);
25+
});
26+
27+
it('should have toggle function', () => {
28+
const alert = shallow(<UncontrolledAlert>Yo!</UncontrolledAlert>);
29+
expect(alert.prop('toggle')).toEqual(jasmine.any(Function));
30+
});
31+
32+
it('should toggle isOpen when toggle is called', () => {
33+
const alert = shallow(<UncontrolledAlert>Yo!</UncontrolledAlert>);
34+
const instance = alert.instance();
35+
instance.toggle();
36+
expect(alert.prop('isOpen')).toBe(false);
37+
});
38+
});
39+
40+
describe('UncontrolledButtonDropdown', () => {
41+
it('should be an ButtonDropdown', () => {
42+
const buttonDropdown = shallow(<UncontrolledButtonDropdown>Yo!</UncontrolledButtonDropdown>);
43+
expect(buttonDropdown.type()).toBe(ButtonDropdown);
44+
});
45+
46+
it('should have isOpen default to false', () => {
47+
const buttonDropdown = shallow(<UncontrolledButtonDropdown>Yo!</UncontrolledButtonDropdown>);
48+
expect(buttonDropdown.prop('isOpen')).toBe(false);
49+
});
50+
51+
it('should have toggle function', () => {
52+
const buttonDropdown = shallow(<UncontrolledButtonDropdown>Yo!</UncontrolledButtonDropdown>);
53+
expect(buttonDropdown.prop('toggle')).toEqual(jasmine.any(Function));
54+
});
55+
56+
it('should toggle isOpen when toggle is called', () => {
57+
const buttonDropdown = shallow(<UncontrolledButtonDropdown>Yo!</UncontrolledButtonDropdown>);
58+
const instance = buttonDropdown.instance();
59+
instance.toggle();
60+
expect(buttonDropdown.prop('isOpen')).toBe(true);
61+
});
62+
});
63+
64+
describe('UncontrolledDropdown', () => {
65+
it('should be an Dropdown', () => {
66+
const dropdown = shallow(<UncontrolledDropdown>Yo!</UncontrolledDropdown>);
67+
expect(dropdown.type()).toBe(Dropdown);
68+
});
69+
70+
it('should have isOpen default to false', () => {
71+
const dropdown = shallow(<UncontrolledDropdown>Yo!</UncontrolledDropdown>);
72+
expect(dropdown.prop('isOpen')).toBe(false);
73+
});
74+
75+
it('should have toggle function', () => {
76+
const dropdown = shallow(<UncontrolledDropdown>Yo!</UncontrolledDropdown>);
77+
expect(dropdown.prop('toggle')).toEqual(jasmine.any(Function));
78+
});
79+
80+
it('should toggle isOpen when toggle is called', () => {
81+
const dropdown = shallow(<UncontrolledDropdown>Yo!</UncontrolledDropdown>);
82+
const instance = dropdown.instance();
83+
instance.toggle();
84+
expect(dropdown.prop('isOpen')).toBe(true);
85+
});
86+
});
87+
88+
describe('UncontrolledNavDropdown', () => {
89+
it('should be an NavDropdown', () => {
90+
const navDropdown = shallow(<UncontrolledNavDropdown>Yo!</UncontrolledNavDropdown>);
91+
expect(navDropdown.type()).toBe(NavDropdown);
92+
});
93+
94+
it('should have isOpen default to false', () => {
95+
const navDropdown = shallow(<UncontrolledNavDropdown>Yo!</UncontrolledNavDropdown>);
96+
expect(navDropdown.prop('isOpen')).toBe(false);
97+
});
98+
99+
it('should have toggle function', () => {
100+
const navDropdown = shallow(<UncontrolledNavDropdown>Yo!</UncontrolledNavDropdown>);
101+
expect(navDropdown.prop('toggle')).toEqual(jasmine.any(Function));
102+
});
103+
104+
it('should toggle isOpen when toggle is called', () => {
105+
const navDropdown = shallow(<UncontrolledNavDropdown>Yo!</UncontrolledNavDropdown>);
106+
const instance = navDropdown.instance();
107+
instance.toggle();
108+
expect(navDropdown.prop('isOpen')).toBe(true);
109+
});
110+
});
111+
112+
describe('UncontrolledTooltip', () => {
113+
it('should be an Tooltip', () => {
114+
const tooltip = shallow(<UncontrolledTooltip target="blah">Yo!</UncontrolledTooltip>);
115+
expect(tooltip.type()).toBe(Tooltip);
116+
});
117+
118+
it('should have isOpen default to false', () => {
119+
const tooltip = shallow(<UncontrolledTooltip target="blah">Yo!</UncontrolledTooltip>);
120+
expect(tooltip.prop('isOpen')).toBe(false);
121+
});
122+
123+
it('should have toggle function', () => {
124+
const tooltip = shallow(<UncontrolledTooltip target="blah">Yo!</UncontrolledTooltip>);
125+
expect(tooltip.prop('toggle')).toEqual(jasmine.any(Function));
126+
});
127+
128+
it('should toggle isOpen when toggle is called', () => {
129+
const tooltip = shallow(<UncontrolledTooltip target="blah">Yo!</UncontrolledTooltip>);
130+
const instance = tooltip.instance();
131+
instance.toggle();
132+
expect(tooltip.prop('isOpen')).toBe(true);
133+
});
134+
});

src/index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ import Collapse from './Collapse';
6666
import ListGroupItem from './ListGroupItem';
6767
import ListGroupItemHeading from './ListGroupItemHeading';
6868
import ListGroupItemText from './ListGroupItemText';
69+
import {
70+
UncontrolledAlert,
71+
UncontrolledButtonDropdown,
72+
UncontrolledDropdown,
73+
UncontrolledNavDropdown,
74+
UncontrolledTooltip,
75+
UncontrolledPopover,
76+
} from './Uncontrolled';
6977

7078
export {
7179
Alert,
@@ -136,4 +144,10 @@ export {
136144
ListGroupItem,
137145
ListGroupItemText,
138146
ListGroupItemHeading,
147+
UncontrolledAlert,
148+
UncontrolledButtonDropdown,
149+
UncontrolledDropdown,
150+
UncontrolledNavDropdown,
151+
UncontrolledTooltip,
152+
UncontrolledPopover,
139153
};

0 commit comments

Comments
 (0)