24 |
25 |
28 | this.setState({
29 | isSwitchChecked: checked,
30 | })
31 | }
32 | />
33 |
34 |
49 |
50 |
54 |
55 |
56 |
60 |
61 |
62 |
67 | this.setState({
68 | isColorSwitchChecked: checked,
69 | })
70 | }
71 | />
72 |
73 |
74 |
77 | this.setState({
78 | isStyleSwitchChecked: checked,
79 | })
80 | }
81 | style={{
82 | marginLeft: 50,
83 | }}
84 | />
85 |
86 |
87 | );
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Switch.jsx:
--------------------------------------------------------------------------------
1 | import { easing, pointer, trackOffset, transform } from 'popmotion';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import normalizeColor from './normalizeColor';
6 | import prefixStyle from './prefixStyle';
7 |
8 | export default class Switch extends React.Component {
9 | static propTypes = {
10 | checked: PropTypes.bool,
11 | className: PropTypes.string,
12 | disabled: PropTypes.bool,
13 | handleColor: PropTypes.string,
14 | name: PropTypes.string,
15 | offColor: PropTypes.string,
16 | onChange: PropTypes.func,
17 | onColor: PropTypes.string,
18 | pendingOffColor: PropTypes.string,
19 | pendingOnColor: PropTypes.string,
20 | readOnly: PropTypes.bool,
21 | style: PropTypes.object,
22 | };
23 |
24 | static defaultProps = {
25 | handleColor: 'white',
26 | offColor: 'white',
27 | onChange: () => {},
28 | onColor: 'rgb(76, 217, 100)',
29 | };
30 |
31 | constructor(props) {
32 | super(props);
33 |
34 | this.state = {
35 | isDragging: false,
36 | offset: null,
37 | };
38 |
39 | this.handleChange = this.handleChange.bind(this);
40 | this.handleClick = this.handleClick.bind(this);
41 | this.handleHandleClick = this.handleHandleClick.bind(this);
42 | this.handleMouseDown = this.handleMouseDown.bind(this);
43 | this.handleMouseUp = this.handleMouseUp.bind(this);
44 | this.setRef = this.setRef.bind(this);
45 | }
46 |
47 | clickChange(checked) {
48 | if (this.ref.parentNode && this.ref.parentNode.tagName.toLowerCase() === 'label') {
49 | // if the parent is a label, we don't need to emit the change event ourselves
50 | return;
51 | }
52 |
53 | this.props.onChange(checked);
54 | }
55 |
56 | componentDidMount() {
57 | window.addEventListener('mouseup', this.handleMouseUp);
58 | }
59 |
60 | componentWillUnmount() {
61 | window.removeEventListener('mouseup', this.handleMouseUp);
62 | }
63 |
64 | getHandleColor() {
65 | return normalizeColor(this.props.handleColor);
66 | }
67 |
68 | getHandleCursor() {
69 | if (this.isDisabled()) {
70 | return 'default';
71 | }
72 |
73 | return this.state.isDragging ? 'grabbing' : 'grab';
74 | }
75 |
76 | getHandleLength() {
77 | return this.getHeight() - 2;
78 | }
79 |
80 | getHeight() {
81 | return 30;
82 | }
83 |
84 | getOffColor() {
85 | return normalizeColor(this.props.offColor);
86 | }
87 |
88 | getOffset() {
89 | if (this.state.isDragging) {
90 | return this.state.offset;
91 | }
92 |
93 | return this.props.checked ? this.getOffsetWidth() : 0;
94 | }
95 |
96 | getOffsetProgress() {
97 | return this.getOffset() / this.getOffsetWidth();
98 | }
99 |
100 | getOffsetWidth(props) {
101 | return (this.getWidth() - this.getHandleLength()) - 2;
102 | }
103 |
104 | getOnColor() {
105 | return normalizeColor(this.props.onColor);
106 | }
107 |
108 | getPendingColor({
109 | color,
110 | pendingColor,
111 | }) {
112 | if (!pendingColor) {
113 | return color === 'white' ?
114 | // default pending color for white color
115 | '#dfdfdf' :
116 | normalizeColor(color);
117 | }
118 |
119 | return normalizeColor(pendingColor);
120 | }
121 |
122 | getPendingOffColor() {
123 | return this.getPendingColor({
124 | color: this.props.offColor,
125 | pendingColor: this.props.pendingOffColor,
126 | });
127 | }
128 |
129 | getPendingOnColor() {
130 | return this.getPendingColor({
131 | color: this.props.onColor,
132 | pendingColor: this.props.pendingOnColor,
133 | });
134 | }
135 |
136 | getWidth() {
137 | return 50;
138 | }
139 |
140 | handleChange(e) {
141 | this.props.onChange(e.target.checked);
142 | }
143 |
144 | handleClick(e) {
145 | if (this.isDisabled()) {
146 | return;
147 | }
148 |
149 | // handle case when the switch is clicked
150 | this.clickChange(!this.props.checked);
151 | }
152 |
153 | handleHandleClick(e) {
154 | e.stopPropagation();
155 | }
156 |
157 | handleMouseDown(e) {
158 | if (this.isDisabled()) {
159 | return;
160 | }
161 |
162 | this.pointerTracker = pointer(e).start();
163 |
164 | this.offsetTracker = trackOffset(this.pointerTracker.x, {
165 | from: this.getOffset(),
166 | onUpdate: transform.pipe(
167 | transform.clamp(0, this.getOffsetWidth()),
168 | offset => this.setState({ offset })
169 | ),
170 | }).start();
171 |
172 | this.setState({
173 | isDragging: true,
174 | offset: this.getOffset(),
175 | });
176 | }
177 |
178 | handleMouseUp() {
179 | if (!this.state.isDragging) {
180 | return;
181 | }
182 |
183 | this.pointerTracker.stop();
184 | this.offsetTracker.stop();
185 |
186 | const prevOffset = this.props.checked ? this.getOffsetWidth() : 0;
187 | const checked = this.state.offset === prevOffset ?
188 | // handle case when the handle is clicked
189 | !this.props.checked :
190 | // handle case when the handle is dragged
191 | this.getOffsetProgress() >= 0.5;
192 |
193 | this.setState({
194 | isDragging: false,
195 | offset: null,
196 | });
197 |
198 | this.clickChange(checked);
199 | }
200 |
201 | isDisabled() {
202 | return this.props.disabled || this.props.readOnly;
203 | }
204 |
205 | render() {
206 | const {
207 | checked,
208 | className,
209 | disabled,
210 | name,
211 | onChange,
212 | readOnly,
213 | style,
214 | } = this.props;
215 |
216 | const { isDragging } = this.state;
217 |
218 | const color = transform.pipe(
219 | easing.createExpoIn(2),
220 | transform.blendColor(this.getOffColor(), this.getOnColor()),
221 | transform.rgba,
222 | )(this.getOffsetProgress());
223 |
224 | const borderColor = transform.pipe(
225 | easing.createExpoIn(1),
226 | transform.blendColor(this.getPendingOffColor(), this.getPendingOnColor()),
227 | transform.rgba,
228 | )(this.getOffsetProgress());
229 |
230 | return (
231 |