diff options
Diffstat (limited to 'packages/forms')
-rw-r--r-- | packages/forms/.gitignore | 2 | ||||
-rw-r--r-- | packages/forms/package.json | 33 | ||||
-rw-r--r-- | packages/forms/src/button/index.tsx | 265 | ||||
-rw-r--r-- | packages/forms/src/error/index.tsx | 20 | ||||
-rw-r--r-- | packages/forms/src/error/styles.ts | 9 | ||||
-rw-r--r-- | packages/forms/src/index.ts | 5 | ||||
-rw-r--r-- | packages/forms/src/input/index.tsx | 208 | ||||
-rw-r--r-- | packages/forms/src/input/scorePassword.ts | 42 | ||||
-rw-r--r-- | packages/forms/src/input/styles.ts | 102 | ||||
-rw-r--r-- | packages/forms/src/label/index.tsx | 52 | ||||
-rw-r--r-- | packages/forms/src/label/styles.ts | 12 | ||||
-rw-r--r-- | packages/forms/src/select/index.tsx | 460 | ||||
-rw-r--r-- | packages/forms/src/textarea/index.tsx | 126 | ||||
-rw-r--r-- | packages/forms/src/textarea/styles.ts | 54 | ||||
-rw-r--r-- | packages/forms/src/toggle/index.tsx | 125 | ||||
-rw-r--r-- | packages/forms/src/typings/generic.ts | 19 | ||||
-rw-r--r-- | packages/forms/src/wrapper/index.tsx | 37 | ||||
-rw-r--r-- | packages/forms/tsconfig.json | 12 |
18 files changed, 0 insertions, 1583 deletions
diff --git a/packages/forms/.gitignore b/packages/forms/.gitignore deleted file mode 100644 index d01826a6b..000000000 --- a/packages/forms/.gitignore +++ /dev/null | |||
@@ -1,2 +0,0 @@ | |||
1 | node_modules/ | ||
2 | lib | ||
diff --git a/packages/forms/package.json b/packages/forms/package.json deleted file mode 100644 index f073221bc..000000000 --- a/packages/forms/package.json +++ /dev/null | |||
@@ -1,33 +0,0 @@ | |||
1 | { | ||
2 | "name": "@meetfranz/forms", | ||
3 | "version": "1.2.1", | ||
4 | "description": "React form components for Franz", | ||
5 | "main": "lib/index.js", | ||
6 | "scripts": { | ||
7 | "dev": "tsc -w", | ||
8 | "build": "tsc" | ||
9 | }, | ||
10 | "publishConfig": { | ||
11 | "access": "public" | ||
12 | }, | ||
13 | "repository": { | ||
14 | "type": "git", | ||
15 | "url": "git+https://github.com/meetfranz/franz.git" | ||
16 | }, | ||
17 | "keywords": [ | ||
18 | "Franz", | ||
19 | "Forms", | ||
20 | "React", | ||
21 | "UI" | ||
22 | ], | ||
23 | "author": "Amine Mouafik <amine@mouafik.fr>", | ||
24 | "license": "Apache-2.0", | ||
25 | "dependencies": { | ||
26 | "@mdi/js": "3.3.92", | ||
27 | "@mdi/react": "1.5.0", | ||
28 | "@meetfranz/theme": "file:../theme", | ||
29 | "csstype": "3.0.8", | ||
30 | "react-loader": "2.4.7" | ||
31 | }, | ||
32 | "gitHead": "00db2bddccb8bb8ad7d29b8d032876c798b8bbf3" | ||
33 | } | ||
diff --git a/packages/forms/src/button/index.tsx b/packages/forms/src/button/index.tsx deleted file mode 100644 index d9f372c4b..000000000 --- a/packages/forms/src/button/index.tsx +++ /dev/null | |||
@@ -1,265 +0,0 @@ | |||
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 | |||
8 | import { IFormField, IWithStyle } from '../typings/generic'; | ||
9 | import { Theme } from '../../../theme'; | ||
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)); | ||
diff --git a/packages/forms/src/error/index.tsx b/packages/forms/src/error/index.tsx deleted file mode 100644 index 8439bfc8b..000000000 --- a/packages/forms/src/error/index.tsx +++ /dev/null | |||
@@ -1,20 +0,0 @@ | |||
1 | import { Classes } from 'jss'; | ||
2 | import { Component } from 'react'; | ||
3 | import injectSheet from 'react-jss'; | ||
4 | |||
5 | import styles from './styles'; | ||
6 | |||
7 | interface IProps { | ||
8 | classes: Classes; | ||
9 | message: string; | ||
10 | } | ||
11 | |||
12 | class ErrorComponent extends Component<IProps> { | ||
13 | render() { | ||
14 | const { classes, message } = this.props; | ||
15 | |||
16 | return <p className={classes.message}>{message}</p>; | ||
17 | } | ||
18 | } | ||
19 | |||
20 | export const Error = injectSheet(styles)(ErrorComponent); | ||
diff --git a/packages/forms/src/error/styles.ts b/packages/forms/src/error/styles.ts deleted file mode 100644 index 4c0c7e2c0..000000000 --- a/packages/forms/src/error/styles.ts +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | import { Theme } from '../../../theme'; | ||
2 | |||
3 | export default (theme: Theme) => ({ | ||
4 | message: { | ||
5 | color: theme.brandDanger, | ||
6 | margin: '5px 0 0', | ||
7 | fontSize: theme.uiFontSize, | ||
8 | }, | ||
9 | }); | ||
diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts deleted file mode 100644 index 45a9ed8e3..000000000 --- a/packages/forms/src/index.ts +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | export { Input } from './input'; | ||
2 | export { Textarea } from './textarea'; | ||
3 | export { Toggle } from './toggle'; | ||
4 | export { Button } from './button'; | ||
5 | export { Select } from './select'; | ||
diff --git a/packages/forms/src/input/index.tsx b/packages/forms/src/input/index.tsx deleted file mode 100644 index 0b16fe688..000000000 --- a/packages/forms/src/input/index.tsx +++ /dev/null | |||
@@ -1,208 +0,0 @@ | |||
1 | import { mdiEye, mdiEyeOff } from '@mdi/js'; | ||
2 | import Icon from '@mdi/react'; | ||
3 | import classnames from 'classnames'; | ||
4 | import { Component, createRef, InputHTMLAttributes } from 'react'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | |||
7 | import { IFormField, IWithStyle } from '../typings/generic'; | ||
8 | |||
9 | import { Error } from '../error'; | ||
10 | import { Label } from '../label'; | ||
11 | import { Wrapper } from '../wrapper'; | ||
12 | import { scorePasswordFunc } from './scorePassword'; | ||
13 | |||
14 | import styles from './styles'; | ||
15 | |||
16 | interface IData { | ||
17 | [index: string]: string; | ||
18 | } | ||
19 | |||
20 | interface IProps | ||
21 | extends InputHTMLAttributes<HTMLInputElement>, | ||
22 | IFormField, | ||
23 | IWithStyle { | ||
24 | focus?: boolean; | ||
25 | prefix?: string; | ||
26 | suffix?: string; | ||
27 | scorePassword?: boolean; | ||
28 | showPasswordToggle?: boolean; | ||
29 | data: IData; | ||
30 | inputClassName?: string; | ||
31 | onEnterKey?: Function; | ||
32 | } | ||
33 | |||
34 | interface IState { | ||
35 | showPassword: boolean; | ||
36 | passwordScore: number; | ||
37 | } | ||
38 | |||
39 | class InputComponent extends Component<IProps, IState> { | ||
40 | static defaultProps = { | ||
41 | focus: false, | ||
42 | onChange: () => {}, | ||
43 | onBlur: () => {}, | ||
44 | onFocus: () => {}, | ||
45 | scorePassword: false, | ||
46 | showLabel: true, | ||
47 | showPasswordToggle: false, | ||
48 | type: 'text', | ||
49 | disabled: false, | ||
50 | }; | ||
51 | |||
52 | state = { | ||
53 | passwordScore: 0, | ||
54 | showPassword: false, | ||
55 | }; | ||
56 | |||
57 | private inputRef = createRef<HTMLInputElement>(); | ||
58 | |||
59 | componentDidMount() { | ||
60 | const { focus, data } = this.props; | ||
61 | |||
62 | if (this.inputRef && this.inputRef.current) { | ||
63 | if (focus) { | ||
64 | this.inputRef.current.focus(); | ||
65 | } | ||
66 | |||
67 | if (data) { | ||
68 | Object.keys(data).map( | ||
69 | key => (this.inputRef.current!.dataset[key] = data[key]), | ||
70 | ); | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | onChange(e: React.ChangeEvent<HTMLInputElement>) { | ||
76 | const { scorePassword, onChange } = this.props; | ||
77 | |||
78 | if (onChange) { | ||
79 | onChange(e); | ||
80 | } | ||
81 | |||
82 | if (this.inputRef && this.inputRef.current && scorePassword) { | ||
83 | this.setState({ | ||
84 | passwordScore: scorePasswordFunc(this.inputRef.current.value), | ||
85 | }); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | onInputKeyPress(e: React.KeyboardEvent) { | ||
90 | if (e.key === 'Enter') { | ||
91 | const { onEnterKey } = this.props; | ||
92 | onEnterKey && onEnterKey(); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | render() { | ||
97 | const { | ||
98 | classes, | ||
99 | className, | ||
100 | disabled, | ||
101 | error, | ||
102 | id, | ||
103 | inputClassName, | ||
104 | label, | ||
105 | prefix, | ||
106 | scorePassword, | ||
107 | suffix, | ||
108 | showLabel, | ||
109 | showPasswordToggle, | ||
110 | type, | ||
111 | value, | ||
112 | name, | ||
113 | placeholder, | ||
114 | spellCheck, | ||
115 | onBlur, | ||
116 | onFocus, | ||
117 | min, | ||
118 | max, | ||
119 | step, | ||
120 | required, | ||
121 | noMargin, | ||
122 | } = this.props; | ||
123 | |||
124 | const { showPassword, passwordScore } = this.state; | ||
125 | |||
126 | const inputType = type === 'password' && showPassword ? 'text' : type; | ||
127 | |||
128 | return ( | ||
129 | <Wrapper | ||
130 | className={className} | ||
131 | identifier="franz-input" | ||
132 | noMargin={noMargin} | ||
133 | > | ||
134 | <Label | ||
135 | title={label} | ||
136 | showLabel={showLabel} | ||
137 | htmlFor={id} | ||
138 | className={classes.label} | ||
139 | isRequired={required} | ||
140 | > | ||
141 | <div | ||
142 | className={classnames({ | ||
143 | [`${inputClassName}`]: inputClassName, | ||
144 | [`${classes.hasPasswordScore}`]: scorePassword, | ||
145 | [`${classes.wrapper}`]: true, | ||
146 | [`${classes.disabled}`]: disabled, | ||
147 | [`${classes.hasError}`]: error, | ||
148 | })} | ||
149 | > | ||
150 | {prefix && <span className={classes.prefix}>{prefix}</span>} | ||
151 | <input | ||
152 | id={id} | ||
153 | type={inputType} | ||
154 | name={name} | ||
155 | value={value as string} | ||
156 | placeholder={placeholder} | ||
157 | spellCheck={spellCheck} | ||
158 | className={classes.input} | ||
159 | ref={this.inputRef} | ||
160 | onChange={this.onChange.bind(this)} | ||
161 | onFocus={onFocus} | ||
162 | onBlur={onBlur} | ||
163 | disabled={disabled} | ||
164 | onKeyPress={this.onInputKeyPress.bind(this)} | ||
165 | min={min} | ||
166 | max={max} | ||
167 | step={step} | ||
168 | /> | ||
169 | {suffix && <span className={classes.suffix}>{suffix}</span>} | ||
170 | {showPasswordToggle && ( | ||
171 | <button | ||
172 | type="button" | ||
173 | className={classes.formModifier} | ||
174 | onClick={() => | ||
175 | this.setState(prevState => ({ | ||
176 | showPassword: !prevState.showPassword, | ||
177 | })) | ||
178 | } | ||
179 | tabIndex={-1} | ||
180 | > | ||
181 | <Icon path={!showPassword ? mdiEye : mdiEyeOff} size={1} /> | ||
182 | </button> | ||
183 | )} | ||
184 | </div> | ||
185 | {scorePassword && ( | ||
186 | <div | ||
187 | className={classnames({ | ||
188 | [`${classes.passwordScore}`]: true, | ||
189 | [`${classes.hasError}`]: error, | ||
190 | })} | ||
191 | > | ||
192 | <meter | ||
193 | value={passwordScore < 5 ? 5 : passwordScore} | ||
194 | low={30} | ||
195 | high={75} | ||
196 | optimum={100} | ||
197 | max={100} | ||
198 | /> | ||
199 | </div> | ||
200 | )} | ||
201 | </Label> | ||
202 | {error && <Error message={error} />} | ||
203 | </Wrapper> | ||
204 | ); | ||
205 | } | ||
206 | } | ||
207 | |||
208 | export const Input = injectSheet(styles)(InputComponent); | ||
diff --git a/packages/forms/src/input/scorePassword.ts b/packages/forms/src/input/scorePassword.ts deleted file mode 100644 index 59502e2b0..000000000 --- a/packages/forms/src/input/scorePassword.ts +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
1 | interface ILetters { | ||
2 | [key: string]: number; | ||
3 | } | ||
4 | |||
5 | interface IVariations { | ||
6 | [index: string]: boolean; | ||
7 | digits: boolean; | ||
8 | lower: boolean; | ||
9 | nonWords: boolean; | ||
10 | upper: boolean; | ||
11 | } | ||
12 | |||
13 | export function scorePasswordFunc(password: string): number { | ||
14 | let score = 0; | ||
15 | if (!password) { | ||
16 | return score; | ||
17 | } | ||
18 | |||
19 | // award every unique letter until 5 repetitions | ||
20 | const letters: ILetters = {}; | ||
21 | for (const element of password) { | ||
22 | letters[element] = (letters[element] || 0) + 1; | ||
23 | score += 5 / letters[element]; | ||
24 | } | ||
25 | |||
26 | // bonus points for mixing it up | ||
27 | const variations: IVariations = { | ||
28 | digits: /\d/.test(password), | ||
29 | lower: /[a-z]/.test(password), | ||
30 | nonWords: /\W/.test(password), | ||
31 | upper: /[A-Z]/.test(password), | ||
32 | }; | ||
33 | |||
34 | let variationCount = 0; | ||
35 | for (const key of Object.keys(variations)) { | ||
36 | variationCount += variations[key] === true ? 1 : 0; | ||
37 | } | ||
38 | |||
39 | score += (variationCount - 1) * 10; | ||
40 | |||
41 | return Math.round(score); | ||
42 | } | ||
diff --git a/packages/forms/src/input/styles.ts b/packages/forms/src/input/styles.ts deleted file mode 100644 index 6d56e93b3..000000000 --- a/packages/forms/src/input/styles.ts +++ /dev/null | |||
@@ -1,102 +0,0 @@ | |||
1 | import { Property } from 'csstype'; | ||
2 | |||
3 | import { Theme } from '../../../theme'; | ||
4 | |||
5 | const prefixStyles = (theme: Theme) => ({ | ||
6 | background: theme.inputPrefixBackground, | ||
7 | color: theme.inputPrefixColor, | ||
8 | lineHeight: `${theme.inputHeight}px`, | ||
9 | padding: '0 10px', | ||
10 | fontSize: theme.uiFontSize, | ||
11 | }); | ||
12 | |||
13 | export default (theme: Theme) => ({ | ||
14 | label: { | ||
15 | '& > div': { | ||
16 | marginTop: 5, | ||
17 | }, | ||
18 | }, | ||
19 | disabled: { | ||
20 | opacity: theme.inputDisabledOpacity, | ||
21 | }, | ||
22 | formModifier: { | ||
23 | background: 'none', | ||
24 | border: 0, | ||
25 | borderLeft: theme.inputBorder, | ||
26 | padding: '4px 20px 0', | ||
27 | outline: 'none', | ||
28 | |||
29 | '&:active': { | ||
30 | opacity: 0.5, | ||
31 | }, | ||
32 | |||
33 | '& svg': { | ||
34 | fill: theme.inputModifierColor, | ||
35 | }, | ||
36 | }, | ||
37 | input: { | ||
38 | background: 'none', | ||
39 | border: 0, | ||
40 | fontSize: theme.uiFontSize, | ||
41 | outline: 'none', | ||
42 | padding: 8, | ||
43 | width: '100%', | ||
44 | color: theme.inputColor, | ||
45 | |||
46 | '&::placeholder': { | ||
47 | color: theme.inputPlaceholderColor, | ||
48 | }, | ||
49 | }, | ||
50 | passwordScore: { | ||
51 | background: theme.inputScorePasswordBackground, | ||
52 | border: theme.inputBorder, | ||
53 | borderTopWidth: 0, | ||
54 | borderBottomLeftRadius: theme.borderRadiusSmall, | ||
55 | borderBottomRightRadius: theme.borderRadiusSmall, | ||
56 | display: 'block', | ||
57 | flexBasis: '100%', | ||
58 | height: 5, | ||
59 | overflow: 'hidden', | ||
60 | |||
61 | '& meter': { | ||
62 | display: 'block', | ||
63 | height: '100%', | ||
64 | width: '100%', | ||
65 | |||
66 | '&::-webkit-meter-bar': { | ||
67 | background: 'none', | ||
68 | }, | ||
69 | |||
70 | '&::-webkit-meter-even-less-good-value': { | ||
71 | background: theme.brandDanger, | ||
72 | }, | ||
73 | |||
74 | '&::-webkit-meter-suboptimum-value': { | ||
75 | background: theme.brandWarning, | ||
76 | }, | ||
77 | |||
78 | '&::-webkit-meter-optimum-value': { | ||
79 | background: theme.brandSuccess, | ||
80 | }, | ||
81 | }, | ||
82 | }, | ||
83 | prefix: prefixStyles(theme), | ||
84 | suffix: prefixStyles(theme), | ||
85 | wrapper: { | ||
86 | background: theme.inputBackground, | ||
87 | border: theme.inputBorder, | ||
88 | borderRadius: theme.borderRadiusSmall, | ||
89 | boxSizing: 'border-box' as Property.BoxSizing, | ||
90 | display: 'flex', | ||
91 | height: theme.inputHeight, | ||
92 | order: 1, | ||
93 | width: '100%', | ||
94 | }, | ||
95 | hasPasswordScore: { | ||
96 | borderBottomLeftRadius: 0, | ||
97 | borderBottomRightRadius: 0, | ||
98 | }, | ||
99 | hasError: { | ||
100 | borderColor: theme.brandDanger, | ||
101 | }, | ||
102 | }); | ||
diff --git a/packages/forms/src/label/index.tsx b/packages/forms/src/label/index.tsx deleted file mode 100644 index 4d86f23f7..000000000 --- a/packages/forms/src/label/index.tsx +++ /dev/null | |||
@@ -1,52 +0,0 @@ | |||
1 | import classnames from 'classnames'; | ||
2 | import { Classes } from 'jss'; | ||
3 | import { Component, LabelHTMLAttributes } from 'react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | |||
6 | import { IFormField } from '../typings/generic'; | ||
7 | |||
8 | import styles from './styles'; | ||
9 | |||
10 | interface ILabel extends IFormField, LabelHTMLAttributes<HTMLLabelElement> { | ||
11 | classes: Classes; | ||
12 | isRequired: boolean; | ||
13 | } | ||
14 | |||
15 | class LabelComponent extends Component<ILabel> { | ||
16 | static defaultProps = { | ||
17 | showLabel: true, | ||
18 | }; | ||
19 | |||
20 | render() { | ||
21 | const { | ||
22 | title, | ||
23 | showLabel, | ||
24 | classes, | ||
25 | className, | ||
26 | children, | ||
27 | htmlFor, | ||
28 | isRequired, | ||
29 | } = this.props; | ||
30 | |||
31 | if (!showLabel) return children; | ||
32 | |||
33 | return ( | ||
34 | <label | ||
35 | className={classnames({ | ||
36 | [`${className}`]: className, | ||
37 | })} | ||
38 | htmlFor={htmlFor} | ||
39 | > | ||
40 | {showLabel && ( | ||
41 | <span className={classes.label}> | ||
42 | {title} | ||
43 | {isRequired && ' *'} | ||
44 | </span> | ||
45 | )} | ||
46 | <div className={classes.content}>{children}</div> | ||
47 | </label> | ||
48 | ); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | export const Label = injectSheet(styles)(LabelComponent); | ||
diff --git a/packages/forms/src/label/styles.ts b/packages/forms/src/label/styles.ts deleted file mode 100644 index 7c62b6b4c..000000000 --- a/packages/forms/src/label/styles.ts +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | import { Theme } from '../../../theme'; | ||
2 | |||
3 | export default (theme: Theme) => ({ | ||
4 | content: {}, | ||
5 | label: { | ||
6 | color: theme.labelColor, | ||
7 | fontSize: theme.uiFontSize, | ||
8 | }, | ||
9 | hasError: { | ||
10 | color: theme.brandDanger, | ||
11 | }, | ||
12 | }); | ||
diff --git a/packages/forms/src/select/index.tsx b/packages/forms/src/select/index.tsx deleted file mode 100644 index d965d3c93..000000000 --- a/packages/forms/src/select/index.tsx +++ /dev/null | |||
@@ -1,460 +0,0 @@ | |||
1 | import { | ||
2 | mdiArrowRightDropCircleOutline, | ||
3 | mdiCloseCircle, | ||
4 | mdiMagnify, | ||
5 | } from '@mdi/js'; | ||
6 | import Icon from '@mdi/react'; | ||
7 | import classnames from 'classnames'; | ||
8 | import { ChangeEvent, Component, createRef } from 'react'; | ||
9 | import injectStyle from 'react-jss'; | ||
10 | |||
11 | import { IFormField, IWithStyle } from '../typings/generic'; | ||
12 | import { Theme } from '../../../theme'; | ||
13 | |||
14 | import { Error } from '../error'; | ||
15 | import { Label } from '../label'; | ||
16 | import { Wrapper } from '../wrapper'; | ||
17 | |||
18 | interface IOptions { | ||
19 | [index: string]: string; | ||
20 | } | ||
21 | |||
22 | interface IData { | ||
23 | [index: string]: string; | ||
24 | } | ||
25 | |||
26 | interface IProps extends IFormField, IWithStyle { | ||
27 | actionText: string; | ||
28 | className?: string; | ||
29 | inputClassName?: string; | ||
30 | defaultValue?: string; | ||
31 | disabled?: boolean; | ||
32 | id?: string; | ||
33 | name: string; | ||
34 | options: IOptions; | ||
35 | value: string; | ||
36 | onChange: (event: ChangeEvent<HTMLInputElement>) => void; | ||
37 | showSearch: boolean; | ||
38 | data: IData; | ||
39 | } | ||
40 | |||
41 | interface IState { | ||
42 | open: boolean; | ||
43 | value: string; | ||
44 | needle: string; | ||
45 | selected: number; | ||
46 | options: IOptions; | ||
47 | } | ||
48 | |||
49 | let popupTransition: string = 'none'; | ||
50 | let toggleTransition: string = 'none'; | ||
51 | |||
52 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
53 | popupTransition = 'all 0.3s'; | ||
54 | toggleTransition = 'transform 0.3s'; | ||
55 | } | ||
56 | |||
57 | const styles = (theme: Theme) => ({ | ||
58 | select: { | ||
59 | background: theme.selectBackground, | ||
60 | border: theme.selectBorder, | ||
61 | borderRadius: theme.borderRadiusSmall, | ||
62 | height: theme.selectHeight, | ||
63 | fontSize: theme.uiFontSize, | ||
64 | width: '100%', | ||
65 | display: 'flex', | ||
66 | alignItems: 'center', | ||
67 | textAlign: 'left', | ||
68 | color: theme.selectColor, | ||
69 | }, | ||
70 | label: { | ||
71 | '& > div': { | ||
72 | marginTop: 5, | ||
73 | }, | ||
74 | }, | ||
75 | popup: { | ||
76 | opacity: 0, | ||
77 | height: 0, | ||
78 | overflowX: 'scroll', | ||
79 | border: theme.selectBorder, | ||
80 | borderTop: 0, | ||
81 | transition: popupTransition, | ||
82 | }, | ||
83 | open: { | ||
84 | opacity: 1, | ||
85 | height: 350, | ||
86 | background: theme.selectPopupBackground, | ||
87 | }, | ||
88 | option: { | ||
89 | padding: 10, | ||
90 | borderBottom: theme.selectOptionBorder, | ||
91 | color: theme.selectOptionColor, | ||
92 | |||
93 | '&:hover': { | ||
94 | background: theme.selectOptionItemHover, | ||
95 | color: theme.selectOptionItemHoverColor, | ||
96 | }, | ||
97 | '&:active': { | ||
98 | background: theme.selectOptionItemActive, | ||
99 | color: theme.selectOptionItemActiveColor, | ||
100 | }, | ||
101 | }, | ||
102 | selected: { | ||
103 | background: theme.selectOptionItemActive, | ||
104 | color: theme.selectOptionItemActiveColor, | ||
105 | }, | ||
106 | toggle: { | ||
107 | marginLeft: 'auto', | ||
108 | fill: theme.selectToggleColor, | ||
109 | transition: toggleTransition, | ||
110 | }, | ||
111 | toggleOpened: { | ||
112 | transform: 'rotateZ(90deg)', | ||
113 | }, | ||
114 | searchContainer: { | ||
115 | display: 'flex', | ||
116 | background: theme.selectSearchBackground, | ||
117 | alignItems: 'center', | ||
118 | paddingLeft: 10, | ||
119 | color: theme.selectColor, | ||
120 | |||
121 | '& svg': { | ||
122 | fill: theme.selectSearchColor, | ||
123 | }, | ||
124 | }, | ||
125 | search: { | ||
126 | border: 0, | ||
127 | width: '100%', | ||
128 | fontSize: theme.uiFontSize, | ||
129 | background: 'none', | ||
130 | marginLeft: 10, | ||
131 | padding: [10, 0], | ||
132 | color: theme.selectSearchColor, | ||
133 | }, | ||
134 | clearNeedle: { | ||
135 | background: 'none', | ||
136 | border: 0, | ||
137 | }, | ||
138 | focused: { | ||
139 | fontWeight: 'bold', | ||
140 | background: theme.selectOptionItemHover, | ||
141 | color: theme.selectOptionItemHoverColor, | ||
142 | }, | ||
143 | hasError: { | ||
144 | borderColor: theme.brandDanger, | ||
145 | }, | ||
146 | disabled: { | ||
147 | opacity: theme.selectDisabledOpacity, | ||
148 | }, | ||
149 | }); | ||
150 | |||
151 | class SelectComponent extends Component<IProps> { | ||
152 | public static defaultProps = { | ||
153 | onChange: () => {}, | ||
154 | showLabel: true, | ||
155 | disabled: false, | ||
156 | error: '', | ||
157 | }; | ||
158 | |||
159 | state = { | ||
160 | open: false, | ||
161 | value: '', | ||
162 | needle: '', | ||
163 | selected: 0, | ||
164 | options: null, | ||
165 | }; | ||
166 | |||
167 | private componentRef = createRef<HTMLDivElement>(); | ||
168 | |||
169 | private inputRef = createRef<HTMLInputElement>(); | ||
170 | |||
171 | private searchInputRef = createRef<HTMLInputElement>(); | ||
172 | |||
173 | private scrollContainerRef = createRef<HTMLDivElement>(); | ||
174 | |||
175 | private activeOptionRef = createRef<HTMLDivElement>(); | ||
176 | |||
177 | private keyListener: any; | ||
178 | |||
179 | componentWillReceiveProps(nextProps: IProps) { | ||
180 | if (nextProps.value && nextProps.value !== this.props.value) { | ||
181 | this.setState({ | ||
182 | value: nextProps.value, | ||
183 | }); | ||
184 | } | ||
185 | } | ||
186 | |||
187 | componentDidUpdate() { | ||
188 | const { open } = this.state; | ||
189 | |||
190 | if (this.searchInputRef && this.searchInputRef.current && open) { | ||
191 | this.searchInputRef.current.focus(); | ||
192 | } | ||
193 | } | ||
194 | |||
195 | componentDidMount() { | ||
196 | if (this.inputRef && this.inputRef.current) { | ||
197 | const { data } = this.props; | ||
198 | |||
199 | if (data) { | ||
200 | Object.keys(data).map( | ||
201 | key => (this.inputRef.current!.dataset[key] = data[key]), | ||
202 | ); | ||
203 | } | ||
204 | } | ||
205 | |||
206 | window.addEventListener('keydown', this.arrowKeysHandler.bind(this), false); | ||
207 | } | ||
208 | |||
209 | componentWillMount() { | ||
210 | const { value } = this.props; | ||
211 | |||
212 | if (this.componentRef && this.componentRef.current) { | ||
213 | this.componentRef.current.removeEventListener( | ||
214 | 'keydown', | ||
215 | this.keyListener, | ||
216 | ); | ||
217 | } | ||
218 | |||
219 | if (value) { | ||
220 | this.setState({ | ||
221 | value, | ||
222 | }); | ||
223 | } | ||
224 | |||
225 | this.setFilter(); | ||
226 | } | ||
227 | |||
228 | componentWillUnmount() { | ||
229 | // eslint-disable-next-line unicorn/no-invalid-remove-event-listener | ||
230 | window.removeEventListener('keydown', this.arrowKeysHandler.bind(this)); | ||
231 | } | ||
232 | |||
233 | setFilter(needle = '') { | ||
234 | const { options } = this.props; | ||
235 | |||
236 | let filteredOptions = {}; | ||
237 | if (needle) { | ||
238 | Object.keys(options).map(key => { | ||
239 | if ( | ||
240 | key.toLocaleLowerCase().startsWith(needle.toLocaleLowerCase()) || | ||
241 | options[key] | ||
242 | .toLocaleLowerCase() | ||
243 | .startsWith(needle.toLocaleLowerCase()) | ||
244 | ) { | ||
245 | Object.assign(filteredOptions, { | ||
246 | [`${key}`]: options[key], | ||
247 | }); | ||
248 | } | ||
249 | }); | ||
250 | } else { | ||
251 | filteredOptions = options; | ||
252 | } | ||
253 | |||
254 | this.setState({ | ||
255 | needle, | ||
256 | options: filteredOptions, | ||
257 | selected: 0, | ||
258 | }); | ||
259 | } | ||
260 | |||
261 | select(key: string) { | ||
262 | this.setState(() => ({ | ||
263 | value: key, | ||
264 | open: false, | ||
265 | })); | ||
266 | |||
267 | this.setFilter(); | ||
268 | |||
269 | if (this.props.onChange) { | ||
270 | this.props.onChange(key as any); | ||
271 | } | ||
272 | } | ||
273 | |||
274 | arrowKeysHandler(e: KeyboardEvent) { | ||
275 | const { selected, open, options } = this.state; | ||
276 | |||
277 | if (!open) return; | ||
278 | |||
279 | if (e.keyCode === 38 || e.keyCode === 40) { | ||
280 | e.preventDefault(); | ||
281 | } | ||
282 | |||
283 | if (this.componentRef && this.componentRef.current) { | ||
284 | if (e.keyCode === 38 && selected > 0) { | ||
285 | this.setState((state: IState) => ({ | ||
286 | selected: state.selected - 1, | ||
287 | })); | ||
288 | } else if ( | ||
289 | e.keyCode === 40 && | ||
290 | selected < Object.keys(options!).length - 1 | ||
291 | ) { | ||
292 | this.setState((state: IState) => ({ | ||
293 | selected: state.selected + 1, | ||
294 | })); | ||
295 | } else if (e.keyCode === 13) { | ||
296 | this.select(Object.keys(options!)[selected]); | ||
297 | } | ||
298 | |||
299 | if ( | ||
300 | this.activeOptionRef && | ||
301 | this.activeOptionRef.current && | ||
302 | this.scrollContainerRef && | ||
303 | this.scrollContainerRef.current | ||
304 | ) { | ||
305 | const containerTopOffset = this.scrollContainerRef.current.offsetTop; | ||
306 | const optionTopOffset = this.activeOptionRef.current.offsetTop; | ||
307 | |||
308 | const topOffset = optionTopOffset - containerTopOffset; | ||
309 | |||
310 | this.scrollContainerRef.current.scrollTop = topOffset - 35; | ||
311 | } | ||
312 | } | ||
313 | |||
314 | switch (e.keyCode) { | ||
315 | case 37: | ||
316 | case 39: | ||
317 | case 38: | ||
318 | case 40: // Arrow keys | ||
319 | case 32: | ||
320 | break; // Space | ||
321 | default: | ||
322 | break; // do not block other keys | ||
323 | } | ||
324 | } | ||
325 | |||
326 | render() { | ||
327 | const { | ||
328 | actionText, | ||
329 | classes, | ||
330 | className, | ||
331 | defaultValue, | ||
332 | disabled, | ||
333 | error, | ||
334 | id, | ||
335 | inputClassName, | ||
336 | name, | ||
337 | label, | ||
338 | showLabel, | ||
339 | showSearch, | ||
340 | onChange, | ||
341 | required, | ||
342 | } = this.props; | ||
343 | |||
344 | const { open, needle, value, selected, options } = this.state; | ||
345 | |||
346 | let selection = ''; | ||
347 | if (!value && defaultValue && options![defaultValue]) { | ||
348 | selection = options![defaultValue]; | ||
349 | } else if (value && options![value]) { | ||
350 | selection = options![value]; | ||
351 | } else { | ||
352 | selection = actionText; | ||
353 | } | ||
354 | |||
355 | return ( | ||
356 | <Wrapper className={className} identifier="franz-select"> | ||
357 | <Label | ||
358 | title={label} | ||
359 | showLabel={showLabel} | ||
360 | htmlFor={id} | ||
361 | className={classes.label} | ||
362 | isRequired={required} | ||
363 | > | ||
364 | <div | ||
365 | className={classnames({ | ||
366 | [`${classes.hasError}`]: error, | ||
367 | [`${classes.disabled}`]: disabled, | ||
368 | })} | ||
369 | ref={this.componentRef} | ||
370 | > | ||
371 | <button | ||
372 | type="button" | ||
373 | className={classnames({ | ||
374 | [`${inputClassName}`]: inputClassName, | ||
375 | [`${classes.select}`]: true, | ||
376 | [`${classes.hasError}`]: error, | ||
377 | })} | ||
378 | onClick={ | ||
379 | !disabled | ||
380 | ? () => | ||
381 | this.setState((state: IState) => ({ | ||
382 | open: !state.open, | ||
383 | })) | ||
384 | : () => {} | ||
385 | } | ||
386 | > | ||
387 | {selection} | ||
388 | <Icon | ||
389 | path={mdiArrowRightDropCircleOutline} | ||
390 | size={0.8} | ||
391 | className={classnames({ | ||
392 | [`${classes.toggle}`]: true, | ||
393 | [`${classes.toggleOpened}`]: open, | ||
394 | })} | ||
395 | /> | ||
396 | </button> | ||
397 | {showSearch && open && ( | ||
398 | <div className={classes.searchContainer}> | ||
399 | <Icon path={mdiMagnify} size={0.8} /> | ||
400 | <input | ||
401 | type="text" | ||
402 | value={needle} | ||
403 | onChange={e => this.setFilter(e.currentTarget.value)} | ||
404 | placeholder="Search" | ||
405 | className={classes.search} | ||
406 | ref={this.searchInputRef} | ||
407 | /> | ||
408 | {needle && ( | ||
409 | <button | ||
410 | type="button" | ||
411 | className={classes.clearNeedle} | ||
412 | onClick={() => this.setFilter()} | ||
413 | > | ||
414 | <Icon path={mdiCloseCircle} size={0.7} /> | ||
415 | </button> | ||
416 | )} | ||
417 | </div> | ||
418 | )} | ||
419 | <div | ||
420 | className={classnames({ | ||
421 | [`${classes.popup}`]: true, | ||
422 | [`${classes.open}`]: open, | ||
423 | })} | ||
424 | ref={this.scrollContainerRef} | ||
425 | > | ||
426 | {Object.keys(options!).map((key, i) => ( | ||
427 | <div | ||
428 | key={key} | ||
429 | onClick={() => this.select(key)} | ||
430 | className={classnames({ | ||
431 | [`${classes.option}`]: true, | ||
432 | [`${classes.selected}`]: options![key] === selection, | ||
433 | [`${classes.focused}`]: selected === i, | ||
434 | })} | ||
435 | onMouseOver={() => this.setState({ selected: i })} | ||
436 | ref={selected === i ? this.activeOptionRef : null} | ||
437 | > | ||
438 | {options![key]} | ||
439 | </div> | ||
440 | ))} | ||
441 | </div> | ||
442 | </div> | ||
443 | <input | ||
444 | className={classes.input} | ||
445 | id={id} | ||
446 | name={name} | ||
447 | type="hidden" | ||
448 | defaultValue={value} | ||
449 | onChange={onChange} | ||
450 | disabled={disabled} | ||
451 | ref={this.inputRef} | ||
452 | /> | ||
453 | </Label> | ||
454 | {error && <Error message={error} />} | ||
455 | </Wrapper> | ||
456 | ); | ||
457 | } | ||
458 | } | ||
459 | |||
460 | export const Select = injectStyle(styles)(SelectComponent); | ||
diff --git a/packages/forms/src/textarea/index.tsx b/packages/forms/src/textarea/index.tsx deleted file mode 100644 index 1b16698eb..000000000 --- a/packages/forms/src/textarea/index.tsx +++ /dev/null | |||
@@ -1,126 +0,0 @@ | |||
1 | import classnames from 'classnames'; | ||
2 | import { Component, createRef, TextareaHTMLAttributes } from 'react'; | ||
3 | import injectSheet from 'react-jss'; | ||
4 | |||
5 | import { IFormField, IWithStyle } from '../typings/generic'; | ||
6 | |||
7 | import { Error } from '../error'; | ||
8 | import { Label } from '../label'; | ||
9 | import { Wrapper } from '../wrapper'; | ||
10 | |||
11 | import styles from './styles'; | ||
12 | |||
13 | interface IData { | ||
14 | [index: string]: string; | ||
15 | } | ||
16 | |||
17 | interface IProps | ||
18 | extends TextareaHTMLAttributes<HTMLTextAreaElement>, | ||
19 | IFormField, | ||
20 | IWithStyle { | ||
21 | focus?: boolean; | ||
22 | data: IData; | ||
23 | textareaClassName?: string; | ||
24 | } | ||
25 | |||
26 | class TextareaComponent extends Component<IProps> { | ||
27 | static defaultProps = { | ||
28 | focus: false, | ||
29 | onChange: () => {}, | ||
30 | onBlur: () => {}, | ||
31 | onFocus: () => {}, | ||
32 | showLabel: true, | ||
33 | disabled: false, | ||
34 | rows: 5, | ||
35 | }; | ||
36 | |||
37 | private textareaRef = createRef<HTMLTextAreaElement>(); | ||
38 | |||
39 | componentDidMount() { | ||
40 | const { data } = this.props; | ||
41 | |||
42 | if (this.textareaRef && this.textareaRef.current && data) { | ||
43 | Object.keys(data).map( | ||
44 | key => (this.textareaRef.current!.dataset[key] = data[key]), | ||
45 | ); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | onChange(e: React.ChangeEvent<HTMLTextAreaElement>) { | ||
50 | const { onChange } = this.props; | ||
51 | |||
52 | if (onChange) { | ||
53 | onChange(e); | ||
54 | } | ||
55 | } | ||
56 | |||
57 | render() { | ||
58 | const { | ||
59 | classes, | ||
60 | className, | ||
61 | disabled, | ||
62 | error, | ||
63 | id, | ||
64 | textareaClassName, | ||
65 | label, | ||
66 | showLabel, | ||
67 | value, | ||
68 | name, | ||
69 | placeholder, | ||
70 | spellCheck, | ||
71 | onBlur, | ||
72 | onFocus, | ||
73 | minLength, | ||
74 | maxLength, | ||
75 | required, | ||
76 | rows, | ||
77 | noMargin, | ||
78 | } = this.props; | ||
79 | |||
80 | return ( | ||
81 | <Wrapper | ||
82 | className={className} | ||
83 | identifier="franz-textarea" | ||
84 | noMargin={noMargin} | ||
85 | > | ||
86 | <Label | ||
87 | title={label} | ||
88 | showLabel={showLabel} | ||
89 | htmlFor={id} | ||
90 | className={classes.label} | ||
91 | isRequired={required} | ||
92 | > | ||
93 | <div | ||
94 | className={classnames({ | ||
95 | [`${textareaClassName}`]: textareaClassName, | ||
96 | [`${classes.wrapper}`]: true, | ||
97 | [`${classes.disabled}`]: disabled, | ||
98 | [`${classes.hasError}`]: error, | ||
99 | })} | ||
100 | > | ||
101 | <textarea | ||
102 | id={id} | ||
103 | name={name} | ||
104 | placeholder={placeholder} | ||
105 | spellCheck={spellCheck} | ||
106 | className={classes.textarea} | ||
107 | ref={this.textareaRef} | ||
108 | onChange={this.onChange.bind(this)} | ||
109 | onFocus={onFocus} | ||
110 | onBlur={onBlur} | ||
111 | disabled={disabled} | ||
112 | minLength={minLength} | ||
113 | maxLength={maxLength} | ||
114 | rows={rows} | ||
115 | > | ||
116 | {value} | ||
117 | </textarea> | ||
118 | </div> | ||
119 | </Label> | ||
120 | {error && <Error message={error} />} | ||
121 | </Wrapper> | ||
122 | ); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | export const Textarea = injectSheet(styles)(TextareaComponent); | ||
diff --git a/packages/forms/src/textarea/styles.ts b/packages/forms/src/textarea/styles.ts deleted file mode 100644 index ff3a3202b..000000000 --- a/packages/forms/src/textarea/styles.ts +++ /dev/null | |||
@@ -1,54 +0,0 @@ | |||
1 | import { Property } from 'csstype'; | ||
2 | |||
3 | import { Theme } from '../../../theme'; | ||
4 | |||
5 | export default (theme: Theme) => ({ | ||
6 | label: { | ||
7 | '& > div': { | ||
8 | marginTop: 5, | ||
9 | }, | ||
10 | }, | ||
11 | disabled: { | ||
12 | opacity: theme.inputDisabledOpacity, | ||
13 | }, | ||
14 | formModifier: { | ||
15 | background: 'none', | ||
16 | border: 0, | ||
17 | borderLeft: theme.inputBorder, | ||
18 | padding: '4px 20px 0', | ||
19 | outline: 'none', | ||
20 | |||
21 | '&:active': { | ||
22 | opacity: 0.5, | ||
23 | }, | ||
24 | |||
25 | '& svg': { | ||
26 | fill: theme.inputModifierColor, | ||
27 | }, | ||
28 | }, | ||
29 | textarea: { | ||
30 | background: 'none', | ||
31 | border: 0, | ||
32 | fontSize: theme.uiFontSize, | ||
33 | outline: 'none', | ||
34 | padding: 8, | ||
35 | width: '100%', | ||
36 | color: theme.inputColor, | ||
37 | |||
38 | '&::placeholder': { | ||
39 | color: theme.inputPlaceholderColor, | ||
40 | }, | ||
41 | }, | ||
42 | wrapper: { | ||
43 | background: theme.inputBackground, | ||
44 | border: theme.inputBorder, | ||
45 | borderRadius: theme.borderRadiusSmall, | ||
46 | boxSizing: 'border-box' as Property.BoxSizing, | ||
47 | display: 'flex', | ||
48 | order: 1, | ||
49 | width: '100%', | ||
50 | }, | ||
51 | hasError: { | ||
52 | borderColor: theme.brandDanger, | ||
53 | }, | ||
54 | }); | ||
diff --git a/packages/forms/src/toggle/index.tsx b/packages/forms/src/toggle/index.tsx deleted file mode 100644 index f9ef5775a..000000000 --- a/packages/forms/src/toggle/index.tsx +++ /dev/null | |||
@@ -1,125 +0,0 @@ | |||
1 | import classnames from 'classnames'; | ||
2 | import { Property } from 'csstype'; | ||
3 | import { Component, InputHTMLAttributes } from 'react'; | ||
4 | import injectStyle from 'react-jss'; | ||
5 | |||
6 | import { IFormField, IWithStyle } from '../typings/generic'; | ||
7 | import { Theme } from '../../../theme'; | ||
8 | |||
9 | import { Error } from '../error'; | ||
10 | import { Label } from '../label'; | ||
11 | import { Wrapper } from '../wrapper'; | ||
12 | |||
13 | interface IProps | ||
14 | extends InputHTMLAttributes<HTMLInputElement>, | ||
15 | IFormField, | ||
16 | IWithStyle { | ||
17 | className?: string; | ||
18 | } | ||
19 | |||
20 | let buttonTransition: string = 'none'; | ||
21 | |||
22 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
23 | buttonTransition = 'all .5s'; | ||
24 | } | ||
25 | |||
26 | const styles = (theme: Theme) => ({ | ||
27 | toggle: { | ||
28 | background: theme.toggleBackground, | ||
29 | borderRadius: theme.borderRadius, | ||
30 | height: theme.toggleHeight, | ||
31 | position: 'relative' as Property.Position, | ||
32 | width: theme.toggleWidth, | ||
33 | }, | ||
34 | button: { | ||
35 | background: theme.toggleButton, | ||
36 | borderRadius: '100%', | ||
37 | boxShadow: '0 1px 4px rgba(0, 0, 0, .3)', | ||
38 | width: theme.toggleHeight - 2, | ||
39 | height: theme.toggleHeight - 2, | ||
40 | left: 1, | ||
41 | top: 1, | ||
42 | position: 'absolute' as Property.Position, | ||
43 | transition: buttonTransition, | ||
44 | }, | ||
45 | buttonActive: { | ||
46 | background: theme.toggleButtonActive, | ||
47 | left: theme.toggleWidth - theme.toggleHeight + 1, | ||
48 | }, | ||
49 | input: { | ||
50 | visibility: 'hidden' as any, | ||
51 | }, | ||
52 | disabled: { | ||
53 | opacity: theme.inputDisabledOpacity, | ||
54 | }, | ||
55 | toggleLabel: { | ||
56 | display: 'flex', | ||
57 | alignItems: 'center', | ||
58 | |||
59 | '& > span': { | ||
60 | order: 1, | ||
61 | marginLeft: 15, | ||
62 | }, | ||
63 | }, | ||
64 | }); | ||
65 | |||
66 | class ToggleComponent extends Component<IProps> { | ||
67 | public static defaultProps = { | ||
68 | onChange: () => {}, | ||
69 | showLabel: true, | ||
70 | disabled: false, | ||
71 | error: '', | ||
72 | }; | ||
73 | |||
74 | render() { | ||
75 | const { | ||
76 | classes, | ||
77 | className, | ||
78 | disabled, | ||
79 | error, | ||
80 | id, | ||
81 | label, | ||
82 | showLabel, | ||
83 | checked, | ||
84 | value, | ||
85 | onChange, | ||
86 | } = this.props; | ||
87 | |||
88 | return ( | ||
89 | <Wrapper className={className} identifier="franz-toggle"> | ||
90 | <Label | ||
91 | title={label} | ||
92 | showLabel={showLabel} | ||
93 | htmlFor={id} | ||
94 | className={classes.toggleLabel} | ||
95 | > | ||
96 | <div | ||
97 | className={classnames({ | ||
98 | [`${classes.toggle}`]: true, | ||
99 | [`${classes.disabled}`]: disabled, | ||
100 | })} | ||
101 | > | ||
102 | <div | ||
103 | className={classnames({ | ||
104 | [`${classes.button}`]: true, | ||
105 | [`${classes.buttonActive}`]: checked, | ||
106 | })} | ||
107 | /> | ||
108 | <input | ||
109 | className={classes.input} | ||
110 | id={id} | ||
111 | type="checkbox" | ||
112 | checked={checked} | ||
113 | value={value} | ||
114 | onChange={onChange} | ||
115 | disabled={disabled} | ||
116 | /> | ||
117 | </div> | ||
118 | </Label> | ||
119 | {error && <Error message={error} />} | ||
120 | </Wrapper> | ||
121 | ); | ||
122 | } | ||
123 | } | ||
124 | |||
125 | export const Toggle = injectStyle(styles)(ToggleComponent); | ||
diff --git a/packages/forms/src/typings/generic.ts b/packages/forms/src/typings/generic.ts deleted file mode 100644 index 0fd0cdbf3..000000000 --- a/packages/forms/src/typings/generic.ts +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | import { Classes } from 'jss'; | ||
2 | |||
3 | import { Theme } from '../../../theme'; | ||
4 | |||
5 | export interface IFormField { | ||
6 | showLabel?: boolean; | ||
7 | label?: string; | ||
8 | error?: string; | ||
9 | required?: boolean; | ||
10 | noMargin?: boolean; | ||
11 | } | ||
12 | |||
13 | export interface IWithStyle { | ||
14 | classes: Classes; | ||
15 | theme: Theme; | ||
16 | } | ||
17 | |||
18 | export type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N; | ||
19 | export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; | ||
diff --git a/packages/forms/src/wrapper/index.tsx b/packages/forms/src/wrapper/index.tsx deleted file mode 100644 index ffcd6fe0b..000000000 --- a/packages/forms/src/wrapper/index.tsx +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | import classnames from 'classnames'; | ||
2 | import { Component, ReactNode } from 'react'; | ||
3 | import injectStyle from 'react-jss'; | ||
4 | import { IWithStyle } from '../typings/generic'; | ||
5 | |||
6 | interface IProps extends IWithStyle { | ||
7 | children: ReactNode; | ||
8 | className?: string; | ||
9 | identifier: string; | ||
10 | noMargin?: boolean; | ||
11 | } | ||
12 | |||
13 | const styles = { | ||
14 | container: { | ||
15 | marginBottom: (props: IProps) => (props.noMargin ? 0 : 20), | ||
16 | }, | ||
17 | }; | ||
18 | |||
19 | class WrapperComponent extends Component<IProps> { | ||
20 | render() { | ||
21 | const { children, classes, className, identifier } = this.props; | ||
22 | |||
23 | return ( | ||
24 | <div | ||
25 | className={classnames({ | ||
26 | [`${classes.container}`]: true, | ||
27 | [`${className}`]: className, | ||
28 | })} | ||
29 | data-type={identifier} | ||
30 | > | ||
31 | {children} | ||
32 | </div> | ||
33 | ); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | export const Wrapper = injectStyle(styles)(WrapperComponent); | ||
diff --git a/packages/forms/tsconfig.json b/packages/forms/tsconfig.json deleted file mode 100644 index 015581136..000000000 --- a/packages/forms/tsconfig.json +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | { | ||
2 | "extends": "../../tsconfig.json", | ||
3 | "compilerOptions": { | ||
4 | "outDir": "lib", | ||
5 | "rootDir": "src" | ||
6 | }, | ||
7 | "references": [ | ||
8 | { | ||
9 | "path": "../theme" | ||
10 | } | ||
11 | ] | ||
12 | } | ||