aboutsummaryrefslogtreecommitdiffstats
path: root/packages/forms/src/button/index.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/forms/src/button/index.tsx')
-rw-r--r--packages/forms/src/button/index.tsx274
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 @@
1import * as mdiIcons from '@mdi/js';
2import Icon from '@mdi/react';
3import { Theme } from '@meetfranz/theme';
4import classnames from 'classnames';
5import CSS from 'csstype';
6import React, { Component } from 'react';
7import injectStyle, { withTheme } from 'react-jss';
8import Loader from 'react-loader';
9
10import { IFormField, IWithStyle } from '../typings/generic';
11
12type ButtonType = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'inverted';
13
14interface 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
29interface IState {
30 busy: boolean;
31}
32
33const 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
136class 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
274export const Button = injectStyle(styles)(withTheme(ButtonComponent));