diff options
Diffstat (limited to 'packages/forms/src/button/index.tsx')
-rw-r--r-- | packages/forms/src/button/index.tsx | 274 |
1 files changed, 274 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..7a7f83dab --- /dev/null +++ b/packages/forms/src/button/index.tsx | |||
@@ -0,0 +1,274 @@ | |||
1 | import * as mdiIcons from '@mdi/js'; | ||
2 | import Icon from '@mdi/react'; | ||
3 | import { Theme } from '@meetfranz/theme'; | ||
4 | import classnames from 'classnames'; | ||
5 | import CSS from 'csstype'; | ||
6 | import React, { Component } from 'react'; | ||
7 | import injectStyle, { withTheme } from 'react-jss'; | ||
8 | import Loader from 'react-loader'; | ||
9 | |||
10 | import { IFormField, IWithStyle } from '../typings/generic'; | ||
11 | |||
12 | type ButtonType = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'inverted'; | ||
13 | |||
14 | interface IProps extends IFormField, IWithStyle { | ||
15 | className?: string; | ||
16 | disabled?: boolean; | ||
17 | id?: string; | ||
18 | type?: string; | ||
19 | onClick: (event: React.MouseEvent<HTMLButtonElement> | React.MouseEvent<HTMLAnchorElement>) => void; | ||
20 | buttonType?: ButtonType; | ||
21 | stretch?: boolean; | ||
22 | loaded?: boolean; | ||
23 | busy?: boolean; | ||
24 | icon?: keyof typeof mdiIcons; | ||
25 | href?: string; | ||
26 | target?: string; | ||
27 | } | ||
28 | |||
29 | interface IState { | ||
30 | busy: boolean; | ||
31 | } | ||
32 | |||
33 | const styles = (theme: Theme) => ({ | ||
34 | button: { | ||
35 | borderRadius: theme.borderRadiusSmall, | ||
36 | border: 'none', | ||
37 | display: 'inline-flex', | ||
38 | position: 'relative' as CSS.PositionProperty, | ||
39 | transition: 'background .5s, opacity 0.3s', | ||
40 | textAlign: 'center' as CSS.TextAlignProperty, | ||
41 | outline: 'none', | ||
42 | alignItems: 'center', | ||
43 | padding: 0, | ||
44 | width: (props: IProps) => (props.stretch ? '100%' : 'auto') as CSS.WidthProperty<string>, | ||
45 | fontSize: theme.uiFontSize, | ||
46 | textDecoration: 'none', | ||
47 | |||
48 | '&:hover': { | ||
49 | opacity: 0.8, | ||
50 | }, | ||
51 | '&:active': { | ||
52 | opacity: 0.5, | ||
53 | transition: 'none', | ||
54 | }, | ||
55 | }, | ||
56 | label: { | ||
57 | margin: '10px 20px', | ||
58 | width: '100%', | ||
59 | display: 'flex', | ||
60 | alignItems: 'center', | ||
61 | justifyContent: 'center', | ||
62 | }, | ||
63 | primary: { | ||
64 | background: theme.buttonPrimaryBackground, | ||
65 | color: theme.buttonPrimaryTextColor, | ||
66 | |||
67 | '& svg': { | ||
68 | fill: theme.buttonPrimaryTextColor, | ||
69 | }, | ||
70 | }, | ||
71 | secondary: { | ||
72 | background: theme.buttonSecondaryBackground, | ||
73 | color: theme.buttonSecondaryTextColor, | ||
74 | |||
75 | '& svg': { | ||
76 | fill: theme.buttonSecondaryTextColor, | ||
77 | }, | ||
78 | }, | ||
79 | success: { | ||
80 | background: theme.buttonSuccessBackground, | ||
81 | color: theme.buttonSuccessTextColor, | ||
82 | |||
83 | '& svg': { | ||
84 | fill: theme.buttonSuccessTextColor, | ||
85 | }, | ||
86 | }, | ||
87 | danger: { | ||
88 | background: theme.buttonDangerBackground, | ||
89 | color: theme.buttonDangerTextColor, | ||
90 | |||
91 | '& svg': { | ||
92 | fill: theme.buttonDangerTextColor, | ||
93 | }, | ||
94 | }, | ||
95 | warning: { | ||
96 | background: theme.buttonWarningBackground, | ||
97 | color: theme.buttonWarningTextColor, | ||
98 | |||
99 | '& svg': { | ||
100 | fill: theme.buttonWarningTextColor, | ||
101 | }, | ||
102 | }, | ||
103 | inverted: { | ||
104 | background: theme.buttonInvertedBackground, | ||
105 | color: theme.buttonInvertedTextColor, | ||
106 | border: theme.buttonInvertedBorder, | ||
107 | |||
108 | '& svg': { | ||
109 | fill: theme.buttonInvertedTextColor, | ||
110 | }, | ||
111 | }, | ||
112 | disabled: { | ||
113 | opacity: theme.inputDisabledOpacity, | ||
114 | }, | ||
115 | loader: { | ||
116 | position: 'relative' as CSS.PositionProperty, | ||
117 | width: 20, | ||
118 | height: 18, | ||
119 | zIndex: 9999, | ||
120 | }, | ||
121 | loaderContainer: { | ||
122 | width: (props: IProps): string => (!props.busy ? '0' : '40px'), | ||
123 | height: 20, | ||
124 | overflow: 'hidden', | ||
125 | transition: 'all 0.3s', | ||
126 | marginLeft: (props: IProps): number => !props.busy ? 10 : 20, | ||
127 | marginRight: (props: IProps): number => !props.busy ? -10 : -20, | ||
128 | position: (props: IProps): CSS.PositionProperty => props.stretch ? 'absolute' : 'inherit', | ||
129 | }, | ||
130 | icon: { | ||
131 | marginLeft: -5, | ||
132 | marginRight: 10, | ||
133 | }, | ||
134 | }); | ||
135 | |||
136 | class ButtonComponent extends Component<IProps> { | ||
137 | public static defaultProps = { | ||
138 | type: 'button', | ||
139 | disabled: false, | ||
140 | onClick: () => null, | ||
141 | buttonType: 'primary' as ButtonType, | ||
142 | stretch: false, | ||
143 | busy: false, | ||
144 | // target: '_self' | ||
145 | }; | ||
146 | |||
147 | state = { | ||
148 | busy: false, | ||
149 | }; | ||
150 | |||
151 | componentWillMount() { | ||
152 | this.setState({ busy: this.props.busy }); | ||
153 | } | ||
154 | |||
155 | componentWillReceiveProps(nextProps: IProps) { | ||
156 | if (nextProps.busy !== this.props.busy) { | ||
157 | if (this.props.busy) { | ||
158 | setTimeout(() => { | ||
159 | this.setState({ busy: nextProps.busy }); | ||
160 | }, 300); | ||
161 | } else { | ||
162 | this.setState({ busy: nextProps.busy }); | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | |||
167 | render() { | ||
168 | const { | ||
169 | classes, | ||
170 | className, | ||
171 | theme, | ||
172 | disabled, | ||
173 | id, | ||
174 | label, | ||
175 | type, | ||
176 | onClick, | ||
177 | buttonType, | ||
178 | loaded, | ||
179 | icon: iconName, | ||
180 | busy: busyProp, | ||
181 | href, | ||
182 | target, | ||
183 | } = this.props; | ||
184 | |||
185 | const { | ||
186 | busy, | ||
187 | } = this.state; | ||
188 | |||
189 | let icon = ''; | ||
190 | if (iconName && mdiIcons[iconName]) { | ||
191 | icon = mdiIcons[iconName]; | ||
192 | } else if (iconName && !mdiIcons[iconName]) { | ||
193 | console.warn(`Icon '${iconName}' was not found`); | ||
194 | } | ||
195 | |||
196 | let showLoader = false; | ||
197 | if (loaded) { | ||
198 | showLoader = !loaded; | ||
199 | console.warn('Franz Button prop `loaded` will be deprecated in the future. Please use `busy` instead'); | ||
200 | } | ||
201 | if (busy) { | ||
202 | showLoader = busy; | ||
203 | } | ||
204 | |||
205 | const content = ( | ||
206 | <> | ||
207 | <div className={classes.loaderContainer}> | ||
208 | {showLoader && ( | ||
209 | <Loader | ||
210 | loaded={false} | ||
211 | width={4} | ||
212 | scale={0.45} | ||
213 | color={theme.buttonLoaderColor[buttonType!]} | ||
214 | parentClassName={classes.loader} | ||
215 | /> | ||
216 | )} | ||
217 | </div> | ||
218 | <div className={classes.label}> | ||
219 | {icon && ( | ||
220 | <Icon | ||
221 | path={icon} | ||
222 | size={1} | ||
223 | className={classes.icon} | ||
224 | /> | ||
225 | )} | ||
226 | {label} | ||
227 | </div> | ||
228 | </> | ||
229 | ); | ||
230 | |||
231 | let wrapperComponent = null; | ||
232 | |||
233 | if (!href) { | ||
234 | wrapperComponent = ( | ||
235 | <button | ||
236 | id={id} | ||
237 | type={type} | ||
238 | onClick={onClick} | ||
239 | className={classnames({ | ||
240 | [`${classes.button}`]: true, | ||
241 | [`${classes[buttonType as ButtonType]}`]: true, | ||
242 | [`${classes.disabled}`]: disabled, | ||
243 | [`${className}`]: className, | ||
244 | })} | ||
245 | disabled={disabled} | ||
246 | data-type="franz-button" | ||
247 | > | ||
248 | {content} | ||
249 | </button> | ||
250 | ); | ||
251 | } else { | ||
252 | wrapperComponent = ( | ||
253 | <a | ||
254 | href={href} | ||
255 | target={target} | ||
256 | onClick={onClick} | ||
257 | className={classnames({ | ||
258 | [`${classes.button}`]: true, | ||
259 | [`${classes[buttonType as ButtonType]}`]: true, | ||
260 | [`${className}`]: className, | ||
261 | })} | ||
262 | rel={target === '_blank' ? 'noopener' : ''} | ||
263 | data-type="franz-button" | ||
264 | > | ||
265 | {content} | ||
266 | </a> | ||
267 | ); | ||
268 | } | ||
269 | |||
270 | return wrapperComponent; | ||
271 | } | ||
272 | } | ||
273 | |||
274 | export const Button = injectStyle(styles)(withTheme(ButtonComponent)); | ||