summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.nvmrc2
-rw-r--r--package-lock.json11
-rw-r--r--package.json1
-rw-r--r--packages/forms/package-lock.json2
-rw-r--r--packages/forms/package.json2
-rw-r--r--packages/forms/src/button/index.tsx2
-rw-r--r--packages/forms/src/error/index.tsx4
-rw-r--r--packages/forms/src/index.ts1
-rw-r--r--packages/forms/src/input/index.tsx36
-rw-r--r--packages/forms/src/input/scorePassword.ts2
-rw-r--r--packages/forms/src/label/index.tsx4
-rw-r--r--packages/forms/src/select/index.tsx422
-rw-r--r--packages/forms/src/toggle/index.tsx15
-rw-r--r--packages/forms/src/wrapper/index.tsx12
-rw-r--r--packages/misty.yml4
-rw-r--r--packages/theme/package-lock.json2
-rw-r--r--packages/theme/src/themes/dark/index.ts12
-rw-r--r--packages/theme/src/themes/default/index.ts58
-rw-r--r--packages/ui/.gitignore2
-rw-r--r--packages/ui/package-lock.json207
-rw-r--r--packages/ui/package.json41
-rw-r--r--packages/ui/src/badge/index.tsx75
-rw-r--r--packages/ui/src/headline/index.tsx71
-rw-r--r--packages/ui/src/icon/index.tsx55
-rw-r--r--packages/ui/src/index.ts5
-rw-r--r--packages/ui/src/infobox/index.tsx194
-rw-r--r--packages/ui/src/loader/index.tsx45
-rw-r--r--packages/ui/src/typings/generic.ts10
-rw-r--r--packages/ui/tsconfig.json12
-rw-r--r--packages/ui/tslint.json3
-rw-r--r--packages/ui/webpack.config.js19
-rw-r--r--uidev/src/app.tsx8
-rw-r--r--uidev/src/stories/badge.stories.tsx21
-rw-r--r--uidev/src/stories/headline.stories.tsx54
-rw-r--r--uidev/src/stories/icon.stories.tsx53
-rw-r--r--uidev/src/stories/infobox.stories.tsx126
-rw-r--r--uidev/src/stories/loader.stories.tsx14
-rw-r--r--uidev/src/stories/select.stories.tsx320
38 files changed, 1898 insertions, 29 deletions
diff --git a/.nvmrc b/.nvmrc
index 714ea4c61..0ca1348de 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
10.13.0 \ No newline at end of file 10.14.0
diff --git a/package-lock.json b/package-lock.json
index a432dda40..435909e89 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2724,7 +2724,7 @@
2724 "requires": { 2724 "requires": {
2725 "@mdi/js": "^3.3.92", 2725 "@mdi/js": "^3.3.92",
2726 "@mdi/react": "^1.1.0", 2726 "@mdi/react": "^1.1.0",
2727 "@meetfranz/theme": "^1.0.4", 2727 "@meetfranz/theme": "file:packages/theme",
2728 "react-html-attributes": "^1.4.3", 2728 "react-html-attributes": "^1.4.3",
2729 "react-loader": "^2.4.5" 2729 "react-loader": "^2.4.5"
2730 }, 2730 },
@@ -2785,6 +2785,15 @@
2785 "color": "^3.1.0" 2785 "color": "^3.1.0"
2786 } 2786 }
2787 }, 2787 },
2788 "@meetfranz/ui": {
2789 "version": "file:packages/ui",
2790 "requires": {
2791 "@mdi/js": "^3.3.92",
2792 "@mdi/react": "^1.1.0",
2793 "@meetfranz/theme": "file:packages/theme",
2794 "react-loader": "^2.4.5"
2795 }
2796 },
2788 "@mrmlnc/readdir-enhanced": { 2797 "@mrmlnc/readdir-enhanced": {
2789 "version": "2.2.1", 2798 "version": "2.2.1",
2790 "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", 2799 "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
diff --git a/package.json b/package.json
index f9bf78f1d..1266eb117 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
35 "@meetfranz/electron-notification-state": "^1.0.0", 35 "@meetfranz/electron-notification-state": "^1.0.0",
36 "@meetfranz/forms": "file:packages/forms", 36 "@meetfranz/forms": "file:packages/forms",
37 "@meetfranz/theme": "file:packages/theme", 37 "@meetfranz/theme": "file:packages/theme",
38 "@meetfranz/ui": "file:packages/ui",
38 "address-rfc2822": "^2.0.1", 39 "address-rfc2822": "^2.0.1",
39 "auto-launch": "https://github.com/meetfranz/node-auto-launch.git", 40 "auto-launch": "https://github.com/meetfranz/node-auto-launch.git",
40 "classnames": "2.2.6", 41 "classnames": "2.2.6",
diff --git a/packages/forms/package-lock.json b/packages/forms/package-lock.json
index cdfab23b5..834db8d33 100644
--- a/packages/forms/package-lock.json
+++ b/packages/forms/package-lock.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "@meetfranz/forms", 2 "name": "@meetfranz/forms",
3 "version": "1.0.1", 3 "version": "1.0.4",
4 "lockfileVersion": 1, 4 "lockfileVersion": 1,
5 "requires": true, 5 "requires": true,
6 "dependencies": { 6 "dependencies": {
diff --git a/packages/forms/package.json b/packages/forms/package.json
index 8dc9920c8..32b81eed2 100644
--- a/packages/forms/package.json
+++ b/packages/forms/package.json
@@ -25,7 +25,7 @@
25 "dependencies": { 25 "dependencies": {
26 "@mdi/js": "^3.3.92", 26 "@mdi/js": "^3.3.92",
27 "@mdi/react": "^1.1.0", 27 "@mdi/react": "^1.1.0",
28 "@meetfranz/theme": "^1.0.4", 28 "@meetfranz/theme": "file:../theme",
29 "react-html-attributes": "^1.4.3", 29 "react-html-attributes": "^1.4.3",
30 "react-loader": "^2.4.5" 30 "react-loader": "^2.4.5"
31 }, 31 },
diff --git a/packages/forms/src/button/index.tsx b/packages/forms/src/button/index.tsx
index 92d69ae0e..b7cca7fa4 100644
--- a/packages/forms/src/button/index.tsx
+++ b/packages/forms/src/button/index.tsx
@@ -8,7 +8,7 @@ import React, { Component } from 'react';
8import injectStyle from 'react-jss'; 8import injectStyle from 'react-jss';
9import Loader from 'react-loader'; 9import Loader from 'react-loader';
10 10
11import { IFormField, IWithStyle, Omit } from '../typings/generic'; 11import { IFormField, IWithStyle } from '../typings/generic';
12 12
13type ButtonType = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'inverted'; 13type ButtonType = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'inverted';
14 14
diff --git a/packages/forms/src/error/index.tsx b/packages/forms/src/error/index.tsx
index 3feaef7f6..9d26e086d 100644
--- a/packages/forms/src/error/index.tsx
+++ b/packages/forms/src/error/index.tsx
@@ -12,7 +12,7 @@ interface IProps {
12} 12}
13 13
14@observer 14@observer
15class Error extends Component<IProps> { 15class ErrorComponent extends Component<IProps> {
16 render() { 16 render() {
17 const { 17 const {
18 classes, 18 classes,
@@ -29,4 +29,4 @@ class Error extends Component<IProps> {
29 } 29 }
30} 30}
31 31
32export default injectSheet(styles)(Error); 32export const Error = injectSheet(styles)(ErrorComponent);
diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts
index fbeb7e3d3..ea47fe25e 100644
--- a/packages/forms/src/index.ts
+++ b/packages/forms/src/index.ts
@@ -1,3 +1,4 @@
1export { Input } from './input'; 1export { Input } from './input';
2export { Toggle } from './toggle'; 2export { Toggle } from './toggle';
3export { Button } from './button'; 3export { Button } from './button';
4export { Select } from './select';
diff --git a/packages/forms/src/input/index.tsx b/packages/forms/src/input/index.tsx
index 9fcf48010..cd6da3778 100644
--- a/packages/forms/src/input/index.tsx
+++ b/packages/forms/src/input/index.tsx
@@ -7,19 +7,25 @@ import injectSheet from 'react-jss';
7 7
8import { IFormField, IWithStyle } from '../typings/generic'; 8import { IFormField, IWithStyle } from '../typings/generic';
9 9
10import Error from '../error'; 10import { Error } from '../error';
11import Label from '../label'; 11import { Label } from '../label';
12import Wrapper from '../wrapper'; 12import { Wrapper } from '../wrapper';
13import scorePasswordFunc from './scorePassword'; 13import { scorePasswordFunc } from './scorePassword';
14 14
15import styles from './styles'; 15import styles from './styles';
16 16
17interface IData {
18 [index: string]: string;
19}
20
17interface IProps extends React.InputHTMLAttributes<HTMLInputElement>, IFormField, IWithStyle { 21interface IProps extends React.InputHTMLAttributes<HTMLInputElement>, IFormField, IWithStyle {
18 focus?: boolean; 22 focus?: boolean;
19 prefix?: string; 23 prefix?: string;
20 suffix?: string; 24 suffix?: string;
21 scorePassword?: boolean; 25 scorePassword?: boolean;
22 showPasswordToggle?: boolean; 26 showPasswordToggle?: boolean;
27 data: IData;
28 inputClassName?: string;
23} 29}
24 30
25interface IState { 31interface IState {
@@ -48,10 +54,16 @@ class InputComponent extends Component<IProps, IState> {
48 private inputRef = createRef<HTMLInputElement>(); 54 private inputRef = createRef<HTMLInputElement>();
49 55
50 componentDidMount() { 56 componentDidMount() {
51 const { focus } = this.props; 57 const { focus, data } = this.props;
58
59 if (this.inputRef && this.inputRef.current) {
60 if (focus) {
61 this.inputRef.current.focus();
62 }
52 63
53 if (focus && this.inputRef && this.inputRef.current) { 64 if (data) {
54 this.inputRef.current.focus(); 65 Object.keys(data).map(key => this.inputRef.current!.dataset[key] = data[key]);
66 }
55 } 67 }
56 } 68 }
57 69
@@ -77,6 +89,7 @@ class InputComponent extends Component<IProps, IState> {
77 disabled, 89 disabled,
78 error, 90 error,
79 id, 91 id,
92 inputClassName,
80 label, 93 label,
81 prefix, 94 prefix,
82 scorePassword, 95 scorePassword,
@@ -99,15 +112,17 @@ class InputComponent extends Component<IProps, IState> {
99 const inputType = type === 'password' && showPassword ? 'text' : type; 112 const inputType = type === 'password' && showPassword ? 'text' : type;
100 113
101 return ( 114 return (
102 <Wrapper> 115 <Wrapper
116 className={className}
117 >
103 <Label 118 <Label
104 title={label} 119 title={label}
105 showLabel={showLabel} 120 showLabel={showLabel}
106 htmlFor={id} 121 htmlFor={id}
107 className={className}
108 > 122 >
109 <div 123 <div
110 className={classnames({ 124 className={classnames({
125 [`${inputClassName}`]: inputClassName,
111 [`${classes.hasPasswordScore}`]: scorePassword, 126 [`${classes.hasPasswordScore}`]: scorePassword,
112 [`${classes.wrapper}`]: true, 127 [`${classes.wrapper}`]: true,
113 [`${classes.disabled}`]: disabled, 128 [`${classes.disabled}`]: disabled,
@@ -122,13 +137,14 @@ class InputComponent extends Component<IProps, IState> {
122 id={id} 137 id={id}
123 type={inputType} 138 type={inputType}
124 name={name} 139 name={name}
125 value={value} 140 defaultValue={value as string}
126 placeholder={placeholder} 141 placeholder={placeholder}
127 spellCheck={spellCheck} 142 spellCheck={spellCheck}
128 className={classes.input} 143 className={classes.input}
129 ref={this.inputRef} 144 ref={this.inputRef}
130 onChange={this.onChange.bind(this)} 145 onChange={this.onChange.bind(this)}
131 onBlur={onBlur} 146 onBlur={onBlur}
147 disabled={disabled}
132 /> 148 />
133 {suffix && ( 149 {suffix && (
134 <span className={classes.suffix}> 150 <span className={classes.suffix}>
diff --git a/packages/forms/src/input/scorePassword.ts b/packages/forms/src/input/scorePassword.ts
index bdad7aa28..0b7719ec1 100644
--- a/packages/forms/src/input/scorePassword.ts
+++ b/packages/forms/src/input/scorePassword.ts
@@ -10,7 +10,7 @@ interface IVariations {
10 upper: boolean; 10 upper: boolean;
11} 11}
12 12
13export default function scorePasswordFunc(password: string): number { 13export function scorePasswordFunc(password: string): number {
14 let score: number = 0; 14 let score: number = 0;
15 if (!password) { 15 if (!password) {
16 return score; 16 return score;
diff --git a/packages/forms/src/label/index.tsx b/packages/forms/src/label/index.tsx
index 348b820c5..ee3268b16 100644
--- a/packages/forms/src/label/index.tsx
+++ b/packages/forms/src/label/index.tsx
@@ -13,7 +13,7 @@ interface ILabel extends IFormField, React.LabelHTMLAttributes<HTMLLabelElement>
13} 13}
14 14
15@observer 15@observer
16class Label extends Component<ILabel> { 16class LabelComponent extends Component<ILabel> {
17 static defaultProps = { 17 static defaultProps = {
18 showLabel: true, 18 showLabel: true,
19 }; 19 };
@@ -46,4 +46,4 @@ class Label extends Component<ILabel> {
46 } 46 }
47} 47}
48 48
49export default injectSheet(styles)(Label); 49export const Label = injectSheet(styles)(LabelComponent);
diff --git a/packages/forms/src/select/index.tsx b/packages/forms/src/select/index.tsx
new file mode 100644
index 000000000..58bb7317a
--- /dev/null
+++ b/packages/forms/src/select/index.tsx
@@ -0,0 +1,422 @@
1import { mdiArrowRightDropCircleOutline, mdiCloseCircle, mdiMagnify } from '@mdi/js';
2import Icon from '@mdi/react';
3import { Theme } from '@meetfranz/theme';
4import classnames from 'classnames';
5import debounce from 'lodash/debounce';
6import { observer } from 'mobx-react';
7import React, { Component, createRef } from 'react';
8import injectStyle from 'react-jss';
9
10import { IFormField, IWithStyle } from '../typings/generic';
11
12import { NONAME } from 'dns';
13import { Error } from '../error';
14import { Label } from '../label';
15import { Wrapper } from '../wrapper';
16
17interface IOptions {
18 [index: string]: string;
19}
20
21interface IData {
22 [index: string]: string;
23}
24
25interface IProps extends IFormField, IWithStyle {
26 actionText: string;
27 className?: string;
28 inputClassName?: string;
29 defaultValue?: string;
30 disabled?: boolean;
31 id?: string;
32 name: string;
33 options: IOptions;
34 value: string;
35 onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
36 showSearch: boolean;
37 data: IData;
38}
39
40interface IState {
41 open: boolean;
42 value: string;
43 needle: string;
44 selected: number;
45 options: IOptions;
46}
47
48const styles = (theme: Theme) => ({
49 select: {
50 background: theme.selectBackground,
51 border: theme.selectBorder,
52 borderRadius: theme.borderRadiusSmall,
53 height: theme.selectHeight,
54 fontSize: theme.uiFontSize,
55 width: '100%',
56 display: 'flex',
57 alignItems: 'center',
58 textAlign: 'left',
59 color: theme.selectColor,
60 },
61 popup: {
62 opacity: 0,
63 height: 0,
64 overflowX: 'scroll',
65 border: theme.selectBorder,
66 borderTop: 0,
67 transition: 'all 0.3s',
68 },
69 open: {
70 opacity: 1,
71 height: 350,
72 background: theme.selectPopupBackground,
73 },
74 option: {
75 padding: 10,
76 borderBottom: theme.selectOptionBorder,
77 color: theme.selectOptionColor,
78
79 '&:hover': {
80 background: theme.selectOptionItemHover,
81 color: theme.selectOptionItemHoverColor,
82 },
83 '&:active': {
84 background: theme.selectOptionItemActive,
85 color: theme.selectOptionItemActiveColor,
86 },
87 },
88 selected: {
89 fontWeight: 'bold',
90 },
91 toggle: {
92 marginLeft: 'auto',
93 fill: theme.selectToggleColor,
94 transition: 'transform 0.3s',
95 },
96 toggleOpened: {
97 transform: 'rotateZ(90deg)',
98 },
99 searchContainer: {
100 display: 'flex',
101 background: theme.selectSearchBackground,
102 alignItems: 'center',
103 paddingLeft: 10,
104 color: theme.selectColor,
105
106 '& svg': {
107 fill: theme.selectSearchColor,
108 },
109 },
110 search: {
111 border: 0,
112 width: '100%',
113 fontSize: theme.uiFontSize,
114 background: 'none',
115 marginLeft: 10,
116 padding: [10, 0],
117 color: theme.selectSearchColor,
118 },
119 clearNeedle: {
120 background: 'none',
121 border: 0,
122 },
123 focused: {
124 fontWeight: 'bold',
125 background: theme.selectOptionItemHover,
126 color: theme.selectOptionItemHoverColor,
127 },
128 hasError: {
129 borderColor: theme.brandDanger,
130 },
131 disabled: {
132 opacity: theme.selectDisabledOpacity,
133 },
134});
135
136@observer
137class SelectComponent extends Component<IProps> {
138 public static defaultProps = {
139 onChange: () => {},
140 showLabel: true,
141 disabled: false,
142 error: '',
143 };
144
145 state = {
146 open: false,
147 value: '',
148 needle: '',
149 selected: 0,
150 options: null,
151 };
152
153 private componentRef = createRef<HTMLDivElement>();
154 private inputRef = createRef<HTMLInputElement>();
155 private searchInputRef = createRef<HTMLInputElement>();
156 private scrollContainerRef = createRef<HTMLDivElement>();
157 private activeOptionRef = createRef<HTMLDivElement>();
158
159 private keyListener: any;
160
161 componentWillReceiveProps(nextProps: IProps) {
162 if (nextProps.value && nextProps.value !== this.props.value) {
163 this.setState({
164 value: nextProps.value,
165 });
166 }
167 }
168
169 componentDidUpdate() {
170 const {
171 open,
172 } = this.state;
173
174 if (this.searchInputRef && this.searchInputRef.current) {
175 if (open) {
176 this.searchInputRef.current.focus();
177 }
178 }
179 }
180
181 componentDidMount() {
182 if (this.componentRef && this.componentRef.current) {
183 this.keyListener = this.componentRef.current.addEventListener('keydown', debounce((e) => {
184 const {
185 selected,
186 open,
187 options,
188 } = this.state;
189
190 if (!open) return;
191
192 if (e.keyCode === 38 && selected > 0) {
193 this.setState((state: IState) => ({
194 selected: state.selected - 1,
195 }));
196 } else if (e.keyCode === 40 && selected < Object.keys(options!).length - 1) {
197 this.setState((state: IState) => ({
198 selected: state.selected + 1,
199 }));
200 } else if (e.keyCode === 13) {
201 this.select(Object.keys(options!)[selected]);
202 }
203
204 if (this.activeOptionRef && this.activeOptionRef.current && this.scrollContainerRef && this.scrollContainerRef.current) {
205 const containerTopOffset = this.scrollContainerRef.current.offsetTop;
206 const optionTopOffset = this.activeOptionRef.current.offsetTop;
207
208 const topOffset = optionTopOffset - containerTopOffset;
209
210 this.scrollContainerRef.current.scrollTop = topOffset - 35;
211 }
212 }, 10, {
213 leading: true,
214 }));
215 }
216
217 if (this.inputRef && this.inputRef.current) {
218 const {
219 data,
220 } = this.props;
221
222 if (data) {
223 Object.keys(data).map(key => this.inputRef.current!.dataset[key] = data[key]);
224 }
225 }
226 }
227
228 componentWillMount() {
229 const { value } = this.props;
230
231 if (this.componentRef && this.componentRef.current) {
232 this.componentRef.current.removeEventListener('keydown', this.keyListener);
233 }
234
235 if (value) {
236 this.setState({
237 value,
238 });
239 }
240
241 this.setFilter();
242 }
243
244 setFilter(needle: string = '') {
245 const { options } = this.props;
246
247 let filteredOptions = {};
248 if (needle) {
249 Object.keys(options).map((key) => {
250 if (key.toLocaleLowerCase().startsWith(needle.toLocaleLowerCase()) || options[key].toLocaleLowerCase().startsWith(needle.toLocaleLowerCase())) {
251 Object.assign(filteredOptions, {
252 [`${key}`]: options[key],
253 });
254 }
255 });
256 } else {
257 filteredOptions = options;
258 }
259
260 this.setState({
261 needle,
262 options: filteredOptions,
263 selected: 0,
264 });
265 }
266
267 select(key: string) {
268 this.setState((state: IState) => ({
269 value: key,
270 open: false,
271 }));
272
273 this.setFilter();
274
275 if (this.props.onChange) {
276 this.props.onChange(key as any);
277 }
278 }
279
280 render() {
281 const {
282 actionText,
283 classes,
284 className,
285 defaultValue,
286 disabled,
287 error,
288 id,
289 inputClassName,
290 name,
291 label,
292 showLabel,
293 showSearch,
294 onChange,
295 } = this.props;
296
297 const {
298 open,
299 needle,
300 value,
301 selected,
302 options,
303 } = this.state;
304
305 let selection = '';
306 if (!value && defaultValue && options![defaultValue]) {
307 selection = options![defaultValue];
308 } else if (value && options![value]) {
309 selection = options![value];
310 } else {
311 selection = actionText;
312 }
313
314 return (
315 <Wrapper
316 className={className}
317 >
318 <Label
319 title={label}
320 showLabel={showLabel}
321 htmlFor={id}
322 >
323 <div
324 className={classnames({
325 [`${classes.hasError}`]: error,
326 [`${classes.disabled}`]: disabled,
327 })}
328 ref={this.componentRef}
329 >
330 <button
331 type="button"
332 className={classnames({
333 [`${inputClassName}`]: inputClassName,
334 [`${classes.select}`]: true,
335 [`${classes.hasError}`]: error,
336 })}
337 onClick= {!disabled ? () => this.setState((state: IState) => ({
338 open: !state.open,
339 })) : () => {}}
340 >
341 {selection}
342 <Icon
343 path={mdiArrowRightDropCircleOutline}
344 size={0.8}
345 className={classnames({
346 [`${classes.toggle}`]: true,
347 [`${classes.toggleOpened}`]: open,
348 })}
349 />
350 </button>
351 {showSearch && open && (
352 <div className={classes.searchContainer}>
353 <Icon
354 path={mdiMagnify}
355 size={0.8}
356 />
357 <input
358 type="text"
359 value={needle}
360 onChange={e => this.setFilter(e.currentTarget.value)}
361 placeholder="Search"
362 className={classes.search}
363 ref={this.searchInputRef}
364 />
365 {needle && (
366 <button
367 type="button"
368 className={classes.clearNeedle}
369 onClick={() => this.setState({ needle: '', selected: -1 })}
370 >
371 <Icon
372 path={mdiCloseCircle}
373 size={0.7}
374 />
375 </button>
376 )}
377 </div>
378 )}
379 <div
380 className={classnames({
381 [`${classes.popup}`]: true,
382 [`${classes.open}`]: open,
383 })}
384 ref={this.scrollContainerRef}
385 >
386 {Object.keys(options!).map(((key, i) => (
387 <div
388 key={key}
389 onClick={() => this.select(key)}
390 className={classnames({
391 [`${classes.option}`]: true,
392 [`${classes.selected}`]: options![key] === selection,
393 [`${classes.focused}`]: selected === i,
394 })}
395 onMouseOver={() => this.setState({ selected: i })}
396 ref={selected === i ? this.activeOptionRef : null}
397 >
398 {options![key]}
399 </div>
400 )))}
401 </div>
402 </div>
403 <input
404 className={classes.input}
405 id={id}
406 name={name}
407 type="hidden"
408 defaultValue={value}
409 onChange={onChange}
410 disabled={disabled}
411 ref={this.inputRef}
412 />
413 </Label>
414 {error && (
415 <Error message={error} />
416 )}
417 </Wrapper>
418 );
419 }
420}
421
422export const Select = injectStyle(styles)(SelectComponent);
diff --git a/packages/forms/src/toggle/index.tsx b/packages/forms/src/toggle/index.tsx
index a1cd7f1a4..4f446ab1a 100644
--- a/packages/forms/src/toggle/index.tsx
+++ b/packages/forms/src/toggle/index.tsx
@@ -7,11 +7,13 @@ import injectStyle from 'react-jss';
7 7
8import { IFormField, IWithStyle, Omit } from '../typings/generic'; 8import { IFormField, IWithStyle, Omit } from '../typings/generic';
9 9
10import Error from '../error'; 10import { Error } from '../error';
11import Label from '../label'; 11import { Label } from '../label';
12import Wrapper from '../wrapper'; 12import { Wrapper } from '../wrapper';
13 13
14interface IProps extends React.InputHTMLAttributes<HTMLInputElement>, IFormField, IWithStyle {} 14interface IProps extends React.InputHTMLAttributes<HTMLInputElement>, IFormField, IWithStyle {
15 className?: string;
16}
15 17
16const styles = (theme: Theme) => ({ 18const styles = (theme: Theme) => ({
17 toggle: { 19 toggle: {
@@ -65,6 +67,7 @@ class ToggleComponent extends Component<IProps> {
65 render() { 67 render() {
66 const { 68 const {
67 classes, 69 classes,
70 className,
68 disabled, 71 disabled,
69 error, 72 error,
70 id, 73 id,
@@ -76,7 +79,9 @@ class ToggleComponent extends Component<IProps> {
76 } = this.props; 79 } = this.props;
77 80
78 return ( 81 return (
79 <Wrapper> 82 <Wrapper
83 className={className}
84 >
80 <Label 85 <Label
81 title={label} 86 title={label}
82 showLabel={showLabel} 87 showLabel={showLabel}
diff --git a/packages/forms/src/wrapper/index.tsx b/packages/forms/src/wrapper/index.tsx
index 633cc4c99..87e2c6513 100644
--- a/packages/forms/src/wrapper/index.tsx
+++ b/packages/forms/src/wrapper/index.tsx
@@ -1,3 +1,4 @@
1import classnames from 'classnames';
1import { observer } from 'mobx-react'; 2import { observer } from 'mobx-react';
2import React, { Component } from 'react'; 3import React, { Component } from 'react';
3import injectStyle from 'react-jss'; 4import injectStyle from 'react-jss';
@@ -7,22 +8,27 @@ import styles from './styles';
7 8
8interface IProps extends IWithStyle { 9interface IProps extends IWithStyle {
9 children: React.ReactNode; 10 children: React.ReactNode;
11 className?: string;
10} 12}
11 13
12@observer 14@observer
13class Wrapper extends Component<IProps> { 15class WrapperComponent extends Component<IProps> {
14 render() { 16 render() {
15 const { 17 const {
16 children, 18 children,
17 classes, 19 classes,
20 className,
18 } = this.props; 21 } = this.props;
19 22
20 return ( 23 return (
21 <div className={classes.container}> 24 <div className={classnames({
25 [`${classes.container}`]: true,
26 [`${className}`]: className,
27 })}>
22 {children} 28 {children}
23 </div> 29 </div>
24 ); 30 );
25 } 31 }
26} 32}
27 33
28export default injectStyle(styles)(Wrapper); 34export const Wrapper = injectStyle(styles)(WrapperComponent);
diff --git a/packages/misty.yml b/packages/misty.yml
index 2750925c0..2d8cff014 100644
--- a/packages/misty.yml
+++ b/packages/misty.yml
@@ -5,3 +5,7 @@ theme:
5forms: 5forms:
6 cwd: ./forms 6 cwd: ./forms
7 cmd: npm run dev 7 cmd: npm run dev
8
9ui:
10 cwd: ./ui
11 cmd: npm run dev
diff --git a/packages/theme/package-lock.json b/packages/theme/package-lock.json
index a1673ffe8..f74af2f24 100644
--- a/packages/theme/package-lock.json
+++ b/packages/theme/package-lock.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "@meetfranz/theme", 2 "name": "@meetfranz/theme",
3 "version": "1.0.2", 3 "version": "1.0.4",
4 "lockfileVersion": 1, 4 "lockfileVersion": 1,
5 "requires": true, 5 "requires": true,
6 "dependencies": { 6 "dependencies": {
diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts
index 58ef63217..9b6bf055c 100644
--- a/packages/theme/src/themes/dark/index.ts
+++ b/packages/theme/src/themes/dark/index.ts
@@ -48,3 +48,15 @@ export const buttonLoaderColor = {
48 danger: '#FFF', 48 danger: '#FFF',
49 inverted: defaultStyles.brandPrimary, 49 inverted: defaultStyles.brandPrimary,
50}; 50};
51
52// Select
53export const selectBackground = inputBackground;
54export const selectBorder = inputBorder;
55export const selectColor = inputColor;
56export const selectToggleColor = inputPrefixColor;
57export const selectPopupBackground = legacyStyles.darkThemeGrayLight;
58export const selectOptionColor = '#FFF';
59export const selectOptionBorder = `1px solid ${color(legacyStyles.darkThemeGrayLight).darken(0.2).hex()}`;
60export const selectOptionItemHover = color(legacyStyles.darkThemeGrayLight).darken(0.2).hex();
61export const selectOptionItemHoverColor = selectColor;
62export const selectSearchColor = inputBackground;
diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts
index 0f78928c3..d36558ff0 100644
--- a/packages/theme/src/themes/default/index.ts
+++ b/packages/theme/src/themes/default/index.ts
@@ -2,6 +2,14 @@ import color from 'color';
2 2
3import * as legacyStyles from '../legacy'; 3import * as legacyStyles from '../legacy';
4 4
5export interface IStyleTypes {
6 [index: string]: {
7 accent: string;
8 contrast: string;
9 border?: string;
10 };
11}
12
5export const brandPrimary = '#3498db'; 13export const brandPrimary = '#3498db';
6export const brandSuccess = '#5cb85c'; 14export const brandSuccess = '#5cb85c';
7export const brandInfo = '#5bc0de'; 15export const brandInfo = '#5bc0de';
@@ -51,6 +59,35 @@ export const toggleButtonActive = brandPrimary;
51export const toggleWidth = 40; 59export const toggleWidth = 40;
52export const toggleHeight = 14; 60export const toggleHeight = 14;
53 61
62// Style Types
63export const styleTypes: IStyleTypes = {
64 primary: {
65 accent: brandPrimary,
66 contrast: '#FFF',
67 },
68 secondary: {
69 accent: legacyStyles.themeGrayLighter,
70 contrast: legacyStyles.themeGray,
71 },
72 success: {
73 accent: brandSuccess,
74 contrast: '#FFF',
75 },
76 warning: {
77 accent: brandWarning,
78 contrast: '#FFF',
79 },
80 danger: {
81 accent: brandDanger,
82 contrast: '#FFF',
83 },
84 inverted: {
85 accent: 'none',
86 contrast: brandPrimary,
87 border: `1px solid ${brandPrimary}`,
88 },
89};
90
54// Button 91// Button
55export const buttonPrimaryBackground = brandPrimary; 92export const buttonPrimaryBackground = brandPrimary;
56export const buttonPrimaryTextColor = '#FFF'; 93export const buttonPrimaryTextColor = '#FFF';
@@ -79,3 +116,24 @@ export const buttonLoaderColor = {
79 danger: '#FFF', 116 danger: '#FFF',
80 inverted: brandPrimary, 117 inverted: brandPrimary,
81}; 118};
119
120// Select
121export const selectBackground = inputBackground;
122export const selectBorder = inputBorder;
123export const selectHeight = inputHeight;
124export const selectColor = inputColor;
125export const selectToggleColor = inputPrefixColor;
126export const selectPopupBackground = '#FFF';
127export const selectOptionColor = inputColor;
128export const selectOptionBorder = `1px solid ${legacyStyles.themeGrayLightest}`;
129export const selectOptionItemHover = legacyStyles.themeGrayLighter;
130export const selectOptionItemHoverColor = selectColor;
131export const selectOptionItemActive = brandPrimary;
132export const selectOptionItemActiveColor = '#FFF';
133export const selectSearchBackground = legacyStyles.themeGrayLighter;
134export const selectSearchColor = inputColor;
135export const selectDisabledOpacity = inputDisabledOpacity;
136
137// Badge
138export const badgeFontSize = uiFontSize - 2;
139export const badgeBorderRadius = 50;
diff --git a/packages/ui/.gitignore b/packages/ui/.gitignore
new file mode 100644
index 000000000..d01826a6b
--- /dev/null
+++ b/packages/ui/.gitignore
@@ -0,0 +1,2 @@
1node_modules/
2lib
diff --git a/packages/ui/package-lock.json b/packages/ui/package-lock.json
new file mode 100644
index 000000000..8fa68a29b
--- /dev/null
+++ b/packages/ui/package-lock.json
@@ -0,0 +1,207 @@
1{
2 "name": "@meetfranz/ui",
3 "version": "0.0.0",
4 "lockfileVersion": 1,
5 "requires": true,
6 "dependencies": {
7 "@mdi/js": {
8 "version": "3.3.92",
9 "resolved": "https://registry.npmjs.org/@mdi/js/-/js-3.3.92.tgz",
10 "integrity": "sha512-l+12IwTycHlijWMiRWBAssm0RSgkQiwMthIy/EcBAdSqtnsHnFjHq+aI2MBZ8/AYX0QBxNUv4+EN0SXZgNkWDg=="
11 },
12 "@mdi/react": {
13 "version": "1.1.0",
14 "resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.1.0.tgz",
15 "integrity": "sha512-c0+avMYEZ6i7Pg1ULLFs+p7k8bDPiie9rrgGYs8VWQhw2tUUYz7r0lIPVzD3bzMghWfyhfkArj88K5Of0WTMNw=="
16 },
17 "@meetfranz/theme": {
18 "version": "file:../theme",
19 "requires": {
20 "color": "^3.1.0"
21 },
22 "dependencies": {
23 "color": {
24 "version": "3.1.0",
25 "bundled": true,
26 "requires": {
27 "color-convert": "^1.9.1",
28 "color-string": "^1.5.2"
29 }
30 },
31 "color-convert": {
32 "version": "1.9.3",
33 "bundled": true,
34 "requires": {
35 "color-name": "1.1.3"
36 }
37 },
38 "color-name": {
39 "version": "1.1.3",
40 "bundled": true
41 },
42 "color-string": {
43 "version": "1.5.3",
44 "bundled": true,
45 "requires": {
46 "color-name": "^1.0.0",
47 "simple-swizzle": "^0.2.2"
48 }
49 },
50 "is-arrayish": {
51 "version": "0.3.2",
52 "bundled": true
53 },
54 "simple-swizzle": {
55 "version": "0.2.2",
56 "bundled": true,
57 "requires": {
58 "is-arrayish": "^0.3.1"
59 }
60 }
61 }
62 },
63 "asap": {
64 "version": "2.0.6",
65 "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
66 "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
67 },
68 "core-js": {
69 "version": "1.2.7",
70 "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
71 "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
72 },
73 "create-react-class": {
74 "version": "15.6.3",
75 "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
76 "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
77 "requires": {
78 "fbjs": "^0.8.9",
79 "loose-envify": "^1.3.1",
80 "object-assign": "^4.1.1"
81 }
82 },
83 "encoding": {
84 "version": "0.1.12",
85 "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
86 "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
87 "requires": {
88 "iconv-lite": "~0.4.13"
89 }
90 },
91 "fbjs": {
92 "version": "0.8.17",
93 "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
94 "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
95 "requires": {
96 "core-js": "^1.0.0",
97 "isomorphic-fetch": "^2.1.1",
98 "loose-envify": "^1.0.0",
99 "object-assign": "^4.1.0",
100 "promise": "^7.1.1",
101 "setimmediate": "^1.0.5",
102 "ua-parser-js": "^0.7.18"
103 }
104 },
105 "iconv-lite": {
106 "version": "0.4.24",
107 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
108 "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
109 "requires": {
110 "safer-buffer": ">= 2.1.2 < 3"
111 }
112 },
113 "is-stream": {
114 "version": "1.1.0",
115 "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
116 "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
117 },
118 "isomorphic-fetch": {
119 "version": "2.2.1",
120 "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
121 "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
122 "requires": {
123 "node-fetch": "^1.0.1",
124 "whatwg-fetch": ">=0.10.0"
125 }
126 },
127 "js-tokens": {
128 "version": "4.0.0",
129 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
130 "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
131 },
132 "loose-envify": {
133 "version": "1.4.0",
134 "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
135 "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
136 "requires": {
137 "js-tokens": "^3.0.0 || ^4.0.0"
138 }
139 },
140 "node-fetch": {
141 "version": "1.7.3",
142 "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
143 "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
144 "requires": {
145 "encoding": "^0.1.11",
146 "is-stream": "^1.0.1"
147 }
148 },
149 "object-assign": {
150 "version": "4.1.1",
151 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
152 "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
153 },
154 "promise": {
155 "version": "7.3.1",
156 "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
157 "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
158 "requires": {
159 "asap": "~2.0.3"
160 }
161 },
162 "prop-types": {
163 "version": "15.6.2",
164 "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
165 "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
166 "requires": {
167 "loose-envify": "^1.3.1",
168 "object-assign": "^4.1.1"
169 }
170 },
171 "react-loader": {
172 "version": "2.4.5",
173 "resolved": "https://registry.npmjs.org/react-loader/-/react-loader-2.4.5.tgz",
174 "integrity": "sha1-zT5VHGzQc4wcDxPwc2VPk4KL5ak=",
175 "requires": {
176 "create-react-class": "^15.5.2",
177 "prop-types": "^15.5.8",
178 "spin.js": "2.x"
179 }
180 },
181 "safer-buffer": {
182 "version": "2.1.2",
183 "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
184 "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
185 },
186 "setimmediate": {
187 "version": "1.0.5",
188 "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
189 "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
190 },
191 "spin.js": {
192 "version": "2.3.2",
193 "resolved": "https://registry.npmjs.org/spin.js/-/spin.js-2.3.2.tgz",
194 "integrity": "sha1-bKpW1SBnNFD9XPvGlx5tB3LDeho="
195 },
196 "ua-parser-js": {
197 "version": "0.7.19",
198 "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz",
199 "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ=="
200 },
201 "whatwg-fetch": {
202 "version": "3.0.0",
203 "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
204 "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
205 }
206 }
207}
diff --git a/packages/ui/package.json b/packages/ui/package.json
new file mode 100644
index 000000000..cd7252850
--- /dev/null
+++ b/packages/ui/package.json
@@ -0,0 +1,41 @@
1{
2 "name": "@meetfranz/ui",
3 "version": "0.0.0",
4 "description": "React UI components for Franz",
5 "main": "lib/index.js",
6 "scripts": {
7 "dev": "NODE_ENV=development ../../node_modules/.bin/webpack -w",
8 "prepare": "../../node_modules/.bin/webpack"
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": "Stefan Malzner <stefan@adlk.io>",
24 "license": "Apache-2.0",
25 "dependencies": {
26 "@mdi/js": "^3.3.92",
27 "@mdi/react": "^1.1.0",
28 "@meetfranz/theme": "file:../theme",
29 "react-loader": "^2.4.5"
30 },
31 "peerDependencies": {
32 "classnames": "^2.2.6",
33 "lodash": "^4.17.11",
34 "mobx": "^5.8.0",
35 "mobx-react": "^5.4.3",
36 "react": "^16.7.0",
37 "react-dom": "16.7.0",
38 "react-jss": "^8.6.1"
39 },
40 "gitHead": "e31248830eb63c8bff3d9add3baa4ca8916b74e1"
41}
diff --git a/packages/ui/src/badge/index.tsx b/packages/ui/src/badge/index.tsx
new file mode 100644
index 000000000..241e778e7
--- /dev/null
+++ b/packages/ui/src/badge/index.tsx
@@ -0,0 +1,75 @@
1import { Theme } from '@meetfranz/theme';
2import classnames from 'classnames';
3import React, { Component } from 'react';
4import injectStyle from 'react-jss';
5
6import { IWithStyle } from '../typings/generic';
7
8interface IProps extends IWithStyle {
9 type: string;
10 className?: string;
11 children: React.ReactNode;
12}
13
14const badgeStyles = (theme: Theme) => {
15 const styles = {};
16 Object.keys(theme.styleTypes).map((style) => {
17 Object.assign(styles, {
18 [style]: {
19 background: theme.styleTypes[style].accent,
20 color: theme.styleTypes[style].contrast,
21 border: theme.styleTypes[style].border,
22 },
23 });
24 });
25
26 return styles;
27};
28
29const styles = (theme: Theme) => ({
30 badge: {
31 display: 'inline-block',
32 padding: [3, 8, 4],
33 fontSize: theme.badgeFontSize,
34 borderRadius: theme.badgeBorderRadius,
35 margin: [0, 4],
36
37 '&:first-child': {
38 marginLeft: 0,
39 },
40
41 '&:last-child': {
42 marginRight: 0,
43 },
44 },
45 ...badgeStyles(theme),
46});
47
48class BadgeComponent extends Component<IProps> {
49 public static defaultProps = {
50 type: 'primary',
51 };
52
53 render() {
54 const {
55 classes,
56 children,
57 type,
58 className,
59 } = this.props;
60
61 return (
62 <div
63 className={classnames({
64 [classes.badge]: true,
65 [classes[type]]: true,
66 [`${className}`]: className,
67 })}
68 >
69 {children}
70 </div>
71 );
72 }
73}
74
75export const Badge = injectStyle(styles)(BadgeComponent);
diff --git a/packages/ui/src/headline/index.tsx b/packages/ui/src/headline/index.tsx
new file mode 100644
index 000000000..3458a40ad
--- /dev/null
+++ b/packages/ui/src/headline/index.tsx
@@ -0,0 +1,71 @@
1import { Theme } from '@meetfranz/theme';
2import classnames from 'classnames';
3import React, { Component } from 'react';
4import injectStyle from 'react-jss';
5
6import { uiFontSize } from '@meetfranz/theme/lib/themes/default';
7import { IWithStyle, Omit } from '../typings/generic';
8
9interface IProps extends IWithStyle {
10 level?: number;
11 className?: string;
12 children: string | React.ReactNode;
13 id?: string;
14}
15
16const styles = (theme: Theme) => ({
17 headline: {
18 fontWeight: 'lighter',
19 color: theme.colorText,
20 marginTop: 0,
21 marginBottom: 10,
22 textAlign: 'left',
23 },
24 h1: {
25 fontSize: 30,
26 marginTop: 0,
27 },
28 h2: {
29 fontSize: 20,
30 },
31 h3: {
32 fontSize: 18,
33 },
34 h4: {
35 fontSize: theme.uiFontSize,
36 },
37});
38
39class HeadlineComponent extends Component<IProps> {
40 render() {
41 const {
42 classes,
43 level,
44 className,
45 children,
46 id,
47 } = this.props;
48
49 return React.createElement(
50 `h${level}`,
51 {
52 id,
53 className: classnames({
54 [classes.headline]: true,
55 [classes[level ? `h${level}` : 'h1']]: true,
56 [`${className}`]: className,
57 }),
58 },
59 children,
60 );
61 }
62}
63
64const Headline = injectStyle(styles)(HeadlineComponent);
65
66const createH = (level: number) => (props: Omit<IProps, 'classes' | 'theme'>) => <Headline level={level} {...props}>{props.children}</Headline>;
67
68export const H1 = createH(1);
69export const H2 = createH(2);
70export const H3 = createH(3);
71export const H4 = createH(4);
diff --git a/packages/ui/src/icon/index.tsx b/packages/ui/src/icon/index.tsx
new file mode 100644
index 000000000..e30d3396d
--- /dev/null
+++ b/packages/ui/src/icon/index.tsx
@@ -0,0 +1,55 @@
1import * as mdiIcons from '@mdi/js';
2import MdiIcon from '@mdi/react';
3import { Theme } from '@meetfranz/theme';
4import classnames from 'classnames';
5import React, { Component } from 'react';
6import injectStyle from 'react-jss';
7
8import { IWithStyle } from '../typings/generic';
9
10interface IProps extends IWithStyle {
11 icon: keyof typeof mdiIcons;
12 size?: number;
13 className?: string;
14}
15
16const styles = (theme: Theme) => ({
17 icon: {
18 fill: theme.colorText,
19 },
20});
21
22class IconComponent extends Component<IProps> {
23 public static defaultProps = {
24 size: 1,
25 };
26
27 render() {
28 const {
29 classes,
30 icon: iconName,
31 size,
32 className,
33 } = this.props;
34
35 let icon = '';
36 if (iconName && mdiIcons[iconName]) {
37 icon = mdiIcons[iconName];
38 } else if (iconName && !mdiIcons[iconName]) {
39 console.warn(`Icon '${iconName}' was not found`);
40 }
41
42 return (
43 <MdiIcon
44 path={icon}
45 size={size}
46 className={classnames({
47 [classes.icon]: true,
48 [`${className}`]: className,
49 })}
50 />
51 );
52 }
53}
54
55export const Icon = injectStyle(styles)(IconComponent);
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
new file mode 100644
index 000000000..1eeec5144
--- /dev/null
+++ b/packages/ui/src/index.ts
@@ -0,0 +1,5 @@
1export { Icon } from './icon';
2export { Infobox } from './infobox';
3export * from './headline';
4export { Loader } from './loader';
5export { Badge } from './badge';
diff --git a/packages/ui/src/infobox/index.tsx b/packages/ui/src/infobox/index.tsx
new file mode 100644
index 000000000..bf985ea9c
--- /dev/null
+++ b/packages/ui/src/infobox/index.tsx
@@ -0,0 +1,194 @@
1import { Theme } from '@meetfranz/theme';
2import classnames from 'classnames';
3import { observer } from 'mobx-react';
4import React, { Component } from 'react';
5import injectStyle from 'react-jss';
6// import Loader from 'react-loader';
7
8import { Icon } from '../';
9import { IWithStyle } from '../typings/generic';
10
11interface IProps extends IWithStyle {
12 icon?: string;
13 type?: string;
14 dismissable?: boolean;
15 onDismiss?: () => void;
16 ctaOnClick?: () => void;
17 ctaLabel?: string;
18 ctaLoading?: boolean;
19 children: React.ReactNode;
20}
21
22interface IState {
23 isDismissing: boolean;
24 dismissed: boolean;
25}
26
27const buttonStyles = (theme: Theme) => {
28 const styles = {};
29 Object.keys(theme.styleTypes).map((style) => {
30 Object.assign(styles, {
31 [style]: {
32 background: theme.styleTypes[style].accent,
33 color: theme.styleTypes[style].contrast,
34 border: theme.styleTypes[style].border,
35
36 '& svg': {
37 fill: theme.styleTypes[style].contrast,
38 },
39 },
40 });
41 });
42
43 return styles;
44};
45
46const styles = (theme: Theme) => ({
47 wrapper: {
48 position: 'relative',
49 overflow: 'hidden',
50 },
51 infobox: {
52 alignItems: 'center',
53 borderRadius: theme.borderRadiusSmall,
54 display: 'flex',
55 height: 'auto',
56 marginBottom: 30,
57 padding: '15px 20px',
58 top: 0,
59 transition: 'all 0.5s',
60 opacity: 1,
61 },
62 dismissing: {
63 // position: 'absolute',
64 marginTop: -100,
65 opacity: 0,
66 },
67 content: {
68 flex: 1,
69 },
70 icon: {
71 marginRight: 10,
72 },
73 close: {
74 color: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast,
75 marginRight: -5,
76 border: 0,
77 background: 'none',
78 },
79 cta: {
80 borderColor: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast,
81 borderRadius: theme.borderRadiusSmall,
82 borderStyle: 'solid',
83 borderWidth: 1,
84 background: 'none',
85 color: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast,
86 marginLeft: 15,
87 padding: [4, 10],
88 fontSize: theme.uiFontSize,
89 transition: 'opacity 0.3s',
90
91 '&:hover': {
92 opacity: 0.6,
93 },
94 },
95 ...buttonStyles(theme),
96});
97
98@observer
99class InfoboxComponent extends Component<IProps, IState> {
100 public static defaultProps = {
101 type: 'primary',
102 dismissable: false,
103 ctaOnClick: () => {},
104 onDismiss: () => {},
105 ctaLabel: '',
106 ctaLoading: false,
107 };
108
109 state = {
110 isDismissing: false,
111 dismissed: false,
112 };
113
114 dismiss() {
115 const {
116 onDismiss,
117 } = this.props;
118
119 this.setState({
120 isDismissing: true,
121 });
122
123 if (onDismiss) {
124 onDismiss();
125 }
126
127 setTimeout(() => {
128 this.setState({
129 dismissed: true,
130 });
131 }, 3000);
132 }
133
134 render() {
135 const {
136 classes,
137 children,
138 icon,
139 type,
140 ctaLabel,
141 ctaLoading,
142 ctaOnClick,
143 dismissable,
144 } = this.props;
145
146 const {
147 isDismissing,
148 dismissed,
149 } = this.state;
150
151 if (dismissed) {
152 return null;
153 }
154
155 return (
156 <div className={classes.wrapper}>
157 <div
158 className={classnames({
159 [classes.infobox]: true,
160 [classes[`${type}`]]: type,
161 [classes.dismissing]: isDismissing,
162 })}
163 >
164 {icon && (
165 <Icon icon={icon} className={classes.icon} />
166 )}
167 <div className={classes.content}>
168 {children}
169 </div>
170 {ctaLabel && (
171 <button
172 className={classes.cta}
173 onClick={ctaOnClick}
174 type="button"
175 >
176 {ctaLabel}
177 </button>
178 )}
179 {dismissable && (
180 <button
181 type="button"
182 onClick={this.dismiss.bind(this)}
183 className={classes.close}
184 >
185 <Icon icon="mdiClose" />
186 </button>
187 )}
188 </div>
189 </div>
190 );
191 }
192}
193
194export const Infobox = injectStyle(styles)(InfoboxComponent);
diff --git a/packages/ui/src/loader/index.tsx b/packages/ui/src/loader/index.tsx
new file mode 100644
index 000000000..799caf195
--- /dev/null
+++ b/packages/ui/src/loader/index.tsx
@@ -0,0 +1,45 @@
1import { Theme } from '@meetfranz/theme';
2import classnames from 'classnames';
3import React, { Component } from 'react';
4import injectStyle from 'react-jss';
5import ReactLoader from 'react-loader';
6
7import { IWithStyle } from '../typings/generic';
8
9interface IProps extends IWithStyle {
10 className?: string;
11}
12
13const styles = (theme: Theme) => ({
14 container: {
15 position: 'relative',
16 height: 60,
17 },
18});
19
20class LoaderComponent extends Component<IProps> {
21 render() {
22 const {
23 classes,
24 className,
25 theme,
26 } = this.props;
27
28 return (
29 <div className={classnames({
30 [classes.container]: true,
31 [`${className}`]: className,
32 })}>
33 <ReactLoader
34 loaded={false}
35 width={4}
36 scale={0.75}
37 color={theme.colorText}
38 parentClassName={classes.loader}
39 />
40 </div>
41 );
42 }
43}
44
45export const Loader = injectStyle(styles)(LoaderComponent);
diff --git a/packages/ui/src/typings/generic.ts b/packages/ui/src/typings/generic.ts
new file mode 100644
index 000000000..d5f953b9f
--- /dev/null
+++ b/packages/ui/src/typings/generic.ts
@@ -0,0 +1,10 @@
1import { Theme } from '@meetfranz/theme/lib';
2import { Classes } from 'jss';
3
4export interface IWithStyle {
5 classes: Classes;
6 theme: Theme;
7}
8
9export type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;
10export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
new file mode 100644
index 000000000..8b9507eac
--- /dev/null
+++ b/packages/ui/tsconfig.json
@@ -0,0 +1,12 @@
1{
2 "extends": "../../tsconfig.settings.json",
3 "compilerOptions": {
4 "outDir": "lib",
5 "rootDir": "src"
6 },
7 "references": [
8 {
9 "path": "../theme"
10 }
11 ]
12}
diff --git a/packages/ui/tslint.json b/packages/ui/tslint.json
new file mode 100644
index 000000000..0946f2096
--- /dev/null
+++ b/packages/ui/tslint.json
@@ -0,0 +1,3 @@
1{
2 "extends": "../../tslint.json"
3}
diff --git a/packages/ui/webpack.config.js b/packages/ui/webpack.config.js
new file mode 100644
index 000000000..cc3370359
--- /dev/null
+++ b/packages/ui/webpack.config.js
@@ -0,0 +1,19 @@
1const path = require('path');
2const baseConfig = require('../../webpack.config.base')(__dirname);
3
4module.exports = Object.assign({}, baseConfig, {
5 output: {
6 filename: 'index.js',
7 path: path.join(__dirname, 'lib'),
8 libraryTarget: 'commonjs2',
9 },
10 externals: {
11 react: 'react',
12 reactDom: 'react-dom',
13 classnames: 'classnames',
14 lodash: 'lodash',
15 mobx: 'mobx',
16 mobxReact: 'mobx-react',
17 reactJss: 'react-jss',
18 },
19});
diff --git a/uidev/src/app.tsx b/uidev/src/app.tsx
index 0fd524e34..870911c2f 100644
--- a/uidev/src/app.tsx
+++ b/uidev/src/app.tsx
@@ -7,8 +7,14 @@ import injectSheet from 'react-jss';
7 7
8import { WithTheme } from './withTheme'; 8import { WithTheme } from './withTheme';
9 9
10import './stories/badge.stories';
10import './stories/button.stories'; 11import './stories/button.stories';
12import './stories/headline.stories';
13import './stories/icon.stories';
14import './stories/infobox.stories';
11import './stories/input.stories'; 15import './stories/input.stories';
16import './stories/loader.stories';
17import './stories/select.stories';
12import './stories/toggle.stories'; 18import './stories/toggle.stories';
13 19
14import { store } from './stores'; 20import { store } from './stores';
@@ -31,6 +37,8 @@ const styles = {
31 position: 'fixed' as CSS.PositionProperty, 37 position: 'fixed' as CSS.PositionProperty,
32 listStyleType: 'none', 38 listStyleType: 'none',
33 fontSize: 14, 39 fontSize: 14,
40 overflow: 'scroll',
41 height: '100%',
34 }, 42 },
35 storyList: { 43 storyList: {
36 paddingLeft: 18, 44 paddingLeft: 18,
diff --git a/uidev/src/stories/badge.stories.tsx b/uidev/src/stories/badge.stories.tsx
new file mode 100644
index 000000000..6de2034bf
--- /dev/null
+++ b/uidev/src/stories/badge.stories.tsx
@@ -0,0 +1,21 @@
1import React from 'react';
2
3import { Badge } from '@meetfranz/ui';
4import { storiesOf } from '../stores/stories';
5
6storiesOf('Badge')
7 .add('Basic', () => (
8 <>
9 <Badge>New</Badge>
10 </>
11 ))
12 .add('Styles', () => (
13 <>
14 <Badge type="primary">Primary</Badge>
15 <Badge type="secondary">secondary</Badge>
16 <Badge type="success">success</Badge>
17 <Badge type="warning">warning</Badge>
18 <Badge type="danger">danger</Badge>
19 <Badge type="inverted">inverted</Badge>
20 </>
21 ));
diff --git a/uidev/src/stories/headline.stories.tsx b/uidev/src/stories/headline.stories.tsx
new file mode 100644
index 000000000..f42771cae
--- /dev/null
+++ b/uidev/src/stories/headline.stories.tsx
@@ -0,0 +1,54 @@
1import { observable } from 'mobx';
2import { observer } from 'mobx-react';
3import React from 'react';
4import uuid from 'uuid/v4';
5
6import { H1, H2, H3, H4 } from '@meetfranz/ui';
7import { storiesOf } from '../stores/stories';
8
9// interface IStoreArgs {
10// value?: boolean;
11// checked?: boolean;
12// label?: string;
13// id?: string;
14// name?: string;
15// disabled?: boolean;
16// error?: string;
17// }
18
19// const createStore = (args?: IStoreArgs) => {
20// return observable(Object.assign({
21// id: `element-${uuid()}`,
22// name: 'toggle',
23// label: 'Label',
24// value: true,
25// checked: false,
26// disabled: false,
27// error: '',
28// }, args));
29// };
30
31// const WithStoreToggle = observer(({ store }: { store: any }) => (
32// <>
33// <Toggle
34// value={store.value}
35// checked={store.checked}
36// label={store.label}
37// id={store.id}
38// name={store.name}
39// disabled={store.disabled}
40// error={store.error}
41// onChange={() => store.checked = !store.checked}
42// />
43// </>
44// ));
45
46storiesOf('Typo')
47 .add('Headlines', () => (
48 <>
49 <H1>Welcome to the world of tomorrow</H1>
50 <H2>Welcome to the world of tomorrow</H2>
51 <H3>Welcome to the world of tomorrow</H3>
52 <H4>Welcome to the world of tomorrow</H4>
53 </>
54 ));
diff --git a/uidev/src/stories/icon.stories.tsx b/uidev/src/stories/icon.stories.tsx
new file mode 100644
index 000000000..c8e7f8ced
--- /dev/null
+++ b/uidev/src/stories/icon.stories.tsx
@@ -0,0 +1,53 @@
1import { observable } from 'mobx';
2import { observer } from 'mobx-react';
3import React from 'react';
4import uuid from 'uuid/v4';
5
6import { Icon } from '@meetfranz/ui';
7import { storiesOf } from '../stores/stories';
8
9// interface IStoreArgs {
10// value?: boolean;
11// checked?: boolean;
12// label?: string;
13// id?: string;
14// name?: string;
15// disabled?: boolean;
16// error?: string;
17// }
18
19// const createStore = (args?: IStoreArgs) => {
20// return observable(Object.assign({
21// id: `element-${uuid()}`,
22// name: 'toggle',
23// label: 'Label',
24// value: true,
25// checked: false,
26// disabled: false,
27// error: '',
28// }, args));
29// };
30
31// const WithStoreToggle = observer(({ store }: { store: any }) => (
32// <>
33// <Toggle
34// value={store.value}
35// checked={store.checked}
36// label={store.label}
37// id={store.id}
38// name={store.name}
39// disabled={store.disabled}
40// error={store.error}
41// onChange={() => store.checked = !store.checked}
42// />
43// </>
44// ));
45
46storiesOf('Icon')
47 .add('Basic', () => (
48 <>
49 <Icon icon="mdiAccountCircle" />
50 <Icon icon="mdiAccountCircle" size={2} />
51 <Icon icon="mdiAccountCircle" size={3} />
52 </>
53 ));
diff --git a/uidev/src/stories/infobox.stories.tsx b/uidev/src/stories/infobox.stories.tsx
new file mode 100644
index 000000000..2a5e8b0d5
--- /dev/null
+++ b/uidev/src/stories/infobox.stories.tsx
@@ -0,0 +1,126 @@
1import { observable } from 'mobx';
2import { observer } from 'mobx-react';
3import React from 'react';
4
5import { Infobox } from '@meetfranz/ui';
6import { storiesOf } from '../stores/stories';
7
8interface IStoreArgs {
9 icon?: string;
10 ctaLabel?: string;
11 type?: string;
12 dismissable?: boolean;
13}
14
15const createStore = (args?: IStoreArgs) => {
16 return observable(Object.assign({
17 type: 'primary',
18 ctaOnClick: () => {
19 alert('on click handler');
20 },
21 }, args));
22};
23
24const WithStoreInfobox = observer(({ store, children }: { store: any, children: string | React.ReactNode }) => (
25 <>
26 <Infobox
27 icon={store.icon}
28 ctaLabel={store.ctaLabel}
29 type={store.type}
30 ctaOnClick={store.ctaOnClick}
31 dismissable={store.dismissable}
32 >
33 {children}
34 </Infobox>
35 </>
36));
37
38storiesOf('Infobox')
39 .add('Basic', () => (
40 <WithStoreInfobox store={createStore()}>Welcome to the world of tomorrow</WithStoreInfobox>
41 ))
42 .add('Icon + Dismissable', () => (
43 <WithStoreInfobox
44 store={createStore({
45 icon: 'mdiEarth',
46 dismissable: true,
47 })}
48 >
49 Welcome to the world of tomorrow
50 </WithStoreInfobox>
51 ))
52 .add('With CTA', () => (
53 <WithStoreInfobox
54 store={createStore({
55 icon: 'mdiEarth',
56 ctaLabel: 'Ok, hi!',
57 })}
58 >
59 Welcome to the world of tomorrow
60 </WithStoreInfobox>
61 ))
62 .add('With long text', () => (
63 <WithStoreInfobox
64 store={createStore({
65 icon: 'mdiEarth',
66 ctaLabel: 'Ok, hi!',
67 })}
68 >
69 Franz is your messaging app / former Emperor of Austria and combines chat & messaging services into one application. Franz currently supports Slack, WhatsApp, WeChat, HipChat, Facebook Messenger, Telegram, Google Hangouts,GroupMe, Skype and many more.
70 </WithStoreInfobox>
71 ))
72 .add('Secondary', () => (
73 <WithStoreInfobox
74 store={createStore({
75 icon: 'mdiEarth',
76 ctaLabel: 'Ok, hi!',
77 type: 'secondary',
78 })}
79 >
80 Welcome to the world of tomorrow
81 </WithStoreInfobox>
82 ))
83 .add('Success', () => (
84 <WithStoreInfobox
85 store={createStore({
86 icon: 'mdiEarth',
87 ctaLabel: 'Ok, hi!',
88 type: 'success',
89 })}
90 >
91 Welcome to the world of tomorrow
92 </WithStoreInfobox>
93 ))
94 .add('Warning', () => (
95 <WithStoreInfobox
96 store={createStore({
97 icon: 'mdiEarth',
98 ctaLabel: 'Ok, hi!',
99 type: 'warning',
100 })}
101 >
102 Welcome to the world of tomorrow
103 </WithStoreInfobox>
104 ))
105 .add('Danger', () => (
106 <WithStoreInfobox
107 store={createStore({
108 icon: 'mdiEarth',
109 ctaLabel: 'Ok, hi!',
110 type: 'danger',
111 })}
112 >
113 Welcome to the world of tomorrow
114 </WithStoreInfobox>
115 ))
116 .add('Inverted', () => (
117 <WithStoreInfobox
118 store={createStore({
119 icon: 'mdiEarth',
120 ctaLabel: 'Ok, hi!',
121 type: 'inverted',
122 })}
123 >
124 Welcome to the world of tomorrow
125 </WithStoreInfobox>
126 ));
diff --git a/uidev/src/stories/loader.stories.tsx b/uidev/src/stories/loader.stories.tsx
new file mode 100644
index 000000000..84e813c04
--- /dev/null
+++ b/uidev/src/stories/loader.stories.tsx
@@ -0,0 +1,14 @@
1import { observable } from 'mobx';
2import { observer } from 'mobx-react';
3import React from 'react';
4import uuid from 'uuid/v4';
5
6import { Loader } from '@meetfranz/ui';
7import { storiesOf } from '../stores/stories';
8
9storiesOf('Loader')
10 .add('Basic', () => (
11 <>
12 <Loader />
13 </>
14 ));
diff --git a/uidev/src/stories/select.stories.tsx b/uidev/src/stories/select.stories.tsx
new file mode 100644
index 000000000..81f7f08a6
--- /dev/null
+++ b/uidev/src/stories/select.stories.tsx
@@ -0,0 +1,320 @@
1import React from 'react';
2import uuid from 'uuid/v4';
3
4import { Select } from '@meetfranz/forms';
5import { storiesOf } from '../stores/stories';
6
7const defaultProps = () => {
8 const id = uuid();
9 return {
10 label: 'Label',
11 id: `test-${id}`,
12 name: `test-${id}`,
13 options: {
14 AF: 'Afghanistan',
15 AL: 'Albania',
16 DZ: 'Algeria',
17 AS: 'American Samoa',
18 AD: 'Andorra',
19 AO: 'Angola',
20 AI: 'Anguilla',
21 AQ: 'Antarctica',
22 AG: 'Antigua and Barbuda',
23 AR: 'Argentina',
24 AM: 'Armenia',
25 AW: 'Aruba',
26 AC: 'Ascension Island',
27 AU: 'Australia',
28 AT: 'Austria',
29 AZ: 'Azerbaijan',
30 BS: 'Bahamas',
31 BH: 'Bahrain',
32 BD: 'Bangladesh',
33 BB: 'Barbados',
34 BY: 'Belarus',
35 BE: 'Belgium',
36 BZ: 'Belize',
37 BJ: 'Benin',
38 BM: 'Bermuda',
39 BT: 'Bhutan',
40 BO: 'Bolivia',
41 BA: 'Bosnia and Herzegovina',
42 BW: 'Botswana',
43 BV: 'Bouvet Island',
44 BR: 'Brazil',
45 BQ: 'British Antarctic Territory',
46 IO: 'British Indian Ocean Territory',
47 VG: 'British Virgin Islands',
48 BN: 'Brunei',
49 BG: 'Bulgaria',
50 BF: 'Burkina Faso',
51 BI: 'Burundi',
52 KH: 'Cambodia',
53 CM: 'Cameroon',
54 CA: 'Canada',
55 IC: 'Canary Islands',
56 CT: 'Canton and Enderbury Islands',
57 CV: 'Cape Verde',
58 KY: 'Cayman Islands',
59 CF: 'Central African Republic',
60 EA: 'Ceuta and Melilla',
61 TD: 'Chad',
62 CL: 'Chile',
63 CN: 'China',
64 CX: 'Christmas Island',
65 CP: 'Clipperton Island',
66 CC: 'Cocos [Keeling] Islands',
67 CO: 'Colombia',
68 KM: 'Comoros',
69 CD: 'Congo - Kinshasa',
70 CG: 'Congo [Republic]',
71 CK: 'Cook Islands',
72 CR: 'Costa Rica',
73 HR: 'Croatia',
74 CU: 'Cuba',
75 CY: 'Cyprus',
76 CZ: 'Czech Republic',
77 CI: 'Côte d’Ivoire',
78 DK: 'Denmark',
79 DG: 'Diego Garcia',
80 DJ: 'Djibouti',
81 DM: 'Dominica',
82 DO: 'Dominican Republic',
83 NQ: 'Dronning Maud Land',
84 TL: 'East Timor',
85 EC: 'Ecuador',
86 EG: 'Egypt',
87 SV: 'El Salvador',
88 GQ: 'Equatorial Guinea',
89 ER: 'Eritrea',
90 EE: 'Estonia',
91 ET: 'Ethiopia',
92 FK: 'Falkland Islands',
93 FO: 'Faroe Islands',
94 FJ: 'Fiji',
95 FI: 'Finland',
96 FR: 'France',
97 GF: 'French Guiana',
98 PF: 'French Polynesia',
99 TF: 'French Southern Territories',
100 FQ: 'French Southern and Antarctic Territories',
101 GA: 'Gabon',
102 GM: 'Gambia',
103 GE: 'Georgia',
104 DE: 'Germany',
105 GH: 'Ghana',
106 GI: 'Gibraltar',
107 GR: 'Greece',
108 GL: 'Greenland',
109 GD: 'Grenada',
110 GP: 'Guadeloupe',
111 GU: 'Guam',
112 GT: 'Guatemala',
113 GG: 'Guernsey',
114 GN: 'Guinea',
115 GW: 'Guinea-Bissau',
116 GY: 'Guyana',
117 HT: 'Haiti',
118 HM: 'Heard Island and McDonald Islands',
119 HN: 'Honduras',
120 HK: 'Hong Kong',
121 HU: 'Hungary',
122 IS: 'Iceland',
123 IN: 'India',
124 ID: 'Indonesia',
125 IR: 'Iran',
126 IQ: 'Iraq',
127 IE: 'Ireland',
128 IM: 'Isle of Man',
129 IL: 'Israel',
130 IT: 'Italy',
131 JM: 'Jamaica',
132 JP: 'Japan',
133 JE: 'Jersey',
134 JT: 'Johnston Island',
135 JO: 'Jordan',
136 KZ: 'Kazakhstan',
137 KE: 'Kenya',
138 KI: 'Kiribati',
139 XK: 'Kosovo',
140 KW: 'Kuwait',
141 KG: 'Kyrgyzstan',
142 LA: 'Laos',
143 LV: 'Latvia',
144 LB: 'Lebanon',
145 LS: 'Lesotho',
146 LR: 'Liberia',
147 LY: 'Libya',
148 LI: 'Liechtenstein',
149 LT: 'Lithuania',
150 LU: 'Luxembourg',
151 MO: 'Macau',
152 MK: 'Macedonia',
153 MG: 'Madagascar',
154 MW: 'Malawi',
155 MY: 'Malaysia',
156 MV: 'Maldives',
157 ML: 'Mali',
158 MT: 'Malta',
159 MH: 'Marshall Islands',
160 MQ: 'Martinique',
161 MR: 'Mauritania',
162 MU: 'Mauritius',
163 YT: 'Mayotte',
164 FX: 'Metropolitan France',
165 MX: 'Mexico',
166 FM: 'Micronesia',
167 MI: 'Midway Islands',
168 MD: 'Moldova',
169 MC: 'Monaco',
170 MN: 'Mongolia',
171 ME: 'Montenegro',
172 MS: 'Montserrat',
173 MA: 'Morocco',
174 MZ: 'Mozambique',
175 MM: 'Myanmar [Burma]',
176 NA: 'Namibia',
177 NR: 'Nauru',
178 NP: 'Nepal',
179 NL: 'Netherlands',
180 AN: 'Netherlands Antilles',
181 NC: 'New Caledonia',
182 NZ: 'New Zealand',
183 NI: 'Nicaragua',
184 NE: 'Niger',
185 NG: 'Nigeria',
186 NU: 'Niue',
187 NF: 'Norfolk Island',
188 KP: 'North Korea',
189 VD: 'North Vietnam',
190 MP: 'Northern Mariana Islands',
191 NO: 'Norway',
192 OM: 'Oman',
193 QO: 'Outlying Oceania',
194 PC: 'Pacific Islands Trust Territory',
195 PK: 'Pakistan',
196 PW: 'Palau',
197 PS: 'Palestinian Territories',
198 PA: 'Panama',
199 PZ: 'Panama Canal Zone',
200 PG: 'Papua New Guinea',
201 PY: 'Paraguay',
202 YD: 'Peoples Democratic Republic of Yemen',
203 PE: 'Peru',
204 PH: 'Philippines',
205 PN: 'Pitcairn Islands',
206 PL: 'Poland',
207 PT: 'Portugal',
208 PR: 'Puerto Rico',
209 QA: 'Qatar',
210 RO: 'Romania',
211 RU: 'Russia',
212 RW: 'Rwanda',
213 RE: 'Réunion',
214 BL: 'Saint Barthélemy',
215 SH: 'Saint Helena',
216 KN: 'Saint Kitts and Nevis',
217 LC: 'Saint Lucia',
218 MF: 'Saint Martin',
219 PM: 'Saint Pierre and Miquelon',
220 VC: 'Saint Vincent and the Grenadines',
221 WS: 'Samoa',
222 SM: 'San Marino',
223 SA: 'Saudi Arabia',
224 SN: 'Senegal',
225 RS: 'Serbia',
226 CS: 'Serbia and Montenegro',
227 SC: 'Seychelles',
228 SL: 'Sierra Leone',
229 SG: 'Singapore',
230 SK: 'Slovakia',
231 SI: 'Slovenia',
232 SB: 'Solomon Islands',
233 SO: 'Somalia',
234 ZA: 'South Africa',
235 GS: 'South Georgia and the South Sandwich Islands',
236 KR: 'South Korea',
237 ES: 'Spain',
238 LK: 'Sri Lanka',
239 SD: 'Sudan',
240 SR: 'Suriname',
241 SJ: 'Svalbard and Jan Mayen',
242 SZ: 'Swaziland',
243 SE: 'Sweden',
244 CH: 'Switzerland',
245 SY: 'Syria',
246 ST: 'São Tomé and Príncipe',
247 TW: 'Taiwan',
248 TJ: 'Tajikistan',
249 TZ: 'Tanzania',
250 TH: 'Thailand',
251 TG: 'Togo',
252 TK: 'Tokelau',
253 TO: 'Tonga',
254 TT: 'Trinidad and Tobago',
255 TA: 'Tristan da Cunha',
256 TN: 'Tunisia',
257 TR: 'Turkey',
258 TM: 'Turkmenistan',
259 TC: 'Turks and Caicos Islands',
260 TV: 'Tuvalu',
261 UM: 'U.S. Minor Outlying Islands',
262 PU: 'U.S. Miscellaneous Pacific Islands',
263 VI: 'U.S. Virgin Islands',
264 UG: 'Uganda',
265 UA: 'Ukraine',
266 AE: 'United Arab Emirates',
267 GB: 'United Kingdom',
268 US: 'United States',
269 UY: 'Uruguay',
270 UZ: 'Uzbekistan',
271 VU: 'Vanuatu',
272 VA: 'Vatican City',
273 VE: 'Venezuela',
274 VN: 'Vietnam',
275 WK: 'Wake Island',
276 WF: 'Wallis and Futuna',
277 EH: 'Western Sahara',
278 YE: 'Yemen',
279 ZM: 'Zambia',
280 ZW: 'Zimbabwe',
281 AX: 'Åland Islands',
282 },
283 actionText: 'Select country',
284 // defaultValue: 'AT',
285 onChange: (e: React.ChangeEvent<HTMLInputElement>) => console.log('changed event', e),
286 };
287};
288
289storiesOf('Select')
290 .add('Basic', () => (
291 <Select
292 {...defaultProps()}
293 />
294 ))
295 .add('With preselection', () => (
296 <Select
297 {...defaultProps()}
298 defaultValue="AT"
299 />
300 ))
301 .add('With search', () => (
302 <Select
303 {...defaultProps()}
304 showSearch
305 />
306 ))
307 .add('Disabled', () => (
308 <Select
309 {...defaultProps()}
310 showSearch
311 disabled
312 />
313 ))
314 .add('With error', () => (
315 <Select
316 {...defaultProps()}
317 showSearch
318 error="Your selection was a bit too funky for my taste"
319 />
320 ));