diff options
Diffstat (limited to 'packages/ui/src')
-rw-r--r-- | packages/ui/src/badge/index.tsx | 76 | ||||
-rw-r--r-- | packages/ui/src/headline/index.tsx | 71 | ||||
-rw-r--r-- | packages/ui/src/icon/index.tsx | 55 | ||||
-rw-r--r-- | packages/ui/src/index.ts | 5 | ||||
-rw-r--r-- | packages/ui/src/infobox/index.tsx | 192 | ||||
-rw-r--r-- | packages/ui/src/loader/index.tsx | 48 | ||||
-rw-r--r-- | packages/ui/src/typings/generic.ts | 10 |
7 files changed, 457 insertions, 0 deletions
diff --git a/packages/ui/src/badge/index.tsx b/packages/ui/src/badge/index.tsx new file mode 100644 index 000000000..fc52ecc73 --- /dev/null +++ b/packages/ui/src/badge/index.tsx | |||
@@ -0,0 +1,76 @@ | |||
1 | import { Theme } from '@meetfranz/theme'; | ||
2 | import classnames from 'classnames'; | ||
3 | import React, { Component } from 'react'; | ||
4 | import injectStyle from 'react-jss'; | ||
5 | |||
6 | import { IWithStyle } from '../typings/generic'; | ||
7 | |||
8 | interface IProps extends IWithStyle { | ||
9 | type: string; | ||
10 | className?: string; | ||
11 | children: React.ReactNode; | ||
12 | } | ||
13 | |||
14 | const 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 | |||
29 | const 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 | |||
48 | class 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 | data-type="franz-badge" | ||
69 | > | ||
70 | {children} | ||
71 | </div> | ||
72 | ); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | export 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..7eabfcf80 --- /dev/null +++ b/packages/ui/src/headline/index.tsx | |||
@@ -0,0 +1,71 @@ | |||
1 | import { Theme } from '@meetfranz/theme'; | ||
2 | import classnames from 'classnames'; | ||
3 | import React, { Component } from 'react'; | ||
4 | import injectStyle from 'react-jss'; | ||
5 | |||
6 | import { IWithStyle, Omit } from '../typings/generic'; | ||
7 | |||
8 | interface IProps extends IWithStyle { | ||
9 | level?: number; | ||
10 | className?: string; | ||
11 | children: string | React.ReactNode; | ||
12 | id?: string; | ||
13 | } | ||
14 | |||
15 | const styles = (theme: Theme) => ({ | ||
16 | headline: { | ||
17 | fontWeight: 'lighter', | ||
18 | color: theme.colorText, | ||
19 | marginTop: 0, | ||
20 | marginBottom: 10, | ||
21 | textAlign: 'left', | ||
22 | }, | ||
23 | h1: { | ||
24 | fontSize: 30, | ||
25 | marginTop: 0, | ||
26 | }, | ||
27 | h2: { | ||
28 | fontSize: 20, | ||
29 | }, | ||
30 | h3: { | ||
31 | fontSize: 18, | ||
32 | }, | ||
33 | h4: { | ||
34 | fontSize: theme.uiFontSize, | ||
35 | }, | ||
36 | }); | ||
37 | |||
38 | class HeadlineComponent extends Component<IProps> { | ||
39 | render() { | ||
40 | const { | ||
41 | classes, | ||
42 | level, | ||
43 | className, | ||
44 | children, | ||
45 | id, | ||
46 | } = this.props; | ||
47 | |||
48 | return React.createElement( | ||
49 | `h${level}`, | ||
50 | { | ||
51 | id, | ||
52 | className: classnames({ | ||
53 | [classes.headline]: true, | ||
54 | [classes[level ? `h${level}` : 'h1']]: true, | ||
55 | [`${className}`]: className, | ||
56 | }), | ||
57 | 'data-type': 'franz-headline', | ||
58 | }, | ||
59 | children, | ||
60 | ); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | const Headline = injectStyle(styles)(HeadlineComponent); | ||
65 | |||
66 | const createH = (level: number) => (props: Omit<IProps, 'classes' | 'theme'>) => <Headline level={level} {...props}>{props.children}</Headline>; | ||
67 | |||
68 | export const H1 = createH(1); | ||
69 | export const H2 = createH(2); | ||
70 | export const H3 = createH(3); | ||
71 | export 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 @@ | |||
1 | import * as mdiIcons from '@mdi/js'; | ||
2 | import MdiIcon from '@mdi/react'; | ||
3 | import { Theme } from '@meetfranz/theme'; | ||
4 | import classnames from 'classnames'; | ||
5 | import React, { Component } from 'react'; | ||
6 | import injectStyle from 'react-jss'; | ||
7 | |||
8 | import { IWithStyle } from '../typings/generic'; | ||
9 | |||
10 | interface IProps extends IWithStyle { | ||
11 | icon: keyof typeof mdiIcons; | ||
12 | size?: number; | ||
13 | className?: string; | ||
14 | } | ||
15 | |||
16 | const styles = (theme: Theme) => ({ | ||
17 | icon: { | ||
18 | fill: theme.colorText, | ||
19 | }, | ||
20 | }); | ||
21 | |||
22 | class 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 | |||
55 | export 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 @@ | |||
1 | export { Icon } from './icon'; | ||
2 | export { Infobox } from './infobox'; | ||
3 | export * from './headline'; | ||
4 | export { Loader } from './loader'; | ||
5 | export { 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..53ed16341 --- /dev/null +++ b/packages/ui/src/infobox/index.tsx | |||
@@ -0,0 +1,192 @@ | |||
1 | import { Theme } from '@meetfranz/theme'; | ||
2 | import classnames from 'classnames'; | ||
3 | import React, { Component } from 'react'; | ||
4 | import injectStyle from 'react-jss'; | ||
5 | |||
6 | import { Icon } from '../'; | ||
7 | import { IWithStyle } from '../typings/generic'; | ||
8 | |||
9 | interface IProps extends IWithStyle { | ||
10 | icon?: string; | ||
11 | type?: string; | ||
12 | dismissable?: boolean; | ||
13 | onDismiss?: () => void; | ||
14 | ctaOnClick?: () => void; | ||
15 | ctaLabel?: string; | ||
16 | ctaLoading?: boolean; | ||
17 | children: React.ReactNode; | ||
18 | } | ||
19 | |||
20 | interface IState { | ||
21 | isDismissing: boolean; | ||
22 | dismissed: boolean; | ||
23 | } | ||
24 | |||
25 | const buttonStyles = (theme: Theme) => { | ||
26 | const styles = {}; | ||
27 | Object.keys(theme.styleTypes).map((style) => { | ||
28 | Object.assign(styles, { | ||
29 | [style]: { | ||
30 | background: theme.styleTypes[style].accent, | ||
31 | color: theme.styleTypes[style].contrast, | ||
32 | border: theme.styleTypes[style].border, | ||
33 | |||
34 | '& svg': { | ||
35 | fill: theme.styleTypes[style].contrast, | ||
36 | }, | ||
37 | }, | ||
38 | }); | ||
39 | }); | ||
40 | |||
41 | return styles; | ||
42 | }; | ||
43 | |||
44 | const styles = (theme: Theme) => ({ | ||
45 | wrapper: { | ||
46 | position: 'relative', | ||
47 | overflow: 'hidden', | ||
48 | }, | ||
49 | infobox: { | ||
50 | alignItems: 'center', | ||
51 | borderRadius: theme.borderRadiusSmall, | ||
52 | display: 'flex', | ||
53 | height: 'auto', | ||
54 | marginBottom: 30, | ||
55 | padding: '15px 20px', | ||
56 | top: 0, | ||
57 | transition: 'all 0.5s', | ||
58 | opacity: 1, | ||
59 | }, | ||
60 | dismissing: { | ||
61 | // position: 'absolute', | ||
62 | marginTop: -100, | ||
63 | opacity: 0, | ||
64 | }, | ||
65 | content: { | ||
66 | flex: 1, | ||
67 | }, | ||
68 | icon: { | ||
69 | marginRight: 10, | ||
70 | }, | ||
71 | close: { | ||
72 | color: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast, | ||
73 | marginRight: -5, | ||
74 | border: 0, | ||
75 | background: 'none', | ||
76 | }, | ||
77 | cta: { | ||
78 | borderColor: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast, | ||
79 | borderRadius: theme.borderRadiusSmall, | ||
80 | borderStyle: 'solid', | ||
81 | borderWidth: 1, | ||
82 | background: 'none', | ||
83 | color: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast, | ||
84 | marginLeft: 15, | ||
85 | padding: [4, 10], | ||
86 | fontSize: theme.uiFontSize, | ||
87 | transition: 'opacity 0.3s', | ||
88 | |||
89 | '&:hover': { | ||
90 | opacity: 0.6, | ||
91 | }, | ||
92 | }, | ||
93 | ...buttonStyles(theme), | ||
94 | }); | ||
95 | |||
96 | class InfoboxComponent extends Component<IProps, IState> { | ||
97 | public static defaultProps = { | ||
98 | type: 'primary', | ||
99 | dismissable: false, | ||
100 | ctaOnClick: () => {}, | ||
101 | onDismiss: () => {}, | ||
102 | ctaLabel: '', | ||
103 | ctaLoading: false, | ||
104 | }; | ||
105 | |||
106 | state = { | ||
107 | isDismissing: false, | ||
108 | dismissed: false, | ||
109 | }; | ||
110 | |||
111 | dismiss() { | ||
112 | const { | ||
113 | onDismiss, | ||
114 | } = this.props; | ||
115 | |||
116 | this.setState({ | ||
117 | isDismissing: true, | ||
118 | }); | ||
119 | |||
120 | if (onDismiss) { | ||
121 | onDismiss(); | ||
122 | } | ||
123 | |||
124 | setTimeout(() => { | ||
125 | this.setState({ | ||
126 | dismissed: true, | ||
127 | }); | ||
128 | }, 3000); | ||
129 | } | ||
130 | |||
131 | render() { | ||
132 | const { | ||
133 | classes, | ||
134 | children, | ||
135 | icon, | ||
136 | type, | ||
137 | ctaLabel, | ||
138 | ctaLoading, | ||
139 | ctaOnClick, | ||
140 | dismissable, | ||
141 | } = this.props; | ||
142 | |||
143 | const { | ||
144 | isDismissing, | ||
145 | dismissed, | ||
146 | } = this.state; | ||
147 | |||
148 | if (dismissed) { | ||
149 | return null; | ||
150 | } | ||
151 | |||
152 | return ( | ||
153 | <div className={classes.wrapper}> | ||
154 | <div | ||
155 | className={classnames({ | ||
156 | [classes.infobox]: true, | ||
157 | [classes[`${type}`]]: type, | ||
158 | [classes.dismissing]: isDismissing, | ||
159 | })} | ||
160 | data-type="franz-infobox" | ||
161 | > | ||
162 | {icon && ( | ||
163 | <Icon icon={icon} className={classes.icon} /> | ||
164 | )} | ||
165 | <div className={classes.content}> | ||
166 | {children} | ||
167 | </div> | ||
168 | {ctaLabel && ( | ||
169 | <button | ||
170 | className={classes.cta} | ||
171 | onClick={ctaOnClick} | ||
172 | type="button" | ||
173 | > | ||
174 | {ctaLabel} | ||
175 | </button> | ||
176 | )} | ||
177 | {dismissable && ( | ||
178 | <button | ||
179 | type="button" | ||
180 | onClick={this.dismiss.bind(this)} | ||
181 | className={classes.close} | ||
182 | > | ||
183 | <Icon icon="mdiClose" /> | ||
184 | </button> | ||
185 | )} | ||
186 | </div> | ||
187 | </div> | ||
188 | ); | ||
189 | } | ||
190 | } | ||
191 | |||
192 | export 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..46545c786 --- /dev/null +++ b/packages/ui/src/loader/index.tsx | |||
@@ -0,0 +1,48 @@ | |||
1 | import { Theme } from '@meetfranz/theme'; | ||
2 | import classnames from 'classnames'; | ||
3 | import React, { Component } from 'react'; | ||
4 | import injectStyle, { withTheme } from 'react-jss'; | ||
5 | import ReactLoader from 'react-loader'; | ||
6 | |||
7 | import { IWithStyle } from '../typings/generic'; | ||
8 | |||
9 | interface IProps extends IWithStyle { | ||
10 | className?: string; | ||
11 | } | ||
12 | |||
13 | const styles = (theme: Theme) => ({ | ||
14 | container: { | ||
15 | position: 'relative', | ||
16 | height: 60, | ||
17 | }, | ||
18 | }); | ||
19 | |||
20 | class LoaderComponent extends Component<IProps> { | ||
21 | render() { | ||
22 | const { | ||
23 | classes, | ||
24 | className, | ||
25 | theme, | ||
26 | } = this.props; | ||
27 | |||
28 | return ( | ||
29 | <div | ||
30 | className={classnames({ | ||
31 | [classes.container]: true, | ||
32 | [`${className}`]: className, | ||
33 | })} | ||
34 | data-type="franz-loader" | ||
35 | > | ||
36 | <ReactLoader | ||
37 | loaded={false} | ||
38 | width={4} | ||
39 | scale={0.75} | ||
40 | color={theme.colorText} | ||
41 | parentClassName={classes.loader} | ||
42 | /> | ||
43 | </div> | ||
44 | ); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | export const Loader = injectStyle(styles)(withTheme(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 @@ | |||
1 | import { Theme } from '@meetfranz/theme/lib'; | ||
2 | import { Classes } from 'jss'; | ||
3 | |||
4 | export interface IWithStyle { | ||
5 | classes: Classes; | ||
6 | theme: Theme; | ||
7 | } | ||
8 | |||
9 | export type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N; | ||
10 | export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; | ||