@draftbit/core 44.2.1-e53c90.0 → 44.2.2

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.
@@ -1,31 +1,77 @@
1
1
  import * as React from "react";
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ Text,
6
+ Platform,
7
+ ViewStyle,
8
+ StyleProp,
9
+ Dimensions,
10
+ } from "react-native";
11
+ import { omit, pickBy, identity, isObject } from "lodash";
12
+ import { SafeAreaView } from "react-native-safe-area-context";
13
+ import { Picker as NativePicker } from "@react-native-picker/picker";
14
+
2
15
  import { withTheme } from "../../theming";
3
- //@ts-ignore
4
- import PickerComponent from "./PickerComponent"; //unable to find file due to using .android/.web/.ios
5
- import { PickerComponentProps, PickerOption } from "./PickerTypes";
16
+ import Portal from "../Portal/Portal";
17
+ import Button from "../DeprecatedButton";
18
+ import Touchable from "../Touchable";
19
+ import type { Theme } from "../../styles/DefaultTheme";
20
+ import type { IconSlot } from "../../interfaces/Icon";
21
+ import {
22
+ extractStyles,
23
+ extractBorderAndMarginStyles,
24
+ borderStyleNames,
25
+ marginStyleNames,
26
+ } from "../../utilities";
27
+
28
+ export interface PickerOption {
29
+ value: string;
30
+ label: string;
31
+ }
6
32
 
7
- type Props = PickerComponentProps & {
33
+ export type PickerProps = {
34
+ error?: any;
8
35
  placeholder?: string;
36
+ disabled?: boolean;
37
+ style?: StyleProp<ViewStyle> & { height?: number };
9
38
  value?: string;
10
39
  options: PickerOption[] | string[];
40
+ onValueChange: (value: string, index: number) => void;
41
+ defaultValue?: string;
42
+ assistiveText?: string;
43
+ label?: string;
44
+ iconColor?: string;
45
+ iconSize?: number;
46
+ leftIconMode?: "inset" | "outset";
47
+ leftIconName?: string;
48
+ placeholderTextColor?: string;
49
+ rightIconName?: string;
50
+ type?: "solid" | "underline";
51
+ theme: Theme;
52
+ Icon: IconSlot["Icon"];
11
53
  };
12
54
 
13
- function normalizeOptions(options: Props["options"]): PickerOption[] {
55
+ function normalizeOptions(options: PickerProps["options"]): PickerOption[] {
14
56
  if (options.length === 0) {
15
57
  return [];
16
58
  }
17
59
 
18
- if (typeof options[0] === "string") {
60
+ if (typeof options[0] === ("string" || "number")) {
19
61
  return (options as string[]).map((option) => ({
20
- label: option,
62
+ label: String(option),
21
63
  value: String(option),
22
64
  }));
23
65
  }
24
66
 
25
- if (options[0].label && options[0].value) {
26
- return options.map((option) => {
67
+ if (
68
+ isObject(options[0]) &&
69
+ options[0].value !== null &&
70
+ options[0].label !== null
71
+ ) {
72
+ return (options as PickerOption[]).map((option) => {
27
73
  return {
28
- label: option.label,
74
+ label: String(option.label),
29
75
  value: String(option.value),
30
76
  };
31
77
  });
@@ -36,18 +82,45 @@ function normalizeOptions(options: Props["options"]): PickerOption[] {
36
82
  );
37
83
  }
38
84
 
39
- const Picker: React.FC<Props> = ({
85
+ const { width: deviceWidth, height: deviceHeight } = Dimensions.get("screen");
86
+ const isIos = Platform.OS === "ios";
87
+ const unstyledColor = "rgba(165, 173, 183, 1)";
88
+ const disabledColor = "rgb(240, 240, 240)";
89
+ const errorColor = "rgba(255, 69, 100, 1)";
90
+
91
+ const Picker: React.FC<PickerProps> = ({
92
+ error,
40
93
  options = [],
94
+ onValueChange,
95
+ defaultValue,
96
+ Icon,
97
+ style,
41
98
  placeholder,
42
- onValueChange: onValueChangeOverride,
43
99
  value,
44
- defaultValue,
45
- ...props
100
+ disabled = false,
101
+ theme,
102
+ assistiveText,
103
+ label,
104
+ iconColor = unstyledColor,
105
+ iconSize = 24,
106
+ leftIconMode = "inset",
107
+ leftIconName,
108
+ placeholderTextColor = unstyledColor,
109
+ rightIconName,
110
+ type = "solid",
46
111
  }) => {
112
+ const androidPickerRef = React.useRef<any | undefined>(undefined);
113
+
47
114
  const [internalValue, setInternalValue] = React.useState<string | undefined>(
48
115
  value || defaultValue
49
116
  );
50
117
 
118
+ const [pickerVisible, setPickerVisible] = React.useState(false);
119
+
120
+ const togglePickerVisible = () => {
121
+ setPickerVisible(!pickerVisible);
122
+ };
123
+
51
124
  React.useEffect(() => {
52
125
  if (value != null) {
53
126
  setInternalValue(value);
@@ -60,16 +133,11 @@ const Picker: React.FC<Props> = ({
60
133
  }
61
134
  }, [defaultValue]);
62
135
 
63
- const onValueChange = React.useCallback(
64
- (itemValue: string, itemIndex: number) => {
65
- if (placeholder && itemIndex === 0) {
66
- return;
67
- }
68
- onValueChangeOverride &&
69
- onValueChangeOverride(String(itemValue), itemIndex);
70
- },
71
- [placeholder, onValueChangeOverride]
72
- );
136
+ React.useEffect(() => {
137
+ if (pickerVisible && androidPickerRef.current) {
138
+ androidPickerRef?.current?.focus();
139
+ }
140
+ }, [pickerVisible, androidPickerRef]);
73
141
 
74
142
  const normalizedOptions = normalizeOptions(options);
75
143
 
@@ -77,22 +145,336 @@ const Picker: React.FC<Props> = ({
77
145
  ? [{ value: placeholder, label: placeholder }, ...normalizedOptions]
78
146
  : normalizedOptions;
79
147
 
148
+ const { colors } = theme;
149
+
150
+ const { viewStyles, textStyles } = extractStyles(style);
151
+
152
+ const additionalBorderStyles = ["backgroundColor"];
153
+
154
+ const additionalMarginStyles = [
155
+ "bottom",
156
+ "height",
157
+ "left",
158
+ "maxHeight",
159
+ "maxWidth",
160
+ "minHeight",
161
+ "minWidth",
162
+ "overflow",
163
+ "position",
164
+ "right",
165
+ "top",
166
+ "width",
167
+ "zIndex",
168
+ ];
169
+
170
+ const {
171
+ borderStyles: extractedBorderStyles,
172
+ marginStyles: extractedMarginStyles,
173
+ } = extractBorderAndMarginStyles(
174
+ viewStyles,
175
+ additionalBorderStyles,
176
+ additionalMarginStyles
177
+ );
178
+
179
+ const borderStyles = {
180
+ ...{
181
+ ...(type === "solid"
182
+ ? {
183
+ borderTopLeftRadius: 5,
184
+ borderTopRightRadius: 5,
185
+ borderBottomRightRadius: 5,
186
+ borderBottomLeftRadius: 5,
187
+ borderTopWidth: 1,
188
+ borderRightWidth: 1,
189
+ borderLeftWidth: 1,
190
+ }
191
+ : {}),
192
+ borderBottomWidth: 1,
193
+ borderColor: unstyledColor,
194
+ borderStyle: "solid",
195
+ },
196
+ ...extractedBorderStyles,
197
+ ...(error ? { borderColor: errorColor } : {}),
198
+ ...(disabled
199
+ ? { borderColor: "transparent", backgroundColor: disabledColor }
200
+ : {}),
201
+ };
202
+
203
+ const marginStyles = {
204
+ height: 60,
205
+ ...extractedMarginStyles,
206
+ };
207
+
208
+ const stylesWithoutBordersAndMargins = omit(viewStyles, [
209
+ ...borderStyleNames,
210
+ ...marginStyleNames,
211
+ ...additionalBorderStyles,
212
+ ...additionalMarginStyles,
213
+ ]);
214
+
215
+ const selectedLabel =
216
+ internalValue &&
217
+ ((pickerOptions as unknown as PickerOption[]).find(
218
+ (option) => option.value === internalValue
219
+ )?.label ??
220
+ internalValue);
221
+
222
+ const labelText = label ? (
223
+ <Text
224
+ style={{
225
+ textAlign: textStyles.textAlign,
226
+ color: unstyledColor,
227
+ fontSize: 12,
228
+ paddingBottom: 4,
229
+ }}
230
+ >
231
+ {label}
232
+ </Text>
233
+ ) : null;
234
+
235
+ const leftIconOutset = leftIconMode === "outset";
236
+
237
+ const leftIcon = leftIconName ? (
238
+ <Icon
239
+ name={leftIconName}
240
+ color={disabled ? unstyledColor : iconColor}
241
+ size={iconSize}
242
+ style={{
243
+ marginRight: 4,
244
+ marginLeft: 4,
245
+ }}
246
+ />
247
+ ) : null;
248
+
249
+ const rightIcon = rightIconName ? (
250
+ <Icon
251
+ name={rightIconName}
252
+ color={disabled ? unstyledColor : iconColor}
253
+ size={iconSize}
254
+ style={{
255
+ marginRight: -10,
256
+ marginLeft: 8,
257
+ }}
258
+ />
259
+ ) : null;
260
+
261
+ const textAlign = textStyles?.textAlign;
262
+
263
+ const calculateLeftPadding = () => {
264
+ if (leftIconOutset) {
265
+ if (textAlign === "center") {
266
+ return iconSize - Math.abs(8 - iconSize);
267
+ }
268
+
269
+ return iconSize + 8;
270
+ }
271
+
272
+ return 0;
273
+ };
274
+
275
+ const assistiveTextLabel = assistiveText ? (
276
+ <Text
277
+ style={{
278
+ textAlign,
279
+ width: "100%",
280
+ paddingLeft: calculateLeftPadding(),
281
+ color: unstyledColor,
282
+ fontSize: 12,
283
+ paddingTop: 4,
284
+ }}
285
+ >
286
+ {assistiveText}
287
+ </Text>
288
+ ) : null;
289
+
290
+ const primaryTextStyle = {
291
+ color: unstyledColor,
292
+ fontSize: 14,
293
+ ...pickBy(textStyles, identity),
294
+ ...(placeholder === internalValue ? { color: placeholderTextColor } : {}),
295
+ ...(disabled ? { color: unstyledColor } : {}),
296
+ };
297
+
80
298
  const handleValueChange = (newValue: string, itemIndex: number) => {
81
- setInternalValue(newValue);
82
- if (onValueChange) {
83
- onValueChange(newValue, itemIndex);
299
+ if (!placeholder || itemIndex > 0) {
300
+ onValueChange?.(newValue, itemIndex);
84
301
  }
302
+ setInternalValue(newValue);
85
303
  };
86
304
 
87
305
  return (
88
- <PickerComponent
89
- {...props}
90
- selectedValue={String(internalValue)}
91
- placeholder={placeholder}
92
- options={pickerOptions}
93
- onValueChange={handleValueChange}
94
- />
306
+ /* marginsContainer */
307
+ <View style={[styles.marginsContainer, marginStyles]}>
308
+ {/* touchableContainer */}
309
+ <Touchable
310
+ disabled={disabled}
311
+ onPress={togglePickerVisible}
312
+ style={styles.touchableContainer}
313
+ >
314
+ {/* outsetContainer */}
315
+ <View
316
+ pointerEvents="none"
317
+ style={[
318
+ styles.outsetContainer,
319
+ stylesWithoutBordersAndMargins,
320
+ !leftIconOutset ? (borderStyles as PickerProps["style"]) : {},
321
+ ]}
322
+ >
323
+ {leftIcon}
324
+
325
+ {/* insetContainer */}
326
+ <View
327
+ style={[
328
+ styles.insetContainer,
329
+ leftIconOutset ? (borderStyles as PickerProps["style"]) : {},
330
+ ]}
331
+ >
332
+ {/* primaryTextContainer */}
333
+ <View style={styles.primaryTextContainer}>
334
+ {labelText}
335
+
336
+ <Text style={primaryTextStyle}>
337
+ {String(selectedLabel ?? placeholder)}
338
+ </Text>
339
+ </View>
340
+
341
+ {rightIcon}
342
+ </View>
343
+ </View>
344
+ {assistiveTextLabel}
345
+ </Touchable>
346
+
347
+ {/* iosPicker */}
348
+ {isIos && pickerVisible ? (
349
+ <Portal>
350
+ <View
351
+ style={[
352
+ styles.iosPicker,
353
+ {
354
+ backgroundColor: colors.divider,
355
+ },
356
+ ]}
357
+ >
358
+ <SafeAreaView style={styles.iosSafeArea}>
359
+ <Button
360
+ Icon={Icon}
361
+ type="text"
362
+ onPress={togglePickerVisible}
363
+ style={styles.iosButton}
364
+ >
365
+ {"Close"}
366
+ </Button>
367
+
368
+ <NativePicker
369
+ style={styles.iosNativePicker}
370
+ selectedValue={internalValue}
371
+ onValueChange={handleValueChange}
372
+ >
373
+ {(pickerOptions as unknown as PickerOption[]).map((option) => (
374
+ <NativePicker.Item
375
+ label={option.label}
376
+ value={option.value}
377
+ key={option.value}
378
+ />
379
+ ))}
380
+ </NativePicker>
381
+ </SafeAreaView>
382
+ </View>
383
+ </Portal>
384
+ ) : null}
385
+
386
+ {/* nonIosPicker */}
387
+ {!isIos && pickerVisible ? (
388
+ <NativePicker
389
+ enabled={pickerVisible}
390
+ selectedValue={internalValue}
391
+ onValueChange={handleValueChange}
392
+ style={styles.nonIosPicker}
393
+ ref={androidPickerRef}
394
+ onBlur={() => setPickerVisible(false)}
395
+ >
396
+ {(pickerOptions as unknown as PickerOption[]).map((option) => (
397
+ <NativePicker.Item
398
+ label={option.label}
399
+ value={option.value}
400
+ key={option.value}
401
+ />
402
+ ))}
403
+ </NativePicker>
404
+ ) : null}
405
+ </View>
95
406
  );
96
407
  };
97
408
 
98
409
  export default withTheme(Picker);
410
+
411
+ const styles = StyleSheet.create({
412
+ marginsContainer: {
413
+ alignSelf: "stretch",
414
+ alignItems: "center",
415
+ width: "100%",
416
+ maxWidth: deviceWidth,
417
+ },
418
+ touchableContainer: {
419
+ flex: 1,
420
+ height: "100%",
421
+ width: "100%",
422
+ alignSelf: "stretch",
423
+ alignItems: "center",
424
+ },
425
+ outsetContainer: {
426
+ flex: 1,
427
+ height: "100%",
428
+ width: "100%",
429
+ flexDirection: "row",
430
+ alignItems: "center",
431
+ justifyContent: "space-between",
432
+ },
433
+ insetContainer: {
434
+ flex: 1,
435
+ height: "100%",
436
+ width: "100%",
437
+ flexDirection: "row",
438
+ alignItems: "center",
439
+ justifyContent: "space-between",
440
+ paddingLeft: 12,
441
+ paddingRight: 12,
442
+ },
443
+ primaryTextContainer: {
444
+ flex: 1,
445
+ },
446
+ iosPicker: {
447
+ position: "absolute",
448
+ bottom: 0,
449
+ left: 0,
450
+ right: 0,
451
+ flexDirection: "row",
452
+ justifyContent: "center",
453
+ width: "100%",
454
+ maxWidth: deviceWidth,
455
+ maxHeight: deviceHeight,
456
+ },
457
+ iosSafeArea: {
458
+ backgroundColor: "white",
459
+ flexDirection: "column",
460
+ width: "100%",
461
+ maxWidth: deviceWidth,
462
+ },
463
+ iosButton: {
464
+ alignSelf: "flex-end",
465
+ },
466
+ iosNativePicker: {
467
+ backgroundColor: "white",
468
+ },
469
+ nonIosPicker: {
470
+ opacity: 0,
471
+ position: "absolute",
472
+ top: 0,
473
+ left: 0,
474
+ right: 0,
475
+ bottom: 0,
476
+ width: "100%",
477
+ maxWidth: deviceWidth,
478
+ maxHeight: deviceHeight,
479
+ },
480
+ });
@@ -1,4 +1,4 @@
1
- import { COMPONENT_TYPES, Triggers, GROUPS, FORM_TYPES, PROP_TYPES, FIELD_NAME, } from "@draftbit/types";
1
+ import { COMPONENT_TYPES, Triggers, GROUPS, FORM_TYPES, PROP_TYPES, FIELD_NAME, createIconSizeProp, createColorProp, } from "@draftbit/types";
2
2
  const SEED_DATA_PROPS = {
3
3
  label: {
4
4
  group: GROUPS.data,
@@ -131,6 +131,8 @@ export const SEED_DATA = [
131
131
  required: true,
132
132
  group: GROUPS.basic,
133
133
  },
134
+ iconSize: createIconSizeProp({ defaultValue: 24 }),
135
+ iconColor: createColorProp({ label: "Icon Color" }),
134
136
  },
135
137
  layout: {},
136
138
  },
@@ -5,6 +5,8 @@ import {
5
5
  FORM_TYPES,
6
6
  PROP_TYPES,
7
7
  FIELD_NAME,
8
+ createIconSizeProp,
9
+ createColorProp,
8
10
  } from "@draftbit/types";
9
11
 
10
12
  const SEED_DATA_PROPS = {
@@ -143,6 +145,8 @@ export const SEED_DATA = [
143
145
  required: true,
144
146
  group: GROUPS.basic,
145
147
  },
148
+ iconSize: createIconSizeProp({ defaultValue: 24 }),
149
+ iconColor: createColorProp({ label: "Icon Color" }),
146
150
  },
147
151
  layout: {},
148
152
  },
package/src/utilities.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StyleSheet } from "react-native";
2
- import { isString, isNumber } from "lodash";
2
+ import { isString, isNumber, pick, pickBy, identity } from "lodash";
3
3
  export function extractStyles(style) {
4
4
  const { color, fontFamily, fontWeight, fontSize, lineHeight, letterSpacing, textTransform, textAlign, textDecorationLine, textDecorationColor, textDecorationStyle, ...viewStyles } = StyleSheet.flatten(style || {});
5
5
  const textStyles = {
@@ -17,6 +17,54 @@ export function extractStyles(style) {
17
17
  };
18
18
  return { viewStyles, textStyles };
19
19
  }
20
+ export const borderStyleNames = [
21
+ "borderRadius",
22
+ "borderBottomColor",
23
+ "borderBottomEndRadius",
24
+ "borderBottomLeftRadius",
25
+ "borderBottomRightRadius",
26
+ "borderBottomStartRadius",
27
+ "borderBottomWidth",
28
+ "borderColor",
29
+ "borderEndColor",
30
+ "borderLeftColor",
31
+ "borderLeftWidth",
32
+ "borderRadius",
33
+ "borderRightColor",
34
+ "borderRightWidth",
35
+ "borderStartColor",
36
+ "borderStyle",
37
+ "borderTopColor",
38
+ "borderTopEndRadius",
39
+ "borderTopLeftRadius",
40
+ "borderTopRightRadius",
41
+ "borderTopStartRadius",
42
+ "borderTopWidth",
43
+ "borderWidth",
44
+ ];
45
+ export const marginStyleNames = [
46
+ "margin",
47
+ "marginBottom",
48
+ "marginEnd",
49
+ "marginHorizontal",
50
+ "marginLeft",
51
+ "marginRight",
52
+ "marginStart",
53
+ "marginTop",
54
+ "marginVertical",
55
+ ];
56
+ export function extractBorderAndMarginStyles(style, additionalBorderStyles, additionalMarginStyles) {
57
+ const flatStyle = StyleSheet.flatten(style || {});
58
+ const borderStyles = pickBy(pick(flatStyle, [
59
+ ...borderStyleNames,
60
+ ...(additionalBorderStyles ? additionalBorderStyles : []),
61
+ ]), identity);
62
+ const marginStyles = pickBy(pick(flatStyle, [
63
+ ...marginStyleNames,
64
+ ...(additionalMarginStyles ? additionalMarginStyles : []),
65
+ ]), identity);
66
+ return { borderStyles, marginStyles };
67
+ }
20
68
  /**
21
69
  * Merges a style object on top of another style object. In React Native,
22
70
  * keys with undefined values in a style object will still override styles
package/src/utilities.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StyleSheet, StyleProp, TextStyle } from "react-native";
2
- import { isString, isNumber } from "lodash";
2
+ import { isString, isNumber, pick, pickBy, identity } from "lodash";
3
3
 
4
4
  export function extractStyles(style: StyleProp<any>) {
5
5
  const {
@@ -34,6 +34,70 @@ export function extractStyles(style: StyleProp<any>) {
34
34
  return { viewStyles, textStyles };
35
35
  }
36
36
 
37
+ export const borderStyleNames = [
38
+ "borderRadius",
39
+ "borderBottomColor",
40
+ "borderBottomEndRadius",
41
+ "borderBottomLeftRadius",
42
+ "borderBottomRightRadius",
43
+ "borderBottomStartRadius",
44
+ "borderBottomWidth",
45
+ "borderColor",
46
+ "borderEndColor",
47
+ "borderLeftColor",
48
+ "borderLeftWidth",
49
+ "borderRadius",
50
+ "borderRightColor",
51
+ "borderRightWidth",
52
+ "borderStartColor",
53
+ "borderStyle",
54
+ "borderTopColor",
55
+ "borderTopEndRadius",
56
+ "borderTopLeftRadius",
57
+ "borderTopRightRadius",
58
+ "borderTopStartRadius",
59
+ "borderTopWidth",
60
+ "borderWidth",
61
+ ];
62
+
63
+ export const marginStyleNames = [
64
+ "margin",
65
+ "marginBottom",
66
+ "marginEnd",
67
+ "marginHorizontal",
68
+ "marginLeft",
69
+ "marginRight",
70
+ "marginStart",
71
+ "marginTop",
72
+ "marginVertical",
73
+ ];
74
+
75
+ export function extractBorderAndMarginStyles(
76
+ style: StyleProp<any>,
77
+ additionalBorderStyles?: string[],
78
+ additionalMarginStyles?: string[]
79
+ ) {
80
+ const flatStyle = StyleSheet.flatten(style || {});
81
+
82
+ const borderStyles = pickBy(
83
+ pick(flatStyle, [
84
+ ...borderStyleNames,
85
+ ...(additionalBorderStyles ? additionalBorderStyles : []),
86
+ ]),
87
+ identity
88
+ );
89
+
90
+ const marginStyles = pickBy(
91
+ pick(flatStyle, [
92
+ ...marginStyleNames,
93
+ ...(additionalMarginStyles ? additionalMarginStyles : []),
94
+ ]),
95
+ identity
96
+ );
97
+
98
+ return { borderStyles, marginStyles };
99
+ }
100
+
37
101
  /**
38
102
  * Merges a style object on top of another style object. In React Native,
39
103
  * keys with undefined values in a style object will still override styles