diff options
Diffstat (limited to 'packages/forms/src/button/index.tsx')
-rw-r--r-- | packages/forms/src/button/index.tsx | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/packages/forms/src/button/index.tsx b/packages/forms/src/button/index.tsx new file mode 100644 index 000000000..c4a138b16 --- /dev/null +++ b/packages/forms/src/button/index.tsx | |||
@@ -0,0 +1,172 @@ | |||
1 | import { Theme } from '@meetfranz/theme'; | ||
2 | import classnames from 'classnames'; | ||
3 | import CSS from 'csstype'; | ||
4 | import { observer } from 'mobx-react'; | ||
5 | import React, { Component } from 'react'; | ||
6 | import injectStyle from 'react-jss'; | ||
7 | import Loader from 'react-loader'; | ||
8 | |||
9 | import { IFormField, IWithStyle } from '../typings/generic'; | ||
10 | |||
11 | type ButtonType = 'primary' | 'secondary' | 'danger' | 'warning' | 'inverted'; | ||
12 | |||
13 | interface IProps extends React.InputHTMLAttributes<HTMLButtonElement>, IFormField, IWithStyle { | ||
14 | buttonType?: ButtonType; | ||
15 | stretch?: boolean; | ||
16 | loaded?: boolean; | ||
17 | busy?: boolean; | ||
18 | } | ||
19 | |||
20 | interface IState { | ||
21 | busy: boolean; | ||
22 | } | ||
23 | |||
24 | const styles = (theme: Theme) => ({ | ||
25 | button: { | ||
26 | borderRadius: theme.borderRadiusSmall, | ||
27 | border: 'none', | ||
28 | display: 'flex', | ||
29 | position: 'relative' as CSS.PositionProperty, | ||
30 | transition: 'background .5s', | ||
31 | textAlign: 'center' as CSS.TextAlignProperty, | ||
32 | outline: 'none', | ||
33 | alignItems: 'center', | ||
34 | padding: 0, | ||
35 | width: (props: IProps) => (props.stretch ? '100%' : 'auto') as CSS.WidthProperty<string>, | ||
36 | fontSize: theme.uiFontSize, | ||
37 | }, | ||
38 | label: { | ||
39 | margin: '10px 20px', | ||
40 | width: '100%', | ||
41 | }, | ||
42 | primary: { | ||
43 | background: theme.buttonPrimaryBackground, | ||
44 | color: theme.buttonPrimaryTextColor, | ||
45 | }, | ||
46 | secondary: { | ||
47 | background: theme.buttonSecondaryBackground, | ||
48 | color: theme.buttonSecondaryTextColor, | ||
49 | }, | ||
50 | danger: { | ||
51 | background: theme.buttonDangerBackground, | ||
52 | color: theme.buttonDangerTextColor, | ||
53 | }, | ||
54 | warning: { | ||
55 | background: theme.buttonWarningBackground, | ||
56 | color: theme.buttonWarningTextColor, | ||
57 | }, | ||
58 | inverted: { | ||
59 | background: theme.buttonInvertedBackground, | ||
60 | color: theme.buttonInvertedTextColor, | ||
61 | border: theme.buttonInvertedBorder, | ||
62 | }, | ||
63 | disabled: { | ||
64 | opacity: theme.inputDisabledOpacity, | ||
65 | }, | ||
66 | loader: { | ||
67 | position: 'relative' as CSS.PositionProperty, | ||
68 | width: 20, | ||
69 | height: 18, | ||
70 | zIndex: 9999, | ||
71 | }, | ||
72 | loaderContainer: { | ||
73 | width: (props: IProps): string => (!props.busy ? '0' : '40px'), | ||
74 | height: 20, | ||
75 | overflow: 'hidden', | ||
76 | transition: 'all 0.3s', | ||
77 | marginLeft: (props: IProps): number => !props.busy ? 10 : 20, | ||
78 | marginRight: (props: IProps): number => !props.busy ? -10 : -20, | ||
79 | position: (props: IProps): CSS.PositionProperty => props.stretch ? 'absolute' : 'inherit', | ||
80 | }, | ||
81 | }); | ||
82 | |||
83 | @observer | ||
84 | class ButtonComponent extends Component<IProps> { | ||
85 | public static defaultProps = { | ||
86 | type: 'button', | ||
87 | disabled: false, | ||
88 | onClick: () => null, | ||
89 | buttonType: 'primary' as ButtonType, | ||
90 | stretch: false, | ||
91 | busy: false, | ||
92 | }; | ||
93 | |||
94 | state = { | ||
95 | busy: false, | ||
96 | }; | ||
97 | |||
98 | componentWillMount() { | ||
99 | this.setState({ busy: this.props.busy }); | ||
100 | } | ||
101 | |||
102 | componentWillReceiveProps(nextProps: IProps) { | ||
103 | if (nextProps.busy !== this.props.busy) { | ||
104 | if (this.props.busy) { | ||
105 | setTimeout(() => { | ||
106 | this.setState({ busy: nextProps.busy }); | ||
107 | }, 300); | ||
108 | } else { | ||
109 | this.setState({ busy: nextProps.busy }); | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | |||
114 | render() { | ||
115 | const { | ||
116 | classes, | ||
117 | theme, | ||
118 | disabled, | ||
119 | id, | ||
120 | label, | ||
121 | type, | ||
122 | onClick, | ||
123 | buttonType, | ||
124 | loaded, | ||
125 | busy: busyProp, | ||
126 | } = this.props; | ||
127 | |||
128 | const { | ||
129 | busy, | ||
130 | } = this.state; | ||
131 | |||
132 | let showLoader = false; | ||
133 | if (loaded) { | ||
134 | showLoader = !loaded; | ||
135 | console.warn('Franz Button prop `loaded` will be deprecated in the future. Please use `busy` instead'); | ||
136 | } | ||
137 | if (busy) { | ||
138 | showLoader = busy; | ||
139 | } | ||
140 | |||
141 | return ( | ||
142 | <button | ||
143 | id={id} | ||
144 | type={type} | ||
145 | onClick={onClick} | ||
146 | className={classnames({ | ||
147 | [`${classes.button}`]: true, | ||
148 | [`${classes[buttonType as ButtonType]}`]: true, | ||
149 | [`${classes.disabled}`]: disabled, | ||
150 | })} | ||
151 | disabled={disabled} | ||
152 | > | ||
153 | <div className={classes.loaderContainer}> | ||
154 | {showLoader && ( | ||
155 | <Loader | ||
156 | loaded={false} | ||
157 | width={4} | ||
158 | scale={0.45} | ||
159 | color={theme.buttonLoaderColor[buttonType!]} | ||
160 | parentClassName={classes.loader} | ||
161 | /> | ||
162 | )} | ||
163 | </div> | ||
164 | <div className={classes.label}> | ||
165 | {label} | ||
166 | </div> | ||
167 | </button> | ||
168 | ); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | export const Button = injectStyle(styles)(ButtonComponent); | ||