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