@performant-software/shared-components 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/build/index.js +2 -0
  4. package/build/index.js.map +1 -0
  5. package/build/main.css +11 -0
  6. package/index.js +1 -0
  7. package/package.json +36 -0
  8. package/src/api/Attachments.js +28 -0
  9. package/src/api/BaseService.js +127 -0
  10. package/src/api/BaseTransform.js +55 -0
  11. package/src/api/FormDataTransform.js +30 -0
  12. package/src/api/NestedAttributesTransform.js +63 -0
  13. package/src/components/EditContainer.css +0 -0
  14. package/src/components/EditContainer.js +448 -0
  15. package/src/components/GoogleAnalytics.css +0 -0
  16. package/src/components/GoogleAnalytics.js +118 -0
  17. package/src/components/GoogleScript.js +5 -0
  18. package/src/components/InfiniteScroll.css +1 -0
  19. package/src/components/InfiniteScroll.js +120 -0
  20. package/src/components/Keyboard.css +11 -0
  21. package/src/components/Keyboard.js +55 -0
  22. package/src/i18n/en.json +204 -0
  23. package/src/i18n/i18n.js +24 -0
  24. package/src/index.js +34 -0
  25. package/src/utils/Browser.js +8 -0
  26. package/src/utils/Calendar.js +232 -0
  27. package/src/utils/Date.js +10 -0
  28. package/src/utils/DragDrop.js +17 -0
  29. package/src/utils/Element.js +36 -0
  30. package/src/utils/Map.js +27 -0
  31. package/src/utils/Object.js +114 -0
  32. package/src/utils/String.js +20 -0
  33. package/src/utils/Timer.js +32 -0
  34. package/src/utils/Utility.js +14 -0
  35. package/test/api/Attachments.spec.js +32 -0
  36. package/types/api/Attachments.js.flow +28 -0
  37. package/types/api/BaseService.js.flow +127 -0
  38. package/types/api/BaseTransform.js.flow +55 -0
  39. package/types/api/FormDataTransform.js.flow +30 -0
  40. package/types/api/NestedAttributesTransform.js.flow +63 -0
  41. package/types/components/EditContainer.js.flow +448 -0
  42. package/types/components/GoogleAnalytics.js.flow +118 -0
  43. package/types/components/GoogleScript.js.flow +5 -0
  44. package/types/components/InfiniteScroll.js.flow +120 -0
  45. package/types/components/Keyboard.js.flow +55 -0
  46. package/types/i18n/i18n.js.flow +24 -0
  47. package/types/index.js.flow +34 -0
  48. package/types/utils/Browser.js.flow +8 -0
  49. package/types/utils/Calendar.js.flow +232 -0
  50. package/types/utils/Date.js.flow +10 -0
  51. package/types/utils/DragDrop.js.flow +17 -0
  52. package/types/utils/Element.js.flow +36 -0
  53. package/types/utils/Map.js.flow +27 -0
  54. package/types/utils/Object.js.flow +114 -0
  55. package/types/utils/String.js.flow +20 -0
  56. package/types/utils/Timer.js.flow +32 -0
  57. package/types/utils/Utility.js.flow +14 -0
  58. package/webpack.config.js +3 -0
@@ -0,0 +1,55 @@
1
+ // @flow
2
+
3
+ import React, { useEffect, useRef } from 'react';
4
+ import Keyboard from 'simple-keyboard';
5
+ import './Keyboard.css';
6
+
7
+ type Props = {
8
+ layout: any,
9
+ onChange: (value: string) => void,
10
+ value: string,
11
+ keyboardClass: string,
12
+ };
13
+
14
+ const KEY_CAPS_LOCK = '{lock}';
15
+ const KEY_SHIFT = '{shift}';
16
+
17
+ const LAYOUT_DEFAULT = 'default';
18
+ const LAYOUT_SHIFT = 'shift';
19
+
20
+ const KeyboardSimple = (props: Props) => {
21
+ const keyboardRef = useRef();
22
+
23
+ // Toggles the layout name for Shift and CapsLock keys.
24
+ const onKeyPress = (value) => {
25
+ const isCaps = value === KEY_CAPS_LOCK || value === KEY_SHIFT;
26
+
27
+ if (isCaps && keyboardRef.current && keyboardRef.current.options.layoutName === LAYOUT_SHIFT) {
28
+ keyboardRef.current.setOptions({ layoutName: LAYOUT_DEFAULT });
29
+ } else if (isCaps && keyboardRef.current && keyboardRef.current.options.layoutName !== LAYOUT_SHIFT) {
30
+ keyboardRef.current.setOptions({ layoutName: LAYOUT_SHIFT });
31
+ }
32
+ };
33
+
34
+ // Sets up the keyboard reference and the initial value when the component is first rendered.
35
+ useEffect(() => {
36
+ const { layout, onChange } = props;
37
+ keyboardRef.current = new Keyboard(`.${props.keyboardClass}`, { ...layout, onChange, onKeyPress });
38
+ keyboardRef.current.setInput(props.value);
39
+ }, []);
40
+
41
+ // Sets the keyboard value when the value property changes.
42
+ useEffect(() => {
43
+ if (keyboardRef.current) {
44
+ keyboardRef.current.setInput(props.value);
45
+ }
46
+ }, [props.value]);
47
+
48
+ return <div className={props.keyboardClass} />;
49
+ };
50
+
51
+ KeyboardSimple.defaultProps = {
52
+ keyboardClass: 'simple-keyboard'
53
+ };
54
+
55
+ export default KeyboardSimple;
@@ -0,0 +1,24 @@
1
+ import i18next from 'i18next';
2
+
3
+ import en from './en.json';
4
+
5
+ const resources = {
6
+ en: {
7
+ translation: en
8
+ }
9
+ };
10
+
11
+ const i18n = i18next.createInstance();
12
+
13
+ i18n
14
+ .init({
15
+ debug: true,
16
+ fallbackLng: 'en',
17
+ lng: 'en',
18
+ interpolation: {
19
+ escapeValue: false,
20
+ },
21
+ resources
22
+ });
23
+
24
+ export default i18n;
@@ -0,0 +1,34 @@
1
+ // @flow
2
+
3
+ // API
4
+ export { default as Attachments } from './api/Attachments';
5
+ export { default as BaseService } from './api/BaseService';
6
+ export { default as BaseTransform } from './api/BaseTransform';
7
+ export { default as FormDataTransform } from './api/FormDataTransform';
8
+ export { default as NestedAttributesTransform } from './api/NestedAttributesTransform';
9
+
10
+ // Components
11
+ export { default as useEditContainer } from './components/EditContainer';
12
+ export { default as withGoogleAnalytics } from './components/GoogleAnalytics';
13
+ export { default as GoogleScript } from './components/GoogleScript';
14
+ export { default as InfiniteScroll } from './components/InfiniteScroll';
15
+ export { default as Keyboard } from './components/Keyboard';
16
+
17
+ // I18n
18
+ export { default as i18n } from './i18n/i18n';
19
+
20
+ // Utils
21
+ export * as Browser from './utils/Browser';
22
+ export { default as Calendar } from './utils/Calendar';
23
+ export { default as Date } from './utils/Date';
24
+ export { default as useDragDrop } from './utils/DragDrop';
25
+ export { default as Element } from './utils/Element';
26
+ export { default as Map } from './utils/Map';
27
+ export { default as Object } from './utils/Object';
28
+ export { default as String } from './utils/String';
29
+ export { default as Timer } from './utils/Timer';
30
+ export { default as Utility } from './utils/Utility'; // TODO: Rename me
31
+
32
+ // Types
33
+ export type { EditContainerProps as EditModalProps } from './components/EditContainer'; // Backwards compatability
34
+ export type { EditContainerProps } from './components/EditContainer';
@@ -0,0 +1,8 @@
1
+ // @flow
2
+
3
+ /**
4
+ * Returns true if the window object is present.
5
+ *
6
+ * @returns {boolean}
7
+ */
8
+ export const isBrowser = () => (typeof window !== 'undefined');
@@ -0,0 +1,232 @@
1
+ // @flow
2
+
3
+ import moment from 'moment-islamic-civil';
4
+ import 'moment/min/locales';
5
+
6
+ const DEFAULT_LOCALE = 'en';
7
+
8
+ const MAX_DAYS_IN_MONTH = 31;
9
+
10
+ /**
11
+ * Wrapper class to handle momentJS dates to multiple calendars.
12
+ */
13
+ class Calendar {
14
+ static Calendars: any;
15
+ static DateFormats: any;
16
+ static Defaults: any;
17
+
18
+ locale: string;
19
+ name: string;
20
+
21
+ /**
22
+ * Constructs a new calendar instance for the passed locale and calendar.
23
+ *
24
+ * @param locale
25
+ * @param name
26
+ */
27
+ constructor(locale: string = DEFAULT_LOCALE, name: string = Calendar.Calendars.gregorian) {
28
+ this.locale = locale;
29
+ this.name = name;
30
+ }
31
+
32
+ /**
33
+ * Increments the date by the passed value.
34
+ *
35
+ * @param date
36
+ * @param value
37
+ *
38
+ * @returns {*}
39
+ */
40
+ addDate(date: Date, value: number) {
41
+ const m = this.moment(date);
42
+ return this.isHijri() ? m.add(value, 'iDate') : m.add(value, 'date');
43
+ }
44
+
45
+ /**
46
+ * Increments the month by the passed value.
47
+ *
48
+ * @param date
49
+ * @param value
50
+ *
51
+ * @returns {*}
52
+ */
53
+ addMonth(date: Date, value: number) {
54
+ const m = this.moment(date);
55
+ return this.isHijri() ? m.add(value, 'iMonth') : m.add(value, 'month');
56
+ }
57
+
58
+ /**
59
+ * Increments the year by the passed value.
60
+ *
61
+ * @param date
62
+ * @param value
63
+ *
64
+ * @returns {*}
65
+ */
66
+ addYear(date: Date, value: number) {
67
+ const m = this.moment(date);
68
+ return this.isHijri() ? m.add(value, 'iYear') : m.add(value, 'year');
69
+ }
70
+
71
+ /**
72
+ * Converts the passed year, month, and date to a moment Date object.
73
+ *
74
+ * @param year
75
+ * @param month
76
+ * @param date
77
+ *
78
+ * @returns {moment.Moment}
79
+ */
80
+ convertToDate({ year, month, date }: { year: number, month: number, date: number }) {
81
+ let m = this.moment().hours(0).minutes(0).seconds(0);
82
+
83
+ if (this.isHijri()) {
84
+ m = m
85
+ .iYear(year || this.getDefaultYear())
86
+ .iMonth(month || this.getDefaultMonth())
87
+ .iDate(date || this.getDefaultDate());
88
+ } else {
89
+ m = m
90
+ .year(year || this.getDefaultYear())
91
+ .month(month || this.getDefaultMonth())
92
+ .date(date || this.getDefaultDate());
93
+ }
94
+
95
+ return m;
96
+ }
97
+
98
+ /**
99
+ * Returns the days in the month for the passed year/month.
100
+ *
101
+ * @param year
102
+ * @param month
103
+ *
104
+ * @returns {number}
105
+ */
106
+ daysInMonth(year: number, month: number) {
107
+ const y = year || this.getDefaultYear();
108
+ const m = month || this.getDefaultMonth();
109
+
110
+ const date = this.convertToDate({ year: y, month: m, date: this.getDefaultDate() });
111
+ return (this.isHijri() ? date.iDaysInMonth() : date.daysInMonth()) || MAX_DAYS_IN_MONTH;
112
+ }
113
+
114
+ /**
115
+ * Formats the passed date.
116
+ *
117
+ * @param date
118
+ * @param format
119
+ *
120
+ * @returns {*}
121
+ */
122
+ format(date: moment, format: number = Calendar.DateFormats.gregorian) {
123
+ return this.moment(date).format(Calendar.DateFormats[this.name][format]);
124
+ }
125
+
126
+ /**
127
+ * Returns the default date for the current calendar.
128
+ *
129
+ * @returns {*}
130
+ */
131
+ getDefaultDate() {
132
+ return Calendar.Defaults[this.name].date;
133
+ }
134
+
135
+ /**
136
+ * Returns the default month for the current calendar.
137
+ *
138
+ * @returns {*}
139
+ */
140
+ getDefaultMonth() {
141
+ return Calendar.Defaults[this.name].month;
142
+ }
143
+
144
+ /**
145
+ * Returns the default year for the current calendar.
146
+ *
147
+ * @returns {number|*|"numeric"|"2-digit"|string|((y: number) => moment.Moment)|(() => number)|moment.numberlike}
148
+ */
149
+ getDefaultYear() {
150
+ return Calendar.Defaults[this.name].year;
151
+ }
152
+
153
+ /**
154
+ * Returns true if the current calendar is Hijri.
155
+ *
156
+ * @returns {boolean}
157
+ */
158
+ isHijri() {
159
+ return this.name === Calendar.Calendars.hijri;
160
+ }
161
+
162
+ /**
163
+ * Returns the list of months for the current calendar.
164
+ *
165
+ * @returns {*}
166
+ */
167
+ listMonths() {
168
+ const localeData = this.moment().localeData();
169
+ return this.isHijri() ? localeData._iMonths : localeData.months();
170
+ }
171
+
172
+ /**
173
+ * Wraps the passed date/string in a moment object.
174
+ *
175
+ * @param date
176
+ */
177
+ moment(date: ?any = null) {
178
+ const m = date ? moment(date) : moment();
179
+ m.locale(this.locale);
180
+ return m;
181
+ }
182
+
183
+ /**
184
+ * Parses the passed date into year, month, and date components.
185
+ *
186
+ * @param date
187
+ *
188
+ * @returns {{date: (*), month: (*), year: (*)}}
189
+ */
190
+ parseDate(date: Date | string) {
191
+ const m = this.moment(date);
192
+
193
+ return {
194
+ year: this.isHijri() ? m.iYear() : m.year(),
195
+ month: this.isHijri() ? m.iMonth() : m.month(),
196
+ date: this.isHijri() ? m.iDate() : m.date()
197
+ };
198
+ }
199
+ }
200
+
201
+ Calendar.Calendars = {
202
+ gregorian: 'gregorian',
203
+ hijri: 'hijri'
204
+ };
205
+
206
+ Calendar.DateFormats = {
207
+ [Calendar.Calendars.gregorian]: {
208
+ '0': 'YYYY',
209
+ '1': 'MMMM YYYY',
210
+ '2': 'L'
211
+ },
212
+ [Calendar.Calendars.hijri]: {
213
+ '0': 'iYYYY',
214
+ '1': 'iMMMM iYYYY',
215
+ '2': 'iM/iD/iYYYY'
216
+ }
217
+ };
218
+
219
+ Calendar.Defaults = {
220
+ [Calendar.Calendars.gregorian]: {
221
+ year: 0,
222
+ month: 0,
223
+ date: 1
224
+ },
225
+ [Calendar.Calendars.hijri]: {
226
+ year: 1410,
227
+ month: 0,
228
+ date: 1
229
+ }
230
+ };
231
+
232
+ export default Calendar;
@@ -0,0 +1,10 @@
1
+ // @flow
2
+
3
+ const formatDate = (value: any, locale?: string, options?: any) => {
4
+ const date = new Date(value);
5
+ return date.toLocaleDateString(locale, options);
6
+ };
7
+
8
+ export default {
9
+ formatDate
10
+ };
@@ -0,0 +1,17 @@
1
+ // @flow
2
+
3
+ import React, { type ComponentType } from 'react';
4
+ import { DndProvider } from 'react-dnd';
5
+ import { HTML5Backend, type BackendFactory } from 'react-dnd-html5-backend';
6
+
7
+ const useDragDrop = (WrappedComponent: ComponentType<any>, backend: BackendFactory = HTML5Backend) => (
8
+ () => (
9
+ <DndProvider
10
+ backend={backend}
11
+ >
12
+ <WrappedComponent />
13
+ </DndProvider>
14
+ )
15
+ );
16
+
17
+ export default useDragDrop;
@@ -0,0 +1,36 @@
1
+ // @flow
2
+
3
+ import React from 'react';
4
+
5
+ /**
6
+ * Returns the subset of children matching the passed component type. This function is useful for designing components
7
+ * using a sub-component type syntax:
8
+ *
9
+ * <MyComponent ...>
10
+ * <MyComponent.Subcomponent ... />
11
+ * <MyComponent.AnotherSubcomponent ... />
12
+ * </MyComponent>
13
+ *
14
+ * @param children
15
+ * @param component
16
+ *
17
+ * @returns {[]}
18
+ */
19
+ const findByType = (children: any, component: any) => {
20
+ const components = [];
21
+
22
+ const type = [component.displayName || component.name];
23
+
24
+ React.Children.forEach(children, (child) => {
25
+ const childType = child && child.type && (child.type.displayName || child.type.name);
26
+ if (type.includes(childType)) {
27
+ components.push(child);
28
+ }
29
+ });
30
+
31
+ return components;
32
+ };
33
+
34
+ export default {
35
+ findByType
36
+ };
@@ -0,0 +1,27 @@
1
+ // @flow
2
+
3
+ /**
4
+ * Returns the numeric latitude and longitude for the passed coordinates.
5
+ *
6
+ * @param coords
7
+ *
8
+ * @returns {*}
9
+ */
10
+ const getPosition = (coords: ?{ lat: any, lng: any }) => {
11
+ let position;
12
+
13
+ if (coords) {
14
+ const lat = Number.parseFloat(coords.lat);
15
+ const lng = Number.parseFloat(coords.lng);
16
+
17
+ if (!Number.isNaN(lat) && !Number.isNaN(lng)) {
18
+ position = { lat, lng };
19
+ }
20
+ }
21
+
22
+ return position;
23
+ };
24
+
25
+ export default {
26
+ getPosition
27
+ };
@@ -0,0 +1,114 @@
1
+ // @flow
2
+
3
+ import _ from 'underscore';
4
+
5
+ type OptionsProps = {
6
+ emptyValues: Array<any>,
7
+ ignoreHtml: boolean
8
+ };
9
+
10
+ const EMPTY_VALUES = [
11
+ '',
12
+ null,
13
+ undefined,
14
+ [],
15
+ {}
16
+ ];
17
+
18
+ const DEFAULT_OPTIONS = {
19
+ emptyValues: EMPTY_VALUES,
20
+ ignoreHtml: true,
21
+ ignoreWhitespace: true
22
+ };
23
+
24
+ const HTML_REGEX = /(<([^>]+)>)/gi;
25
+ const WHITESPACE_REGEX = /\s\s+/g;
26
+
27
+ /**
28
+ * Returns true if the passed value is considered "empty".
29
+ *
30
+ * @param value
31
+ *
32
+ * @returns {boolean|*}
33
+ */
34
+ export const isEmpty = (value: any) => {
35
+ // If the value is an object or array, use underscore's isEmpty check.
36
+ if (_.isObject(value) || _.isArray(value)) {
37
+ return _.isEmpty(value);
38
+ }
39
+
40
+ return !value;
41
+ };
42
+
43
+ /**
44
+ * Returns true if the passed two arguments as deep equal. This function will perform a recursive check against all of
45
+ * the keys in the objects.
46
+ *
47
+ * @param a
48
+ * @param b
49
+ *
50
+ * @returns {boolean}
51
+ */
52
+ export const isEqual = (a: any, b: any, userOptions: OptionsProps = {}) => {
53
+ const options = _.defaults(userOptions, DEFAULT_OPTIONS);
54
+
55
+ // Check equality, consider empty strings and "null" values as equal
56
+ if (a === b || (_.contains(options.emptyValues, a) && _.contains(options.emptyValues, b))) {
57
+ return true;
58
+ }
59
+
60
+ // Deep string comparison
61
+ if (_.isString(a) && _.isString(b)) {
62
+ let aString = a;
63
+ let bString = b;
64
+
65
+ // Remove superfluous whitespace
66
+ if (options.ignoreWhitespace) {
67
+ aString = a.replace(WHITESPACE_REGEX, ' ');
68
+ bString = b.replace(WHITESPACE_REGEX, ' ');
69
+ }
70
+
71
+ // If we're ignoring HTML, compare the string values with HTML tags removed
72
+ if (options.ignoreHtml) {
73
+ aString = aString.replace(HTML_REGEX, '');
74
+ bString = bString.replace(HTML_REGEX, '');
75
+ }
76
+
77
+ if (aString === bString) {
78
+ return true;
79
+ }
80
+ }
81
+
82
+ if (a !== null && typeof a === 'object' && b !== null && typeof b === 'object') {
83
+ const aKeys = _.keys(a);
84
+ const bKeys = _.keys(b);
85
+
86
+ // If the objects contain different number of keys, return false
87
+ if (aKeys.length !== bKeys.length) {
88
+ return false;
89
+ }
90
+
91
+ // Recursively check each key for equality
92
+ let equal = true;
93
+
94
+ _.each(_.keys(a), (key) => {
95
+ if (!(_.has(b, key) && isEqual(a[key], b[key]))) {
96
+ equal = false;
97
+ }
98
+ });
99
+
100
+ // If any of the recursive keys are not equal, return false
101
+ if (!equal) {
102
+ return false;
103
+ }
104
+
105
+ // If we've made it this far, we've checked the equality of all the keys in both objects, return true
106
+ return true;
107
+ }
108
+
109
+ return false;
110
+ };
111
+
112
+ export default {
113
+
114
+ };
@@ -0,0 +1,20 @@
1
+ // @flow
2
+
3
+ import _ from 'underscore';
4
+
5
+ const includes = (a: any, b: any) => (
6
+ a && b && a.toString().toLowerCase().includes(b.toString().toLowerCase())
7
+ );
8
+
9
+ const toString = (value: any) => {
10
+ if (_.isNumber(value) || _.isBoolean(value)) {
11
+ return value;
12
+ }
13
+
14
+ return value || '';
15
+ };
16
+
17
+ export default {
18
+ includes,
19
+ toString
20
+ };
@@ -0,0 +1,32 @@
1
+ // @flow
2
+
3
+ const DEFAULT_TIMEOUT = 500;
4
+
5
+ /**
6
+ * The timer class encapsulates the logic for setting and clearing a timeout. This is particularly useful for
7
+ * keydown/keyup events when we only want to perform an action after the user has finished typing.
8
+ */
9
+ class Timer {
10
+ timeout: TimeoutID | null;
11
+
12
+ constructor() {
13
+ this.timeout = null;
14
+ }
15
+
16
+ /**
17
+ * Clears the search timer.
18
+ */
19
+ clearSearchTimer() {
20
+ clearTimeout(this.timeout);
21
+ }
22
+
23
+ /**
24
+ * Sets the search timer.
25
+ */
26
+ setSearchTimer(onTimeout: () => void) {
27
+ clearTimeout(this.timeout);
28
+ this.timeout = setTimeout(onTimeout, DEFAULT_TIMEOUT);
29
+ }
30
+ }
31
+
32
+ export default new Timer();
@@ -0,0 +1,14 @@
1
+ // @flow
2
+
3
+ /**
4
+ * Returns true if passed value is a promise (i.e, has a .then method)
5
+ *
6
+ * @param *
7
+ *
8
+ * @returns {boolean}
9
+ */
10
+ const isPromise = (value: any) => !!value && typeof value === 'object' && typeof value.then === 'function';
11
+
12
+ export default {
13
+ isPromise
14
+ };
@@ -0,0 +1,3 @@
1
+ const { configure } = require('@performant-software/webpack-config');
2
+
3
+ module.exports = configure(__dirname);